65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { error } from '@sveltejs/kit';
|
|
|
|
export const MAX_JSON_BODY_BYTES = 64 * 1024;
|
|
|
|
export function jsonOk(data: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(data), {
|
|
status,
|
|
headers: { 'content-type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
export function jsonError(
|
|
status: number,
|
|
message: string,
|
|
headers?: Record<string, string>
|
|
): Response {
|
|
return new Response(JSON.stringify({ message }), {
|
|
status,
|
|
headers: { 'content-type': 'application/json', ...headers }
|
|
});
|
|
}
|
|
|
|
/** Read and parse a JSON request body with a size cap. Throws 413 for
|
|
* oversized bodies and 400 for invalid JSON / wrong content type. */
|
|
export async function readJsonBody(request: Request): Promise<unknown> {
|
|
const contentType = request.headers.get('content-type') ?? '';
|
|
if (!contentType.includes('application/json')) {
|
|
throw error(400, 'content-type must be application/json');
|
|
}
|
|
|
|
const contentLength = Number(request.headers.get('content-length') ?? 0);
|
|
if (contentLength > MAX_JSON_BODY_BYTES) {
|
|
throw error(413, 'request body too large');
|
|
}
|
|
|
|
const raw = await request.text();
|
|
if (raw.length === 0) {
|
|
throw error(400, 'request body is required');
|
|
}
|
|
if (raw.length > MAX_JSON_BODY_BYTES) {
|
|
throw error(413, 'request body too large');
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(raw);
|
|
} catch {
|
|
throw error(400, 'invalid JSON body');
|
|
}
|
|
}
|
|
|
|
/** Build a 429 response with Retry-After set in seconds. */
|
|
export function rateLimited(retryAfterMs: number): Response {
|
|
const retryAfterSeconds = Math.max(1, Math.ceil(retryAfterMs / 1000));
|
|
return jsonError(429, 'rate limit exceeded', { 'retry-after': String(retryAfterSeconds) });
|
|
}
|
|
|
|
export function clientIp(request: Request): string {
|
|
const fwd = request.headers.get('x-forwarded-for');
|
|
if (fwd) {
|
|
const first = fwd.split(',')[0]?.trim();
|
|
if (first) return first;
|
|
}
|
|
return request.headers.get('x-real-ip') ?? 'unknown';
|
|
}
|