mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Merge pull request #18 from BradNut/development
Add two factor auth timeout
This commit is contained in:
commit
ce0e44bf85
6 changed files with 77 additions and 24 deletions
|
|
@ -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',
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { redirect } from 'sveltekit-flash-message/server';
|
|||
import type { PageServerLoad } from './$types';
|
||||
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
|
||||
import db from '../../../../../../db';
|
||||
import { roles, user_roles, users } from '$db/schema';
|
||||
import { roles, userRoles, users } from '$db/schema';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const { params } = event;
|
||||
|
|
@ -17,7 +17,7 @@ export const load: PageServerLoad = async (event) => {
|
|||
const foundUser = await db.query.users.findFirst({
|
||||
where: eq(users.cuid, id),
|
||||
with: {
|
||||
user_roles: {
|
||||
userRoles: {
|
||||
with: {
|
||||
role: {
|
||||
columns: {
|
||||
|
|
@ -30,7 +30,7 @@ export const load: PageServerLoad = async (event) => {
|
|||
},
|
||||
});
|
||||
|
||||
const containsAdminRole = foundUser?.user_roles?.some(
|
||||
const containsAdminRole = foundUser?.userRoles?.some(
|
||||
(user_role) => user_role?.role?.name === 'admin',
|
||||
);
|
||||
if (!containsAdminRole) {
|
||||
|
|
@ -38,7 +38,7 @@ export const load: PageServerLoad = async (event) => {
|
|||
redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || [];
|
||||
const currentRoleIds = foundUser?.userRoles?.map((user_role) => user_role?.role.cuid) || [];
|
||||
let availableRoles: { name: string; cuid: string }[] = [];
|
||||
if (currentRoleIds?.length > 0) {
|
||||
availableRoles = await db.query.roles.findMany({
|
||||
|
|
@ -65,8 +65,8 @@ export const actions = {
|
|||
redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const userRoles = await db.query.user_roles.findMany({
|
||||
where: eq(user_roles.user_id, user.id),
|
||||
const userRoles = await db.query.userRoles.findMany({
|
||||
where: eq(userRoles.user_id, user.id),
|
||||
with: {
|
||||
role: {
|
||||
columns: {
|
||||
|
|
@ -92,7 +92,7 @@ export const actions = {
|
|||
});
|
||||
console.log('dbRole', dbRole);
|
||||
if (dbRole) {
|
||||
await db.insert(user_roles).values({
|
||||
await db.insert(userRoles).values({
|
||||
user_id: user.id,
|
||||
role_id: dbRole.id,
|
||||
});
|
||||
|
|
@ -108,8 +108,8 @@ export const actions = {
|
|||
redirect(302, '/login', notSignedInMessage, event);
|
||||
}
|
||||
|
||||
const userRoles = await db.query.user_roles.findMany({
|
||||
where: eq(user_roles.user_id, user.id),
|
||||
const userRoles = await db.query.userRoles.findMany({
|
||||
where: eq(userRoles.user_id, user.id),
|
||||
with: {
|
||||
role: {
|
||||
columns: {
|
||||
|
|
@ -133,11 +133,11 @@ export const actions = {
|
|||
console.log('dbRole', dbRole);
|
||||
if (dbRole) {
|
||||
await db
|
||||
.delete(user_roles)
|
||||
.where(and(eq(user_roles.user_id, user.id), eq(user_roles.role_id, dbRole.id)));
|
||||
.delete(userRoles)
|
||||
.where(and(eq(userRoles.user_id, user.id), eq(userRoles.role_id, dbRole.id)));
|
||||
redirect({ type: 'success', message: `Successfully removed role ${dbRole.name}!` }, event);
|
||||
} else {
|
||||
redirect({ type: 'error', message: `Failed to remove role ${dbRole.name} !` }, event);
|
||||
redirect({ type: 'error', message: `Failed to remove role ${role?.toString()} !` }, event);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -13,10 +13,11 @@ import { totpSchema } from '$lib/validations/auth';
|
|||
import { users, twoFactor, recoveryCodes } from '$db/schema';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { notSignedInMessage } from '$lib/flashMessages';
|
||||
import { TWO_FACTOR_TIMEOUT } from '../../../env';
|
||||
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,8 +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
|
||||
if (Date.now() - twoFactorDetails?.initiatedTime > TWO_FACTOR_TIMEOUT) {
|
||||
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);
|
||||
}
|
||||
|
|
@ -70,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;
|
||||
|
||||
|
|
@ -115,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);
|
||||
|
||||
|
|
@ -166,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) {
|
||||
const userRecoveryCodes = await db.query.recoveryCodes.findMany({
|
||||
where: and(eq(recoveryCodes.used, false), eq(recoveryCodes.userId, userId)),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import db from '../db';
|
||||
import { roles, user_roles } from '$db/schema';
|
||||
import { roles, userRoles } from '$db/schema';
|
||||
|
||||
export async function add_user_to_role(user_id: string, role_name: string, primary = false) {
|
||||
// Find the role by its name
|
||||
|
|
@ -13,7 +13,7 @@ export async function add_user_to_role(user_id: string, role_name: string, prima
|
|||
}
|
||||
|
||||
// Create a UserRole entry linking the user and the role
|
||||
return db.insert(user_roles).values({
|
||||
return db.insert(userRoles).values({
|
||||
user_id,
|
||||
role_id: role.id,
|
||||
primary,
|
||||
|
|
|
|||
Loading…
Reference in a new issue