import { hash, verify } from '@node-rs/argon2'; import { encodeBase32LowerCase } from '@oslojs/encoding'; import { fail, redirect } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; import * as auth from '$lib/server/auth'; import { db } from '$lib/server/db'; import * as table from '$lib/server/db/schema'; import type { Actions, PageServerLoad } from './$types'; export const load: PageServerLoad = async (event) => { if (event.locals.user) { return redirect(302, '/demo/lucia'); } return {}; }; export const actions: Actions = { login: async (event) => { const formData = await event.request.formData(); const username = formData.get('username'); const password = formData.get('password'); if (!validateUsername(username)) { return fail(400, { message: 'Invalid username (min 3, max 31 characters, alphanumeric only)' }); } if (!validatePassword(password)) { return fail(400, { message: 'Invalid password (min 6, max 255 characters)' }); } const results = await db.select().from(table.user).where(eq(table.user.username, username)); const existingUser = results.at(0); if (!existingUser) { return fail(400, { message: 'Incorrect username or password' }); } const validPassword = await verify(existingUser.passwordHash, password, { memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1 }); if (!validPassword) { return fail(400, { message: 'Incorrect username or password' }); } const sessionToken = auth.generateSessionToken(); const session = await auth.createSession(sessionToken, existingUser.id); auth.setSessionTokenCookie(event, sessionToken, session.expiresAt); return redirect(302, '/demo/lucia'); }, register: async (event) => { const formData = await event.request.formData(); const username = formData.get('username'); const password = formData.get('password'); if (!validateUsername(username)) { return fail(400, { message: 'Invalid username' }); } if (!validatePassword(password)) { return fail(400, { message: 'Invalid password' }); } const userId = generateUserId(); const passwordHash = await hash(password, { // recommended minimum parameters memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1 }); try { await db.insert(table.user).values({ id: userId, username, passwordHash }); const sessionToken = auth.generateSessionToken(); const session = await auth.createSession(sessionToken, userId); auth.setSessionTokenCookie(event, sessionToken, session.expiresAt); } catch { return fail(500, { message: 'An error has occurred' }); } return redirect(302, '/demo/lucia'); } }; function generateUserId() { // ID with 120 bits of entropy, or about the same as UUID v4. const bytes = crypto.getRandomValues(new Uint8Array(15)); const id = encodeBase32LowerCase(bytes); return id; } function validateUsername(username: unknown): username is string { return ( typeof username === 'string' && username.length >= 3 && username.length <= 31 && /^[a-z0-9_-]+$/.test(username) ); } function validatePassword(password: unknown): password is string { return typeof password === 'string' && password.length >= 6 && password.length <= 255; }