Files
internal-mail-manager/apps/api/src/lib/http.ts
T
2026-03-30 16:36:59 +00:00

119 lines
2.9 KiB
TypeScript

import { ZodError, type ZodSchema } from "zod";
import { config } from "./config";
import { HttpError } from "./errors";
const allowedOrigin = (request: Request): string | null => {
const origin = request.headers.get("origin");
if (!origin) {
return null;
}
return config.appOrigins.includes(origin.toLowerCase()) ? origin : null;
};
const securityHeaders = (): Record<string, string> => ({
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Referrer-Policy": "no-referrer"
});
const mergeHeaders = (request: Request, headers?: HeadersInit): Headers => {
const result = new Headers(headers);
const origin = allowedOrigin(request);
for (const [key, value] of Object.entries(securityHeaders())) {
result.set(key, value);
}
result.set("Vary", "Origin");
if (origin) {
result.set("Access-Control-Allow-Origin", origin);
result.set("Access-Control-Allow-Credentials", "true");
result.set("Access-Control-Allow-Headers", "Content-Type");
result.set("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,OPTIONS");
}
return result;
};
export const assertAllowedOrigin = (request: Request): void => {
const origin = request.headers.get("origin");
if (!origin) {
return;
}
console.log(config.appOrigins, origin)
if (!config.appOrigins.includes(origin.toLowerCase())) {
throw new HttpError(403, "Origin is not allowed.");
}
};
export const jsonResponse = (
request: Request,
status: number,
payload: unknown,
headers?: HeadersInit
): Response =>
new Response(JSON.stringify(payload), {
status,
headers: mergeHeaders(request, {
"content-type": "application/json; charset=utf-8",
...headers
})
});
export const emptyResponse = (request: Request, status = 204, headers?: HeadersInit): Response =>
new Response(null, {
status,
headers: mergeHeaders(request, headers)
});
export const optionsResponse = (request: Request): Response =>
emptyResponse(request, 204);
export const parseJson = async <T>(request: Request, schema: ZodSchema<T>): Promise<T> => {
let body: unknown;
try {
body = await request.json();
} catch {
throw new HttpError(400, "Request body must be valid JSON.");
}
try {
return schema.parse(body);
} catch (error) {
if (error instanceof ZodError) {
throw new HttpError(400, error.issues[0]?.message ?? "Invalid request body.");
}
throw error;
}
};
export const errorResponse = (request: Request, error: unknown): Response => {
if (error instanceof HttpError) {
return jsonResponse(request, error.status, {
error: {
message: error.message
}
}, error.headers);
}
if (error instanceof ZodError) {
return jsonResponse(request, 400, {
error: {
message: error.issues[0]?.message ?? "Invalid request."
}
});
}
console.error(error);
return jsonResponse(request, 500, {
error: {
message: "Unexpected server error."
}
});
};