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}