diff --git a/cli/commands/disableUser2fa.ts b/cli/commands/disableUser2fa.ts new file mode 100644 index 000000000..8b602c334 --- /dev/null +++ b/cli/commands/disableUser2fa.ts @@ -0,0 +1,60 @@ +import { CommandModule } from "yargs"; +import { db, users } from "@server/db"; +import { eq } from "drizzle-orm"; + +/** + * Disable 2FA for a user by email address. + */ +type DisableUser2faArgs = { + email: string; +}; + +export const disableUser2fa: CommandModule<{}, DisableUser2faArgs> = { + command: "disable-user-2fa", + describe: "Disable 2FA for a user (sets twoFactorEnabled=false, clears secret)", + builder: (yargs) => { + return yargs.option("email", { + type: "string", + demandOption: true, + describe: "User email address" + }); + }, + handler: async (argv: { email: string }) => { + try { + const { email } = argv; + console.log(`Looking for user with email: ${email}`); + + // Find the user by email + const [user] = await db + .select() + .from(users) + .where(eq(users.email, email)) + .limit(1); + + if (!user) { + console.error(`User with email '${email}' not found`); + process.exit(1); + } + + if (!user.twoFactorEnabled) { + console.log(`2FA is already disabled for user '${email}'.`); + process.exit(0); + } + + // Update user: disable 2FA and clear secret + await db.update(users) + .set({ + twoFactorEnabled: false, + twoFactorSecret: null, + twoFactorSetupRequested: false + }) + .where(eq(users.userId, user.userId)); + + console.log(`2FA disabled for user '${email}'.`); + process.exit(0); + } catch (error) { + console.error("Error disabling 2FA:", error); + process.exit(1); + } + } +}; diff --git a/cli/index.ts b/cli/index.ts index 3664bb8f8..19585bc6f 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -10,6 +10,7 @@ import { clearLicenseKeys } from "./commands/clearLicenseKeys"; import { deleteClient } from "./commands/deleteClient"; import { generateOrgCaKeys } from "./commands/generateOrgCaKeys"; import { clearCertificates } from "./commands/clearCertificates"; +import { disableUser2fa } from "./commands/disableUser2fa"; yargs(hideBin(process.argv)) .scriptName("pangctl") @@ -21,5 +22,6 @@ yargs(hideBin(process.argv)) .command(deleteClient) .command(generateOrgCaKeys) .command(clearCertificates) + .command(disableUser2fa) .demandCommand() .help().argv; diff --git a/messages/en-US.json b/messages/en-US.json index 7cbf6b166..52981764b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2668,6 +2668,8 @@ "validPassword": "Valid Password", "validEmail": "Valid email", "validSSO": "Valid SSO", + "view": "View", + "configManaged": "Config Managed", "connectedClient": "Connected Client", "resourceBlocked": "Resource Blocked", "droppedByRule": "Dropped by Rule", diff --git a/src/components/DomainPageClient.tsx b/src/components/DomainPageClient.tsx index 31527c5b8..6d64e42ce 100644 --- a/src/components/DomainPageClient.tsx +++ b/src/components/DomainPageClient.tsx @@ -13,6 +13,8 @@ import DomainCertForm from "@app/components/DomainCertForm"; import { build } from "@server/build"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useTranslations } from "next-intl"; +import { Lock } from "lucide-react"; +import { Badge } from "@app/components/ui/badge"; interface DomainPageClientProps { initialDomain: GetDomainResponse; @@ -49,7 +51,22 @@ export default function DomainPageClient({ <>
{errorMessage}
++ {errorMessage} +
{errorMessage}
++ {errorMessage} +