From 2098a5cdfdad068d13fa6e0edc91b170fdf462d3 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 12 Jul 2024 17:44:45 -0700 Subject: [PATCH] Checking TOTP expiry time. --- src/lib/flashMessages.ts | 4 ++ src/routes/(auth)/login/+page.server.ts | 2 +- src/routes/(auth)/logout/+page.server.ts | 4 +- src/routes/(auth)/totp/+page.server.ts | 62 +++++++++++++++++++++--- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/lib/flashMessages.ts b/src/lib/flashMessages.ts index 141ca4b..b96f045 100644 --- a/src/lib/flashMessages.ts +++ b/src/lib/flashMessages.ts @@ -6,3 +6,7 @@ export const forbiddenMessage = { type: 'error', message: 'You are not allowed to access this', } as const; +export const signedOutMessage = { + type: 'success', + message: 'Successfully signed out', +} \ No newline at end of file diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index 9173289..1bf98a6 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -94,7 +94,7 @@ export const actions: Actions = { if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { await db.update(twoFactor).set({ - initiated_time: new Date(), + initiatedTime: new Date(), }); session = await lucia.createSession(user.id, { diff --git a/src/routes/(auth)/logout/+page.server.ts b/src/routes/(auth)/logout/+page.server.ts index 49655a5..78f9d98 100644 --- a/src/routes/(auth)/logout/+page.server.ts +++ b/src/routes/(auth)/logout/+page.server.ts @@ -1,7 +1,7 @@ import { fail } from '@sveltejs/kit'; import { redirect } from 'sveltekit-flash-message/server'; import { lucia } from '$lib/server/auth'; -import { notSignedInMessage } from '$lib/flashMessages'; +import { signedOutMessage } from '$lib/flashMessages'; import type { Actions } from "./$types"; export const actions: Actions = { @@ -17,6 +17,6 @@ export const actions: Actions = { path: '.', ...sessionCookie.attributes }); - return redirect(302, '/login', notSignedInMessage, event); + return redirect(302, '/login', signedOutMessage, event); } }; diff --git a/src/routes/(auth)/totp/+page.server.ts b/src/routes/(auth)/totp/+page.server.ts index 90b812f..576500a 100644 --- a/src/routes/(auth)/totp/+page.server.ts +++ b/src/routes/(auth)/totp/+page.server.ts @@ -1,4 +1,4 @@ -import { fail, error, type Actions } from '@sveltejs/kit'; +import { fail, error, type Actions, type Cookies, type RequestEvent } from '@sveltejs/kit'; import { and, eq } from 'drizzle-orm'; import { Argon2id } from 'oslo/password'; import { decodeHex } from 'oslo/encoding'; @@ -16,7 +16,8 @@ import { notSignedInMessage } from '$lib/flashMessages'; import env from '../../../env'; export const load: PageServerLoad = async (event) => { - const { user, session } = event.locals; + const { cookies, locals } = event; + const { user, session } = locals; if (!user || !session) { redirect(302, '/login', notSignedInMessage, event); @@ -31,9 +32,33 @@ export const load: PageServerLoad = async (event) => { where: eq(twoFactor.userId, dbUser!.id!), }); + if (!twoFactorDetails || !twoFactorDetails.enabled) { + const message = { type: 'error', message: 'Two factor authentication is not enabled' } as const; + redirect(302, '/login', message, event); + } + + let twoFactorInitiatedTime = twoFactorDetails.initiatedTime; + if (twoFactorInitiatedTime === null) { + console.log('twoFactorInitiatedTime is null'); + twoFactorInitiatedTime = new Date(); + console.log('twoFactorInitiatedTime', twoFactorInitiatedTime); + await db + .update(twoFactor) + .set({ initiatedTime: twoFactorInitiatedTime }) + .where(eq(twoFactor.userId, dbUser!.id!)); + } + // Check if two factor started less than TWO_FACTOR_TIMEOUT - const - if ((Date.now() - twoFactorDetails?.initiatedTime) > env.TWO_FACTOR_TIMEOUT) {s + const timeElapsed = Date.now() - twoFactorInitiatedTime.getTime(); + console.log('Time elapsed', timeElapsed); + if (timeElapsed > env.TWO_FACTOR_TIMEOUT) { + console.log('Time elapsed was more than TWO_FACTOR_TIMEOUT', timeElapsed, env.TWO_FACTOR_TIMEOUT); + await lucia.invalidateSession(session!.id!); + const sessionCookie = lucia.createBlankSessionCookie(); + cookies.set(sessionCookie.name, sessionCookie.value, { + path: '.', + ...sessionCookie.attributes, + }); const message = { type: 'error', message: 'Two factor authentication has expired' } as const; redirect(302, '/login', message, event); } @@ -71,7 +96,7 @@ export const actions: Actions = { throw error(429); } - const { locals } = event; + const { cookies, locals } = event; const session = locals.session; const user = locals.user; @@ -116,15 +141,18 @@ export const actions: Actions = { const twoFactorSecretPopulated = twoFactorDetails?.secret !== '' && twoFactorDetails?.secret !== null; - if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !totpToken) { + if (twoFactorDetails?.enabled && !twoFactorSecretPopulated && !totpToken) { return fail(400, { form, }); } else if (twoFactorSecretPopulated && totpToken) { + // Check if two factor started less than TWO_FACTOR_TIMEOUT + await checkTOTPExpiry(twoFactorDetails, session, cookies, event); + console.log('totpToken', totpToken); const validOTP = await new TOTPController().verify( totpToken, - decodeHex(twoFactorDetails.secret ?? ''), + decodeHex(twoFactorDetails?.secret ?? ''), ); console.log('validOTP', validOTP); @@ -167,6 +195,26 @@ export const actions: Actions = { }, }; +async function checkTOTPExpiry(twoFactorDetails: { id: string; cuid: string | null; secret: string; enabled: boolean; initiatedTime: Date | null; createdAt: Date; updatedAt: Date; userId: string; } | undefined, session, cookies: Cookies, event: RequestEvent>, string | null>) { + const twoFactorInitiatedTime = twoFactorDetails?.initiatedTime; + if (twoFactorInitiatedTime === null || twoFactorInitiatedTime === undefined) { + redirect(302, '/login'); + } + const timeElapsed = Date.now() - twoFactorInitiatedTime.getTime(); + console.log('Time elapsed', timeElapsed); + if (timeElapsed > env.TWO_FACTOR_TIMEOUT) { + console.log('Time elapsed was more than TWO_FACTOR_TIMEOUT', timeElapsed, env.TWO_FACTOR_TIMEOUT); + await lucia.invalidateSession(session!.id!); + const sessionCookie = lucia.createBlankSessionCookie(); + cookies.set(sessionCookie.name, sessionCookie.value, { + path: '.', + ...sessionCookie.attributes, + }); + const message = { type: 'error', message: 'Two factor authentication has expired' } as const; + redirect(302, '/login', message, event); + } +} + async function checkRecoveryCode(recoveryCode: string, userId: string) { const userRecoveryCodes = await db.query.recoveryCodes.findMany({ where: and(eq(recoveryCodes.used, false), eq(recoveryCodes.userId, userId)),