Login uses loginLimiter, authenticates via authenticateUser, creates session cookie, and redirects foster role to /foster, others to /admin. Logout destroys session and redirects to /. Register uses registrationLimiter, validates input, creates inactive user, and sends registration notification. Change-password verifies current password, enforces 8-char minimum, updates hash, clears mustChangePassword flag. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
import type { Actions, PageServerLoad } from './$types';
|
|
import { fail, redirect } from '@sveltejs/kit';
|
|
import { db } from '$lib/server/db';
|
|
import { users } from '$lib/server/schema';
|
|
import { eq } from 'drizzle-orm';
|
|
import { hashPassword } from '$lib/server/auth-utils';
|
|
import { sendRegistrationNotification } from '$lib/server/email';
|
|
import { registrationLimiter } from '$lib/server/rate-limit';
|
|
|
|
export const load: PageServerLoad = async ({ locals }) => {
|
|
if (locals.user) {
|
|
throw redirect(303, locals.user.role === 'foster' ? '/foster' : '/admin');
|
|
}
|
|
};
|
|
|
|
export const actions: Actions = {
|
|
default: async ({ request, getClientAddress, locals }) => {
|
|
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? getClientAddress();
|
|
|
|
if (!registrationLimiter.check(ip)) {
|
|
return fail(429, { error: 'Too many registration attempts. Please try again later.' });
|
|
}
|
|
|
|
const data = await request.formData();
|
|
const name = data.get('name')?.toString().trim();
|
|
const email = data.get('email')?.toString().trim().toLowerCase();
|
|
const password = data.get('password')?.toString();
|
|
const confirmPassword = data.get('confirmPassword')?.toString();
|
|
const reason = data.get('reason')?.toString().trim();
|
|
|
|
if (!name || !email || !password || !confirmPassword || !reason) {
|
|
return fail(400, { error: 'All fields are required', name, email, reason });
|
|
}
|
|
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
return fail(400, { error: 'Please enter a valid email address', name, email, reason });
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
return fail(400, { error: 'Password must be at least 8 characters', name, email, reason });
|
|
}
|
|
|
|
if (password !== confirmPassword) {
|
|
return fail(400, { error: 'Passwords do not match', name, email, reason });
|
|
}
|
|
|
|
const [existing] = await db.select({ id: users.id }).from(users).where(eq(users.email, email)).limit(1);
|
|
if (existing) {
|
|
return fail(400, { error: 'An account with this email already exists', name, email, reason });
|
|
}
|
|
|
|
const passwordHash = await hashPassword(password);
|
|
|
|
await db.insert(users).values({
|
|
name,
|
|
email,
|
|
passwordHash,
|
|
role: 'viewer',
|
|
active: false,
|
|
mustChangePassword: false
|
|
});
|
|
|
|
const orgConfig = locals.orgConfig;
|
|
if (orgConfig.orgInfo.email) {
|
|
try {
|
|
await sendRegistrationNotification(
|
|
orgConfig.orgInfo.name,
|
|
orgConfig.orgInfo.email,
|
|
name,
|
|
email,
|
|
reason
|
|
);
|
|
} catch {
|
|
// Email failure shouldn't block registration
|
|
}
|
|
}
|
|
|
|
return { success: true };
|
|
}
|
|
};
|