Files
fosterflow/src/routes/register/+page.server.ts
T
justinandClaude Sonnet 4.6 c83f24084c feat: add auth pages (login, logout, register, change-password)
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>
2026-03-26 12:14:56 -05:00

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 };
}
};