fix(auth): improve security key login flow.

- Fix login to verify password before showing security key prompt
- Add proper 2FA verification flow when deleting security keys

Previously, users with security keys would see the security key prompt
even if they entered an incorrect password. Now the password is verified
first. Additionally, security key deletion now properly handles 2FA
verification when enabled.
This commit is contained in:
Adrian Astles 2025-07-07 17:48:23 +08:00
parent 813992141a
commit f0a1c10ec5
4 changed files with 294 additions and 155 deletions

View file

@ -30,6 +30,7 @@ import config from "@server/lib/config";
import { UserType } from "@server/types/UserTypes";
import { verifyPassword } from "@server/auth/password";
import { unauthorized } from "@server/auth/unauthorizedResponse";
import { verifyTotpCode } from "@server/auth/totp";
// The RP ID is the domain name of your application
const rpID = (() => {
@ -120,7 +121,8 @@ export const verifyAuthenticationBody = z.object({
}).strict();
export const deleteSecurityKeyBody = z.object({
password: z.string().min(1)
password: z.string().min(1),
code: z.string().optional()
}).strict();
export async function startRegistration(
@ -159,17 +161,6 @@ export async function startRegistration(
return next(unauthorized());
}
// If user has 2FA enabled, require a code
if (user.twoFactorEnabled) {
return response<{ codeRequested: boolean }>(res, {
data: { codeRequested: true },
success: true,
error: false,
message: "Two-factor authentication required",
status: HttpCode.ACCEPTED
});
}
// Get existing security keys for user
const existingSecurityKeys = await db
.select()
@ -373,7 +364,7 @@ export async function deleteSecurityKey(
);
}
const { password } = parsedBody.data;
const { password, code } = parsedBody.data;
// Only allow internal users to use security keys
if (user.type !== UserType.Internal) {
@ -392,15 +383,37 @@ export async function deleteSecurityKey(
return next(unauthorized());
}
// If user has 2FA enabled, require a code
// If user has 2FA enabled, require and verify the code
if (user.twoFactorEnabled) {
return response<{ codeRequested: boolean }>(res, {
data: { codeRequested: true },
success: true,
error: false,
message: "Two-factor authentication required",
status: HttpCode.ACCEPTED
});
if (!code) {
return response<{ codeRequested: boolean }>(res, {
data: { codeRequested: true },
success: true,
error: false,
message: "Two-factor authentication required",
status: HttpCode.ACCEPTED
});
}
const validOTP = await verifyTotpCode(
code,
user.twoFactorSecret!,
user.userId
);
if (!validOTP) {
if (config.getRawConfig().app.log_failed_attempts) {
logger.info(
`Two-factor code incorrect. Email: ${user.email}. IP: ${req.ip}.`
);
}
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
"The two-factor code you entered is incorrect"
)
);
}
}
await db