feat(mvp): phase 3 - authentication system
Implement local auth flow: login, registration, logout, JWT access/refresh tokens in HTTP-only cookies, hooks.server.ts middleware, guest mode support, Superforms + Zod validation, and reusable auth/authorize middleware.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import type { Actions, PageServerLoad } from './$types.js';
|
||||
import { superValidate, setError } from 'sveltekit-superforms';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { loginSchema } from '$lib/utils/validators.js';
|
||||
import * as userService from '$lib/server/services/userService.js';
|
||||
import * as authService from '$lib/server/services/authService.js';
|
||||
|
||||
const COOKIE_BASE = {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax' as const,
|
||||
path: '/'
|
||||
};
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
// If already logged in, redirect to home
|
||||
if (locals.user) {
|
||||
throw redirect(302, '/');
|
||||
}
|
||||
|
||||
const form = await superValidate(zod(loginSchema));
|
||||
return { form };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async ({ request, cookies }) => {
|
||||
const form = await superValidate(request, zod(loginSchema));
|
||||
|
||||
if (!form.valid) {
|
||||
return fail(400, { form });
|
||||
}
|
||||
|
||||
const { email, password } = form.data;
|
||||
|
||||
// Find user by email
|
||||
const user = await userService.findByEmail(email);
|
||||
if (!user) {
|
||||
return setError(form, 'email', 'Invalid email or password');
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if (!user.password) {
|
||||
return setError(form, 'email', 'This account does not use password authentication');
|
||||
}
|
||||
|
||||
const passwordValid = await authService.verifyPassword(password, user.password);
|
||||
if (!passwordValid) {
|
||||
return setError(form, 'email', 'Invalid email or password');
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = authService.signAccessToken({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
});
|
||||
const refreshToken = authService.generateRefreshToken();
|
||||
await authService.saveRefreshToken(user.id, refreshToken);
|
||||
|
||||
// Set cookies
|
||||
cookies.set('access_token', accessToken, {
|
||||
...COOKIE_BASE,
|
||||
maxAge: 900 // 15 minutes
|
||||
});
|
||||
cookies.set('refresh_token', refreshToken, {
|
||||
...COOKIE_BASE,
|
||||
maxAge: 604800 // 7 days
|
||||
});
|
||||
cookies.set('refresh_user_id', user.id, {
|
||||
...COOKIE_BASE,
|
||||
maxAge: 604800 // 7 days
|
||||
});
|
||||
|
||||
throw redirect(302, '/');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user