({ request, response }: { request: IncomingMessage; response: ServerResponse })
| 13513 | return { |
| 13514 | priority: 100, // Higher priority — API routes run before static file serving |
| 13515 | async onRequest({ request, response }: { request: IncomingMessage; response: ServerResponse }) { |
| 13516 | const url = request.url?.split('?')[0]; |
| 13517 | if (!url) return; |
| 13518 | |
| 13519 | const headerString = (name: string): string | undefined => { |
| 13520 | const value = request.headers[name]; |
| 13521 | if (value === undefined) return undefined; |
| 13522 | return Array.isArray(value) ? value.join(', ') : value; |
| 13523 | }; |
| 13524 | recordEmbedProbe({ |
| 13525 | ts: Date.now(), |
| 13526 | url, |
| 13527 | method: request.method ?? '', |
| 13528 | ua: headerString('user-agent'), |
| 13529 | origin: headerString('origin'), |
| 13530 | referer: headerString('referer'), |
| 13531 | host: headerString('host'), |
| 13532 | remote: request.socket?.remoteAddress, |
| 13533 | secChUa: headerString('sec-ch-ua'), |
| 13534 | secChUaMobile: headerString('sec-ch-ua-mobile'), |
| 13535 | secChUaPlatform: headerString('sec-ch-ua-platform'), |
| 13536 | secFetchSite: headerString('sec-fetch-site'), |
| 13537 | secFetchDest: headerString('sec-fetch-dest'), |
| 13538 | secFetchMode: headerString('sec-fetch-mode'), |
| 13539 | secFetchUser: headerString('sec-fetch-user'), |
| 13540 | }); |
| 13541 | |
| 13542 | if (url.startsWith('/api/')) { |
| 13543 | const origin = request.headers.origin; |
| 13544 | if (origin !== undefined && !isAllowedApiOrigin(origin)) { |
| 13545 | errorResponse(response, 403, 'urn:ok:error:invalid-origin', 'Origin not allowed.', { |
| 13546 | handler: 'api-origin-gate', |
| 13547 | }); |
| 13548 | return; |
| 13549 | } |
| 13550 | if (typeof response.setHeader === 'function') { |
| 13551 | if (origin !== undefined) { |
| 13552 | response.setHeader('Access-Control-Allow-Origin', origin); |
| 13553 | response.setHeader('Vary', 'Origin'); |
| 13554 | } |
| 13555 | response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); |
| 13556 | response.setHeader( |
| 13557 | 'Access-Control-Allow-Headers', |
| 13558 | `Content-Type, Authorization, traceparent, tracestate, baggage, ${CLIENT_VERSION_HEADER.protocol}, ${CLIENT_VERSION_HEADER.runtime}, ${CLIENT_VERSION_HEADER.kind}`, |
| 13559 | ); |
| 13560 | } |
| 13561 | if (request.method === 'OPTIONS') { |
| 13562 | response.writeHead(204); |
| 13563 | response.end(); |
| 13564 | return; |
| 13565 | } |
| 13566 | } |
| 13567 | |
| 13568 | if (MUTATING_ROUTES.has(url) || STATE_MUTATING_PREFIXES.some((p) => url.startsWith(p))) { |
| 13569 | const peerAddress = request.socket?.remoteAddress; |
| 13570 | if (peerAddress !== undefined && !isLoopbackAddress(peerAddress)) { |
| 13571 | errorResponse(response, 403, 'urn:ok:error:loopback-required', 'Loopback required.', { |
| 13572 | handler: 'api-mutating-gate', |
nothing calls this directly
no test coverage detected