diff --git a/src/lib/validations/auth.ts b/src/lib/validations/auth.ts index 8b89d9b..c656e75 100644 --- a/src/lib/validations/auth.ts +++ b/src/lib/validations/auth.ts @@ -1,5 +1,6 @@ import { refinePasswords } from "./account"; import { userSchema } from "./zod-schemas"; +import {z} from "zod"; export const signUpSchema = userSchema .pick({ @@ -15,7 +16,14 @@ export const signUpSchema = userSchema refinePasswords(confirm_password, password, ctx); }); -export const signInSchema = userSchema.pick({ - username: true, - password: true -}); \ No newline at end of file +export const signInSchema = z.object({ + username: z + .string() + .trim() + .min(3, { message: 'Username must be at least 3 characters' }) + .max(50, { message: 'Username must be less than 50 characters' }), + password: z + .string({ required_error: 'Password is required' }) + .trim(), + totpToken: z.string().trim().min(6).max(6).optional() +}) \ 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 3e2955e..1074e15 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -1,15 +1,17 @@ import { fail, error, type Actions } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; +import { Argon2id } from 'oslo/password'; +import { decodeHex } from 'oslo/encoding'; +import { TOTPController } from 'oslo/otp'; import { zod } from 'sveltekit-superforms/adapters'; import { setError, superValidate } from 'sveltekit-superforms/server'; import { redirect } from 'sveltekit-flash-message/server'; -import { Argon2id } from 'oslo/password'; +import { RateLimiter } from 'sveltekit-rate-limiter/server'; import db from '$lib/drizzle'; import { lucia } from '$lib/server/auth'; import { signInSchema } from '$lib/validations/auth'; import { collections, users, wishlists } from '../../../schema'; import type { PageServerLoad } from './$types'; -import { RateLimiter } from 'sveltekit-rate-limiter/server'; export const load: PageServerLoad = async (event) => { if (event.locals.user) { @@ -20,13 +22,13 @@ export const load: PageServerLoad = async (event) => { const form = await superValidate(event, zod(signInSchema)); return { - form + form, }; }; const limiter = new RateLimiter({ // A rate is defined by [number, unit] - IPUA: [5, 'm'] + IPUA: [5, 'm'], }); export const actions: Actions = { @@ -41,7 +43,7 @@ export const actions: Actions = { if (!form.valid) { form.data.password = ''; return fail(400, { - form + form, }); } @@ -51,12 +53,12 @@ export const actions: Actions = { const password = form.data.password; const user = await db.query.users.findFirst({ - where: eq(users.username, form.data.username) + where: eq(users.username, form.data.username), }); console.log('user', JSON.stringify(user, null, 2)); - if (!user || !user.hashed_password) { + if (!user?.hashed_password) { form.data.password = ''; return setError(form, '', 'Your username or password is incorrect.'); } @@ -71,21 +73,38 @@ export const actions: Actions = { await db .insert(collections) .values({ - user_id: user.id + user_id: user.id, }) .onConflictDoNothing(); await db .insert(wishlists) .values({ - user_id: user.id + user_id: user.id, }) .onConflictDoNothing(); + if (user?.two_factor_enabled && user?.two_factor_secret && !form?.data?.totpToken) { + return setError( + form, + 'totpToken', + 'Two factor authentication is enabled. Please enter your 2FA code.', + ); + } else if (user?.two_factor_enabled && user?.two_factor_secret && form?.data?.totpToken) { + console.log('totpToken', form.data.totpToken); + const validOTP = await new TOTPController().verify( + form.data.totpToken, + decodeHex(user.two_factor_secret) + ); + console.log('validOTP', validOTP); + if (!validOTP) { + return setError(form, 'totpToken', 'Invalid 2FA code'); + } + } console.log('ip', locals.ip); console.log('country', locals.country); session = await lucia.createSession(user.id, { ip_country: locals.country, - ip_address: locals.ip + ip_address: locals.ip, }); console.log('logging in session', session); sessionCookie = lucia.createSessionCookie(session.id); @@ -100,12 +119,12 @@ export const actions: Actions = { console.log('setting session cookie', sessionCookie); event.cookies.set(sessionCookie.name, sessionCookie.value, { path: '.', - ...sessionCookie.attributes + ...sessionCookie.attributes, }); form.data.username = ''; form.data.password = ''; const message = { type: 'success', message: 'Signed In!' } as const; redirect(302, '/', message, event); - } + }, }; diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 8a55220..ce30c45 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -47,6 +47,10 @@ + {#if $errors.totpToken} + + + {/if} {#if $errors._errors}