import type { IncomingMessage, ServerResponse } from 'node:http'; import { toNodeHandler } from 'better-auth/node'; import type { Auth } from '@mosaicstack/auth'; import type { NestFastifyApplication } from '@nestjs/platform-fastify'; import { AUTH } from './auth.tokens.js'; export function mountAuthHandler(app: NestFastifyApplication): void { const auth = app.get(AUTH); const nodeHandler = toNodeHandler(auth); const corsOrigin = process.env['GATEWAY_CORS_ORIGIN'] ?? 'http://localhost:3000'; const fastify = app.getHttpAdapter().getInstance(); // BetterAuth is mounted at the raw HTTP level via Fastify's onRequest hook, // bypassing NestJS middleware (including CORS). We must set CORS headers // manually on the raw response before handing off to BetterAuth. fastify.addHook( 'onRequest', ( req: { raw: IncomingMessage; url: string; method: string }, reply: { raw: ServerResponse; hijack: () => void }, done: () => void, ) => { if (!req.url.startsWith('/api/auth/')) { done(); return; } const origin = req.raw.headers.origin; const allowed = corsOrigin.split(',').map((o) => o.trim()); if (origin && allowed.includes(origin)) { reply.raw.setHeader('Access-Control-Allow-Origin', origin); reply.raw.setHeader('Access-Control-Allow-Credentials', 'true'); reply.raw.setHeader( 'Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS', ); reply.raw.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cookie'); } // Handle preflight if (req.method === 'OPTIONS') { reply.hijack(); reply.raw.writeHead(204); reply.raw.end(); return; } reply.hijack(); nodeHandler(req.raw as IncomingMessage, reply.raw as ServerResponse) .then(() => { if (!reply.raw.writableEnded) { reply.raw.end(); } }) .catch((err: unknown) => { if (!reply.raw.headersSent) { reply.raw.writeHead(500, { 'Content-Type': 'application/json' }); } if (!reply.raw.writableEnded) { reply.raw.end(JSON.stringify({ error: 'Internal auth error' })); } console.error('[AUTH] Handler error:', err); }); }, ); }