fix(auth): add CORS headers to BetterAuth raw HTTP handler
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

The auth handler uses Fastify's onRequest hook with reply.hijack(),
which bypasses all NestJS middleware including CORS. Manually set
Access-Control-Allow-* headers on the raw response and handle OPTIONS
preflight before delegating to BetterAuth's node handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 11:47:02 -05:00
parent 014ebdacda
commit 394f4f9fab

View File

@@ -7,16 +7,17 @@ import { AUTH } from './auth.tokens.js';
export function mountAuthHandler(app: NestFastifyApplication): void {
const auth = app.get<Auth>(AUTH);
const nodeHandler = toNodeHandler(auth);
const corsOrigin = process.env['GATEWAY_CORS_ORIGIN'] ?? 'http://localhost:3000';
const fastify = app.getHttpAdapter().getInstance();
// Use Fastify's addHook to intercept auth requests at the raw HTTP level,
// before Fastify's body parser runs. This avoids conflicts with NestJS's
// custom content-type parser.
// 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 },
req: { raw: IncomingMessage; url: string; method: string },
reply: { raw: ServerResponse; hijack: () => void },
done: () => void,
) => {
@@ -25,6 +26,27 @@ export function mountAuthHandler(app: NestFastifyApplication): void {
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(() => {