diff --git a/server/routers/auth/disable2fa.ts b/server/routers/auth/disable2fa.ts index 999083f9..3b7cfb97 100644 --- a/server/routers/auth/disable2fa.ts +++ b/server/routers/auth/disable2fa.ts @@ -9,13 +9,20 @@ import { db } from "@server/db"; import { User, users } from "@server/db/schema"; import { eq } from "drizzle-orm"; import { response } from "@server/utils"; +import { decodeHex } from "oslo/encoding"; +import { TOTPController } from "oslo/otp"; export const disable2faBody = z.object({ password: z.string(), + code: z.string().optional(), }); export type Disable2faBody = z.infer; +export type Disable2faResponse = { + codeRequested?: boolean; +}; + export async function disable2fa( req: Request, res: Response, @@ -32,7 +39,7 @@ export async function disable2fa( ); } - const { password } = parsedBody.data; + const { password, code } = parsedBody.data; const user = req.user as User; try { @@ -54,6 +61,40 @@ export async function disable2fa( "Two-factor authentication is already disabled", ), ); + } else { + if (!code) { + return response<{ codeRequested: boolean }>(res, { + data: { codeRequested: true }, + success: true, + error: false, + message: "Two-factor authentication required", + status: HttpCode.ACCEPTED, + }); + } + } + + if (!user.twoFactorSecret) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to authenticate user", + ), + ); + } + + const validOTP = await new TOTPController().verify( + code, + decodeHex(user.twoFactorSecret), + ); + + if (!validOTP) { + await new Promise((resolve) => setTimeout(resolve, 250)); // delay to prevent brute force attacks + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "The two-factor code you entered is incorrect", + ), + ); } await db