From 4fb14e85e8cbb15ed24bd439459c684711595f09 Mon Sep 17 00:00:00 2001 From: Hampus Date: Wed, 2 Sep 2026 17:23:47 +0200 Subject: [PATCH] fix(auth): harden login rate keys and totp reuse (#2347) --- fluxer_api/src/api/auth/AuthLogin.ts | 14 ++++++++++++-- fluxer_api/src/api/auth/AuthMfa.ts | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/fluxer_api/src/api/auth/AuthLogin.ts b/fluxer_api/src/api/auth/AuthLogin.ts index 10ef84498..d374eb5f8 100644 --- a/fluxer_api/src/api/auth/AuthLogin.ts +++ b/fluxer_api/src/api/auth/AuthLogin.ts @@ -38,6 +38,9 @@ import * as AuthPassword from './AuthPassword'; import * as AuthSession from './AuthSession'; import * as AuthUtility from './AuthUtility'; +const DUMMY_ARGON2_HASH = + '$argon2id$v=19$m=65536,t=3,p=4$fT6tGpAyxFiz+n1RbkRqWQ$v05UT17QGeqhsgRjcVjIWcGw6gUDYeCcAA8FiZ63MtA'; + interface LoginParams { data: LoginRequest; request: Request; @@ -194,7 +197,7 @@ export async function login( const {inviteService, kvDeletionQueue} = deps; const skipRateLimits = config.dev.testModeEnabled || config.dev.disableRateLimits; const emailRateLimit = await rateLimit.checkLimit({ - identifier: `login:email:${data.email}`, + identifier: `login:email:${data.email.toLowerCase()}`, maxAttempts: 5, windowMs: ms('15 minutes'), }); @@ -221,9 +224,16 @@ export async function login( ]); } AuthUtility.assertNonBotUser(ctx, user); + if (!user.passwordHash) { + await AuthPassword.verifyPassword(ctx, {password: data.password, passwordHash: DUMMY_ARGON2_HASH}); + throw InputValidationError.fromCodes([ + {path: 'email', code: ValidationErrorCodes.INVALID_EMAIL_OR_PASSWORD}, + {path: 'password', code: ValidationErrorCodes.INVALID_EMAIL_OR_PASSWORD}, + ]); + } const isMatch = await AuthPassword.verifyPassword(ctx, { password: data.password, - passwordHash: user.passwordHash!, + passwordHash: user.passwordHash, }); if (!isMatch) { throw InputValidationError.fromCodes([ diff --git a/fluxer_api/src/api/auth/AuthMfa.ts b/fluxer_api/src/api/auth/AuthMfa.ts index 3e3450fcb..afa1f36f7 100644 --- a/fluxer_api/src/api/auth/AuthMfa.ts +++ b/fluxer_api/src/api/auth/AuthMfa.ts @@ -79,7 +79,7 @@ export async function verifyMfaCode(ctx: ApiContext, params: VerifyMfaCodeParams return true; } const reuseKey = `mfa-totp:${userId}:${code}`; - const lockToken = await cache.acquireLock(reuseKey, seconds('30 seconds')); + const lockToken = await cache.acquireLock(reuseKey, seconds('90 seconds')); if (lockToken) { return true; }