Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
+76
-22
@@ -2,48 +2,102 @@ import { error, isHttpError, json, type Cookies } from '@sveltejs/kit';
|
||||
import { AuthError, SESSION_COOKIE, type AuthResult } from './index.js';
|
||||
import type { AuthUser } from '../../auth.js';
|
||||
|
||||
export function sessionCookie(url: URL): string { return url.protocol === 'https:' ? SESSION_COOKIE : 'ec_session_dev'; }
|
||||
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.');
|
||||
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') {
|
||||
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' } });
|
||||
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);
|
||||
if (!user) throw new AuthError('Sign in to continue.', 401);
|
||||
return user;
|
||||
}
|
||||
export async function authBody(request: Request, maxBytes = 8192): Promise<Record<string, unknown>> {
|
||||
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;
|
||||
export async function authBody(
|
||||
request: Request,
|
||||
maxBytes = 8192
|
||||
): Promise<Record<string, unknown>> {
|
||||
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;
|
||||
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); }
|
||||
if (length > maxBytes) {
|
||||
void reader.cancel().catch(() => {});
|
||||
throw new AuthError('Request is too large.', 413);
|
||||
}
|
||||
chunks.push(value);
|
||||
}
|
||||
} finally { reader.releaseLock(); }
|
||||
} 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.');
|
||||
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<string, unknown>;
|
||||
}
|
||||
export function authError(cause: unknown): Response {
|
||||
if (isHttpError(cause)) return json({ error: cause.body.message }, { status:cause.status, headers:{'Cache-Control':'no-store'} });
|
||||
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 : message.includes('busy') ? 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':'900'}:{})} });
|
||||
const status =
|
||||
cause instanceof AuthError
|
||||
? cause.status
|
||||
: message.includes('busy')
|
||||
? 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': '900' } : {})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user