import { password, select } from "@inquirer/prompts"; import { hashPassword } from "better-auth/crypto"; import { Command } from "commander"; import { and, eq } from "drizzle-orm"; import { toMessage } from "~/server/utils/errors"; import { db } from "../../db/db"; import { account, sessionsTable, usersTable } from "../../db/schema"; const listUsers = () => { return db.select({ id: usersTable.id, username: usersTable.username }).from(usersTable); }; const resetPassword = async (username: string, newPassword: string) => { const [user] = await db.select().from(usersTable).where(eq(usersTable.username, username)); if (!user) { throw new Error(`User "${username}" not found`); } const newPasswordHash = await hashPassword(newPassword); const legacyHash = user.passwordHash ? await Bun.password.hash(newPassword) : null; db.transaction((tx) => { const existingAccount = tx .select() .from(account) .where(and(eq(account.userId, user.id), eq(account.providerId, "credential"))) .get(); if (existingAccount) { tx.update(account).set({ password: newPasswordHash }).where(eq(account.id, existingAccount.id)).run(); } else { tx.insert(account) .values({ id: crypto.randomUUID(), providerId: "credential", accountId: user.username, userId: user.id, password: newPasswordHash, createdAt: new Date(), updatedAt: new Date(), }) .run(); } if (legacyHash) { tx.update(usersTable).set({ passwordHash: legacyHash }).where(eq(usersTable.id, user.id)).run(); } tx.delete(sessionsTable).where(eq(sessionsTable.userId, user.id)).run(); }); }; export const resetPasswordCommand = new Command("reset-password") .description("Reset password for a user") .option("-u, --username ", "Username of the account") .option("-p, --password ", "New password for the account") .action(async (options) => { console.info("\nšŸ” Zerobyte Password Reset\n"); let username = options.username; let newPassword = options.password; if (!username) { const users = await listUsers(); if (users.length === 0) { console.error("āŒ No users found in the database."); console.info(" Please create a user first by starting the application."); process.exit(1); } username = await select({ message: "Select user to reset password for:", choices: users.map((u) => ({ name: u.username, value: u.username })), }); } if (!newPassword) { newPassword = await password({ message: "Enter new password:", mask: "*", validate: (value) => { if (value.length < 8) { return "Password must be at least 8 characters long"; } return true; }, }); const confirmPassword = await password({ message: "Confirm new password:", mask: "*", }); if (newPassword !== confirmPassword) { console.error("\nāŒ Passwords do not match."); process.exit(1); } } else if (newPassword.length < 8) { console.error("\nāŒ Password must be at least 8 characters long."); process.exit(1); } try { await resetPassword(username, newPassword); console.info(`\nāœ… Password for user "${username}" has been reset successfully.`); console.info(" All existing sessions have been invalidated."); } catch (error) { console.error(`\nāŒ Failed to reset password: ${toMessage(error)}`); process.exit(1); } process.exit(0); });