Files
Dune/lib/admin-http.ts

48 lines
2.7 KiB
TypeScript

import { requestOrigin } from './request-origin';
import { AdminError } from '../db/admin-access';
const cookieName = 'dune_admin_session';
export function adminToken(request: Request): string | undefined {
return request.headers.get('cookie')?.split(';').map(x => x.trim()).find(x => x.startsWith(cookieName + '='))?.slice(cookieName.length + 1);
}
export function adminCookie(request: Request, token: string, configuredOrigin?: string): string {
const secure = new URL(requestOrigin(request, configuredOrigin)).protocol === 'https:';
return `${cookieName}=${token}; Path=/api/admin; HttpOnly; SameSite=Strict; Max-Age=${token ? 28800 : 0}${secure ? '; Secure' : ''}`;
}
export async function adminBody(request: Request, configuredOrigin?: string): Promise<Record<string, unknown>> {
// Require a browser's exact configured origin even for sign-in, preventing login CSRF.
if (request.headers.get('origin') !== requestOrigin(request, configuredOrigin) ||
request.headers.get('sec-fetch-site') === 'cross-site') throw new AdminError('Invalid request origin.', 403);
if (request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') throw new AdminError('Send a JSON request.', 415);
const reader = request.body?.getReader();
if (!reader) throw new AdminError('Enter the request details.', 400);
let size = 0;
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
// Drain the request without retaining bytes beyond the limit.
if (size <= 2048) chunks.push(value);
}
if (size > 2048) throw new AdminError('Request too large.', 413);
const bytes = new Uint8Array(size);
let offset = 0;
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
const data: unknown = JSON.parse(new TextDecoder().decode(bytes));
if (!data || typeof data !== 'object' || Array.isArray(data)) throw new AdminError('Invalid request.', 400);
return data as Record<string, unknown>;
}
export function adminResponse(value: unknown, extra: HeadersInit = {}) {
const headers = new Headers(extra);
headers.set('Cache-Control', 'no-store');
headers.set('Vary', 'Cookie');
headers.set('X-Content-Type-Options', 'nosniff');
return Response.json(value, { headers });
}
export function adminFailure(error: unknown) {
const status = error instanceof AdminError ? error.status : error instanceof SyntaxError ? 400 : 503;
return Response.json({ error: error instanceof AdminError ? error.message : error instanceof SyntaxError ? 'Invalid JSON.' : 'Administration is temporarily unavailable. Try again.' },
{ status, headers: { 'Cache-Control': 'no-store', 'Vary': 'Cookie', 'X-Content-Type-Options': 'nosniff' } });
}