From dd9f5f702ad82f94c0429c9ad59c502dc311fc76 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 8 Jan 2026 21:20:17 +0100 Subject: [PATCH] feat: disable 2fa cli --- app/lib/auth.ts | 2 +- app/server/cli/commands/disable-2fa.ts | 74 ++++++++++++++++++++++++++ app/server/cli/index.ts | 2 + 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 app/server/cli/commands/disable-2fa.ts diff --git a/app/lib/auth.ts b/app/lib/auth.ts index e7a75fbb..35939000 100644 --- a/app/lib/auth.ts +++ b/app/lib/auth.ts @@ -46,7 +46,7 @@ const createBetterAuth = (secret: string) => session: { modelName: "sessionsTable", }, - plugins: [username({})], + plugins: [username(), twoFactor()], advanced: { disableOriginCheck: true, }, diff --git a/app/server/cli/commands/disable-2fa.ts b/app/server/cli/commands/disable-2fa.ts new file mode 100644 index 00000000..1e0f1413 --- /dev/null +++ b/app/server/cli/commands/disable-2fa.ts @@ -0,0 +1,74 @@ +import { select } from "@inquirer/prompts"; +import { Command } from "commander"; +import { eq } from "drizzle-orm"; +import { toMessage } from "~/server/utils/errors"; +import { db } from "../../db/db"; +import { twoFactor, usersTable } from "../../db/schema"; + +const listUsers = () => { + return db + .select({ id: usersTable.id, username: usersTable.username }) + .from(usersTable); +}; + +const disable2FA = async (username: string) => { + const [user] = await db + .select() + .from(usersTable) + .where(eq(usersTable.username, username)); + + if (!user) { + throw new Error(`User "${username}" not found`); + } + + if (!user.twoFactorEnabled) { + throw new Error(`User "${username}" does not have 2FA enabled`); + } + + await db.transaction(async (tx) => { + await tx + .update(usersTable) + .set({ twoFactorEnabled: false }) + .where(eq(usersTable.id, user.id)); + await tx.delete(twoFactor).where(eq(twoFactor.userId, user.id)); + }); +}; + +export const disable2FACommand = new Command("disable-2fa") + .description("Disable two-factor authentication for a user") + .option("-u, --username ", "Username of the account") + .action(async (options) => { + console.log("\nšŸ” Zerobyte 2FA Disable\n"); + + let username = options.username; + + if (!username) { + const users = await listUsers(); + + if (users.length === 0) { + console.error("āŒ No users found in the database."); + console.log( + " Please create a user first by starting the application.", + ); + process.exit(1); + } + + username = await select({ + message: "Select user to disable 2FA for:", + choices: users.map((u) => ({ name: u.username, value: u.username })), + }); + } + + try { + await disable2FA(username); + console.log( + `\nāœ… Two-factor authentication has been disabled for user "${username}".`, + ); + console.log(" The user can re-enable 2FA from their account settings."); + } catch (error) { + console.error(`\nāŒ Failed to disable 2FA: ${toMessage(error)}`); + process.exit(1); + } + + process.exit(0); + }); diff --git a/app/server/cli/index.ts b/app/server/cli/index.ts index 9a4be586..74a6d074 100644 --- a/app/server/cli/index.ts +++ b/app/server/cli/index.ts @@ -1,10 +1,12 @@ import { Command } from "commander"; +import { disable2FACommand } from "./commands/disable-2fa"; import { resetPasswordCommand } from "./commands/reset-password"; const program = new Command(); program.name("zerobyte").description("Zerobyte CLI - Backup automation tool built on top of Restic").version("1.0.0"); program.addCommand(resetPasswordCommand); +program.addCommand(disable2FACommand); export async function runCLI(argv: string[]): Promise { const args = argv.slice(2);