Adding show 2FA on login if it is enabled on the user.

This commit is contained in:
Bradley Shellnut 2024-04-07 22:11:52 -07:00
parent 826d06113d
commit 8b48466c7e
3 changed files with 47 additions and 16 deletions

View file

@ -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
});
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()
})

View file

@ -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);
}
},
};

View file

@ -47,6 +47,10 @@
<Input type="text" id="username" name="username" placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} required />
<Label for="password">Password</Label>
<Input type="password" id="password" name="password" placeholder="Password" autocomplete="password" data-invalid={$errors.password} bind:value={$form.password} required />
{#if $errors.totpToken}
<Label for="totpToken">2FA Code</Label>
<Input type="text" id="totpToken" name="totpToken" placeholder="2FA Code" autocomplete="one-time-code" data-invalid={$errors.totpToken} bind:value={$form.totpToken} />
{/if}
<Button type="submit">Login</Button>
{#if $errors._errors}
<Alert.Root variant="destructive">