import { error, isHttpError, json, type Cookies } from '@sveltejs/kit'; import { AuthError, SESSION_COOKIE, type AuthResult } from './index.js'; import { PasswordServiceBusyError } from './passwords.js'; import type { AuthUser } from '../../auth.js'; export function sessionCookie(url: URL): string { return url.protocol === 'https:' ? SESSION_COOKIE : 'ec_session_dev'; } export function requireUserTransport(url: URL): void { if (url.protocol === 'https:') return; if ( process.env.EC_AUTH_ALLOW_HTTP === 'true' && ['localhost', '127.0.0.1', '[::1]', '::1'].includes(url.hostname) ) return; error( 426, 'Operator login requires HTTPS. For local development only, set EC_AUTH_ALLOW_HTTP=true and use localhost.' ); } export function requireSameOrigin(request: Request, url: URL): void { if ( request.headers.get('origin') !== url.origin || request.headers.get('sec-fetch-site') === 'cross-site' ) { error(403, 'This action requires a same-origin request.'); } } export function sessionResponse(result: AuthResult, cookies: Cookies, url: URL) { cookies.set(sessionCookie(url), result.session.token, { path: '/', httpOnly: true, secure: url.protocol === 'https:', sameSite: 'lax', expires: new Date(result.session.expiresAt) }); return json({ user: result.user }, { headers: { 'Cache-Control': 'no-store' } }); } export function requireUser(user: AuthUser | null): AuthUser { if (!user) throw new AuthError('Sign in to continue.', 401); return user; } export async function authBody( request: Request, maxBytes = 8192 ): Promise> { if (!request.headers.get('content-type')?.toLowerCase().startsWith('application/json')) throw new AuthError('Send a JSON request.', 415); if (Number(request.headers.get('content-length')) > maxBytes) throw new AuthError('Request is too large.', 413); const reader = request.body?.getReader(); if (!reader) throw new AuthError('A JSON object is required.'); const chunks: Uint8Array[] = []; let length = 0; try { for (;;) { const { done, value } = await reader.read(); if (done) break; length += value.byteLength; if (length > maxBytes) { void reader.cancel().catch(() => {}); throw new AuthError('Request is too large.', 413); } chunks.push(value); } } finally { reader.releaseLock(); } let body: unknown; try { body = JSON.parse(Buffer.concat(chunks, length).toString('utf8')); } catch { throw new AuthError('Invalid JSON.'); } if (!body || typeof body !== 'object' || Array.isArray(body)) throw new AuthError('A JSON object is required.'); return body as Record; } export function authError(cause: unknown): Response { if (isHttpError(cause)) return json( { error: cause.body.message }, { status: cause.status, headers: { 'Cache-Control': 'no-store' } } ); const message = cause instanceof Error ? cause.message : 'Authentication request failed.'; const status = cause instanceof AuthError ? cause.status : cause instanceof PasswordServiceBusyError ? 429 : message.startsWith('Use a password') ? 400 : 500; return json( { error: status === 500 ? 'The authentication request could not be completed.' : message }, { status, headers: { 'Cache-Control': 'no-store', ...(status === 429 ? { 'Retry-After': cause instanceof PasswordServiceBusyError ? String(cause.retryAfterSeconds) : '900' } : {}) } } ); }