fix: TOTP login flow loses remember_me — sessions expire after 1 hour

The TOTP verification endpoint always created refresh tokens with
remember_me=false and 1-hour expiry, regardless of what the user
selected at login.

Fix:
- Frontend now sends remember_me in the TOTP verify request body
- Backend reads remember_me and uses it for token expiry (30 days)
  and cookie maxAge

Users with 2FA who checked "remember me" will now stay logged in
for 30 days instead of being kicked out after 1 hour.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-23 20:28:54 +01:00
parent c4c4433561
commit 33268b38ae
2 changed files with 10 additions and 4 deletions

View File

@@ -76,6 +76,8 @@ export default async function authRoutes(fastify: FastifyInstance): Promise<void
const parsed = parseBody(TotpVerifySchema, request.body);
if ('error' in parsed) return error(reply, parsed.error, 400);
const { login_token, totp_code } = parsed.data;
const rawBody = request.body as unknown as Record<string, unknown>;
const rememberMe = rawBody.remember_me === true || rawBody.remember_me === 'true';
const tokenHash = crypto.createHash('sha256').update(login_token).digest('hex');
@@ -127,18 +129,22 @@ export default async function authRoutes(fastify: FastifyInstance): Promise<void
const refreshTokenRaw = crypto.randomBytes(32).toString('hex');
const refreshTokenHash = crypto.createHash('sha256').update(refreshTokenRaw).digest('hex');
const expiresIn = rememberMe
? config.jwt.refreshTokenRememberExpiry
: config.jwt.refreshTokenSessionExpiry;
await prisma.refresh_tokens.create({
data: {
user_id: user.id,
token_hash: refreshTokenHash,
expires_at: new Date(Date.now() + config.jwt.refreshTokenSessionExpiry * 1000),
remember_me: false,
expires_at: new Date(Date.now() + expiresIn * 1000),
remember_me: rememberMe,
ip_address: request.ip,
user_agent: request.headers['user-agent'] ?? null,
},
});
setRefreshCookie(reply, refreshTokenRaw, false);
setRefreshCookie(reply, refreshTokenRaw, rememberMe);
return success(reply, { access_token: accessToken, user: authData });
});