From 2be43b39ecb1a3c217b3901d904584f04720be66 Mon Sep 17 00:00:00 2001 From: Nguyen LNP Date: Thu, 21 May 2026 14:50:43 +0700 Subject: [PATCH] fix: harden auth token handling --- .../framework/core/auth/src/email-verification.ts | 13 +++++++++++-- storage/framework/core/auth/src/password/reset.ts | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/storage/framework/core/auth/src/email-verification.ts b/storage/framework/core/auth/src/email-verification.ts index 5d6cb09689..1596e8ffd7 100644 --- a/storage/framework/core/auth/src/email-verification.ts +++ b/storage/framework/core/auth/src/email-verification.ts @@ -29,17 +29,26 @@ export interface EmailVerificationResult { */ function generateVerificationToken(userId: number): { token: string, hash: string } { const nonce = randomBytes(32).toString('hex') - const appKey = config.app.key || 'stacks-default-key' + const appKey = getAppKey() const payload = `${userId}:${nonce}` const hash = createHmac('sha256', appKey).update(payload).digest('hex') return { token: nonce, hash } } +function getAppKey(): string { + const appKey = config.app.key + if (typeof appKey !== 'string' || appKey.length === 0) { + throw new Error('[auth/email-verification] Missing config.app.key; refusing to sign verification tokens with a fallback key') + } + + return appKey +} + /** * Verify a token matches the stored hash */ function verifyToken(userId: number, token: string, storedHash: string): boolean { - const appKey = config.app.key || 'stacks-default-key' + const appKey = getAppKey() const payload = `${userId}:${token}` const hash = createHmac('sha256', appKey).update(payload).digest('hex') const a = Buffer.from(hash) diff --git a/storage/framework/core/auth/src/password/reset.ts b/storage/framework/core/auth/src/password/reset.ts index 2a2e8dc192..4a2b868062 100644 --- a/storage/framework/core/auth/src/password/reset.ts +++ b/storage/framework/core/auth/src/password/reset.ts @@ -63,6 +63,11 @@ export function passwordResets(email: string): PasswordResetActions { const token = generateResetToken() const hashedToken = await makeHash(token, { algorithm: 'bcrypt' }) + await db + .deleteFrom('password_resets') + .where('email', '=', email) + .execute() + await db .insertInto('password_resets') .values({ @@ -139,6 +144,7 @@ export function passwordResets(email: string): PasswordResetActions { const resetRecord = await trx .selectFrom('password_resets') .where('email', '=', email) + .orderBy('created_at', 'desc') .selectAll() .executeTakeFirst()