feat: enhance password security with server-side hashing and constant-time comparisons

This commit is contained in:
shuaiplus
2026-03-01 20:22:48 +08:00
committed by Shuai
parent 4390251c1e
commit e9ace523e6
7 changed files with 88 additions and 22 deletions
+8 -1
View File
@@ -24,5 +24,12 @@ export function createRecoveryCode(): string {
export function recoveryCodeEquals(input: string, storedCode: string | null | undefined): boolean {
if (!storedCode) return false;
return normalizeRecoveryCode(input) === normalizeRecoveryCode(storedCode);
const a = new TextEncoder().encode(normalizeRecoveryCode(input));
const b = new TextEncoder().encode(normalizeRecoveryCode(storedCode));
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) {
diff |= a[i] ^ b[i];
}
return diff === 0;
}
+10 -2
View File
@@ -69,11 +69,19 @@ export async function verifyTotpToken(secretRaw: string, tokenRaw: string, nowMs
if (!secret) return false;
const currentCounter = Math.floor(nowMs / 1000 / TOTP_STEP_SECONDS);
let matched = false;
for (let delta = -TOTP_WINDOW; delta <= TOTP_WINDOW; delta++) {
const expected = await hotp(secret, currentCounter + delta);
if (expected === token) return true;
// Constant-time comparison: always check all windows, never short-circuit.
const a = new TextEncoder().encode(expected);
const b = new TextEncoder().encode(token);
let diff = a.length ^ b.length;
for (let i = 0; i < a.length && i < b.length; i++) {
diff |= a[i] ^ b[i];
}
if (diff === 0) matched = true;
}
return false;
return matched;
}
export function isTotpEnabled(secretRaw: string | undefined | null): boolean {