import { Either, FourOhFourActivityImpl, HealthCheckActivityImpl, type HealthChecker, HealthCheckOutput, type IFourOhFourActivity, type IHealthCheckActivity, type ITraceable, PenguenoRequest, PenguenoResponse, Server, type ServerTrace, } from '@emprespresso/pengueno'; import { Storage } from '../storage/index.js'; import { TokenGenerateActivityImpl, WebhookActivityImpl, type ITokenGenerateActivity, type IWebhookActivity, } from '../activity/index.js'; import { TokenSigner } from '../token/index.js'; const defaultHealthCheck: HealthChecker = async (input) => { void input.get(); return Either.right(HealthCheckOutput.YAASSSLAYQUEEN); }; type CorsOriginRule = | { kind: 'origin'; origin: string } | { kind: 'host'; host: string; port?: string } | { kind: 'wildcard-host'; suffix: string }; type AllowedCorsOrigins = '*' | CorsOriginRule[]; const parseAllowedCorsOrigins = (raw: string): AllowedCorsOrigins => { const trimmed = raw.trim(); if (trimmed === '' || trimmed === '*') { return '*'; } const rules = trimmed .split(',') .map((entry) => entry.trim()) .filter((entry) => entry.length > 0) .flatMap((entry) => { // Full origin (scheme + host [+ port]) if (entry.includes('://')) { try { const parsed = new URL(entry); return [{ kind: 'origin', origin: parsed.origin }]; } catch { return []; } } // Wildcard hostname (matches any subdomain, not apex) if (entry.startsWith('*.')) { const suffix = entry.slice(2).toLowerCase(); return suffix.length > 0 ? [{ kind: 'wildcard-host', suffix }] : []; } // Hostname (optionally with :port) const hostPortMatch = entry.match(/^(.+?)(?::(\d+))?$/); if (!hostPortMatch) { return []; } const host = hostPortMatch[1]!.toLowerCase(); const port = hostPortMatch[2]; if (host.length === 0) { return []; } return [{ kind: 'host', host, port }]; }); return rules.length === 0 ? '*' : rules; }; export class PosthookServer implements Server { constructor( storage: Storage, signer: TokenSigner, corsOriginsRaw: string = '*', healthCheck: HealthChecker = defaultHealthCheck, private readonly healthCheckActivity: IHealthCheckActivity = new HealthCheckActivityImpl(healthCheck), private readonly webhookActivity: IWebhookActivity = new WebhookActivityImpl(storage, signer), private readonly tokenGenerateActivity: ITokenGenerateActivity = new TokenGenerateActivityImpl(storage, signer), private readonly fourOhFourActivity: IFourOhFourActivity = new FourOhFourActivityImpl(), private readonly corsOrigins: AllowedCorsOrigins = parseAllowedCorsOrigins(corsOriginsRaw), ) {} private corsHeaders(origin: string | undefined): Record { if (origin === undefined) { return {}; } // Special case: allow everything (including http origins). if (this.corsOrigins === '*') { return { 'Access-Control-Allow-Origin': '*', }; } let parsed: URL; try { parsed = new URL(origin); } catch { return {}; } // If we're restricting origins, only allow https. if (parsed.protocol !== 'https:') { return {}; } const candidateOrigin = parsed.origin; const hostname = parsed.hostname.toLowerCase(); const port = parsed.port; const allowed = this.corsOrigins.some((rule) => { if (rule.kind === 'origin') { return candidateOrigin === rule.origin; } if (rule.kind === 'host') { if (hostname !== rule.host) { return false; } return rule.port === undefined ? true : rule.port === port; } // wildcard-host if (hostname === rule.suffix) { return false; } return hostname.endsWith(`.${rule.suffix}`); }); if (!allowed) { return {}; } return { 'Access-Control-Allow-Origin': candidateOrigin, }; } private corsPreflightHeaders(requestedHeaders: string | undefined): Record { const allowHeaders = requestedHeaders?.trim() ?? 'Content-Type, X-CSRF-Token, H-Captcha-Response, Authorization, Accept, Origin'; return { 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': allowHeaders, 'Access-Control-Max-Age': '86400', }; } private applyCorsHeaders(res: PenguenoResponse, headers: Record) { for (const [key, value] of Object.entries(headers)) { res.headers[key] = value; } } public async serve(req: ITraceable): Promise { const penguenoReq = req.get().req; const method = penguenoReq.method; const requestHeaders = penguenoReq.header(); const origin = requestHeaders['origin']; const corsHeaders = this.corsHeaders(origin); // Handle CORS preflight. if (method === 'OPTIONS' && origin !== undefined && Object.keys(corsHeaders).length > 0) { return new PenguenoResponse(req, '', { status: 204, statusText: 'No Content', headers: { ...corsHeaders, ...this.corsPreflightHeaders(requestHeaders['access-control-request-headers']), }, }); } const url = new URL(penguenoReq.url); const { pathname } = url; let result: Promise; // === Public Routes (/) === if (pathname === '/health') { result = this.healthCheckActivity.checkHealth(req); } else { // Token generation endpoint - /hook/{routeName}/token const tokenMatch = pathname.match(/^\/hook\/([^/]+)\/token$/); if (tokenMatch && method === 'GET') { const routeName = tokenMatch[1]; result = this.tokenGenerateActivity.generateToken(routeName)(req); } else { // Dynamic webhook endpoints - /hook/{routeName} const hookMatch = pathname.match(/^\/hook\/([^/]+)$/); if (hookMatch && method === 'POST') { const routeName = hookMatch[1]; result = this.webhookActivity.processWebhook(routeName)(req); } else { // 404 for everything else result = this.fourOhFourActivity.fourOhFour(req); } } } const response = await result; if (Object.keys(corsHeaders).length > 0) { this.applyCorsHeaders(response, corsHeaders); } return response; } }