import { json, error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { createUser, mergeSessionProgress, createUserSession } from '$lib/server/pocketbase'; import { createAuthToken } from '../../../../hooks.server'; import { log } from '$lib/server/logger'; import { randomBytes } from 'node:crypto'; const AUTH_COOKIE = 'libnovel_auth'; const ONE_YEAR = 60 * 60 * 24 * 365; /** * POST /api/auth/register * Body: { username: string, password: string } * Returns: { token: string, user: { id, username, role } } * * Sets the libnovel_auth cookie and returns the raw token value so the * iOS app can persist it for subsequent requests. */ export const POST: RequestHandler = async ({ request, cookies, locals }) => { let body: { username?: string; password?: string }; try { body = await request.json(); } catch { error(400, 'Invalid JSON body'); } const username = (body.username ?? '').trim(); const password = body.password ?? ''; if (!username || !password) { error(400, 'Username and password are required'); } if (username.length < 3 || username.length > 32) { error(400, 'Username must be between 3 and 32 characters'); } if (!/^[a-zA-Z0-9_-]+$/.test(username)) { error(400, 'Username may only contain letters, numbers, underscores and hyphens'); } if (password.length < 8) { error(400, 'Password must be at least 8 characters'); } let user; try { user = await createUser(username, password); } catch (e: unknown) { const msg = e instanceof Error ? e.message : 'Registration failed.'; if (msg.includes('Username already taken')) { error(409, 'That username is already taken'); } log.error('api/auth/register', 'unexpected error', { username, err: String(e) }); error(500, 'An error occurred. Please try again.'); } // Merge anonymous session progress (non-fatal) mergeSessionProgress(locals.sessionId, user.id).catch((e) => log.warn('api/auth/register', 'mergeSessionProgress failed (non-fatal)', { err: String(e) }) ); const authSessionId = randomBytes(16).toString('hex'); const userAgent = request.headers.get('user-agent') ?? ''; const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? request.headers.get('x-real-ip') ?? ''; createUserSession(user.id, authSessionId, userAgent, ip).catch((e) => log.warn('api/auth/register', 'createUserSession failed (non-fatal)', { err: String(e) }) ); const token = createAuthToken(user.id, user.username, user.role ?? 'user', authSessionId); cookies.set(AUTH_COOKIE, token, { path: '/', httpOnly: true, sameSite: 'lax', maxAge: ONE_YEAR }); return json({ token, user: { id: user.id, username: user.username, role: user.role ?? 'user' } }); };