Checking TOTP expiry time.

This commit is contained in:
Bradley Shellnut 2024-07-12 17:44:45 -07:00
parent a90a9d4fd6
commit 2098a5cdfd
4 changed files with 62 additions and 10 deletions

View file

@ -6,3 +6,7 @@ export const forbiddenMessage = {
type: 'error', type: 'error',
message: 'You are not allowed to access this', message: 'You are not allowed to access this',
} as const; } as const;
export const signedOutMessage = {
type: 'success',
message: 'Successfully signed out',
}

View file

@ -94,7 +94,7 @@ export const actions: Actions = {
if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
await db.update(twoFactor).set({ await db.update(twoFactor).set({
initiated_time: new Date(), initiatedTime: new Date(),
}); });
session = await lucia.createSession(user.id, { session = await lucia.createSession(user.id, {

View file

@ -1,7 +1,7 @@
import { fail } from '@sveltejs/kit'; import { fail } from '@sveltejs/kit';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { lucia } from '$lib/server/auth'; import { lucia } from '$lib/server/auth';
import { notSignedInMessage } from '$lib/flashMessages'; import { signedOutMessage } from '$lib/flashMessages';
import type { Actions } from "./$types"; import type { Actions } from "./$types";
export const actions: Actions = { export const actions: Actions = {
@ -17,6 +17,6 @@ export const actions: Actions = {
path: '.', path: '.',
...sessionCookie.attributes ...sessionCookie.attributes
}); });
return redirect(302, '/login', notSignedInMessage, event); return redirect(302, '/login', signedOutMessage, event);
} }
}; };

View file

@ -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 { and, eq } from 'drizzle-orm';
import { Argon2id } from 'oslo/password'; import { Argon2id } from 'oslo/password';
import { decodeHex } from 'oslo/encoding'; import { decodeHex } from 'oslo/encoding';
@ -16,7 +16,8 @@ import { notSignedInMessage } from '$lib/flashMessages';
import env from '../../../env'; import env from '../../../env';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const { user, session } = event.locals; const { cookies, locals } = event;
const { user, session } = locals;
if (!user || !session) { if (!user || !session) {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
@ -31,9 +32,33 @@ export const load: PageServerLoad = async (event) => {
where: eq(twoFactor.userId, dbUser!.id!), 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 // Check if two factor started less than TWO_FACTOR_TIMEOUT
const const timeElapsed = Date.now() - twoFactorInitiatedTime.getTime();
if ((Date.now() - twoFactorDetails?.initiatedTime) > env.TWO_FACTOR_TIMEOUT) {s 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; const message = { type: 'error', message: 'Two factor authentication has expired' } as const;
redirect(302, '/login', message, event); redirect(302, '/login', message, event);
} }
@ -71,7 +96,7 @@ export const actions: Actions = {
throw error(429); throw error(429);
} }
const { locals } = event; const { cookies, locals } = event;
const session = locals.session; const session = locals.session;
const user = locals.user; const user = locals.user;
@ -116,15 +141,18 @@ export const actions: Actions = {
const twoFactorSecretPopulated = const twoFactorSecretPopulated =
twoFactorDetails?.secret !== '' && twoFactorDetails?.secret !== null; twoFactorDetails?.secret !== '' && twoFactorDetails?.secret !== null;
if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !totpToken) { if (twoFactorDetails?.enabled && !twoFactorSecretPopulated && !totpToken) {
return fail(400, { return fail(400, {
form, form,
}); });
} else if (twoFactorSecretPopulated && totpToken) { } else if (twoFactorSecretPopulated && totpToken) {
// Check if two factor started less than TWO_FACTOR_TIMEOUT
await checkTOTPExpiry(twoFactorDetails, session, cookies, event);
console.log('totpToken', totpToken); console.log('totpToken', totpToken);
const validOTP = await new TOTPController().verify( const validOTP = await new TOTPController().verify(
totpToken, totpToken,
decodeHex(twoFactorDetails.secret ?? ''), decodeHex(twoFactorDetails?.secret ?? ''),
); );
console.log('validOTP', validOTP); 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<Partial<Record<string, string>>, 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) { async function checkRecoveryCode(recoveryCode: string, userId: string) {
const userRecoveryCodes = await db.query.recoveryCodes.findMany({ const userRecoveryCodes = await db.query.recoveryCodes.findMany({
where: and(eq(recoveryCodes.used, false), eq(recoveryCodes.userId, userId)), where: and(eq(recoveryCodes.used, false), eq(recoveryCodes.userId, userId)),