diff --git a/apps/gateway/src/auth/auth.controller.ts b/apps/gateway/src/auth/auth.controller.ts index 934e3aa..ef458fa 100644 --- a/apps/gateway/src/auth/auth.controller.ts +++ b/apps/gateway/src/auth/auth.controller.ts @@ -1,20 +1,46 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; -import { All, Controller, Inject, Req, Res } from '@nestjs/common'; -import type { FastifyReply, FastifyRequest } from 'fastify'; import { toNodeHandler } from 'better-auth/node'; import type { Auth } from '@mosaic/auth'; -import { AUTH } from './auth.module.js'; +import type { NestFastifyApplication } from '@nestjs/platform-fastify'; +import { AUTH } from './auth.tokens.js'; -@Controller('api/auth') -export class AuthController { - private readonly handler: (req: IncomingMessage, res: ServerResponse) => Promise; +export function mountAuthHandler(app: NestFastifyApplication): void { + const auth = app.get(AUTH); + const nodeHandler = toNodeHandler(auth); - constructor(@Inject(AUTH) auth: Auth) { - this.handler = toNodeHandler(auth); - } + const fastify = app.getHttpAdapter().getInstance(); - @All('*path') - async handleAuth(@Req() req: FastifyRequest, @Res() res: FastifyReply): Promise { - await this.handler(req.raw, res.raw); - } + // 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. + fastify.addHook( + 'onRequest', + ( + req: { raw: IncomingMessage; url: string }, + reply: { raw: ServerResponse; hijack: () => void }, + done: () => void, + ) => { + if (!req.url.startsWith('/api/auth/')) { + done(); + 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); + }); + }, + ); } diff --git a/apps/gateway/src/auth/auth.guard.ts b/apps/gateway/src/auth/auth.guard.ts index 633f9d5..0920a86 100644 --- a/apps/gateway/src/auth/auth.guard.ts +++ b/apps/gateway/src/auth/auth.guard.ts @@ -8,7 +8,7 @@ import { import { fromNodeHeaders } from 'better-auth/node'; import type { Auth } from '@mosaic/auth'; import type { FastifyRequest } from 'fastify'; -import { AUTH } from './auth.module.js'; +import { AUTH } from './auth.tokens.js'; @Injectable() export class AuthGuard implements CanActivate { diff --git a/apps/gateway/src/auth/auth.module.ts b/apps/gateway/src/auth/auth.module.ts index c9a7f75..b2ed4de 100644 --- a/apps/gateway/src/auth/auth.module.ts +++ b/apps/gateway/src/auth/auth.module.ts @@ -2,9 +2,7 @@ import { Global, Module } from '@nestjs/common'; import { createAuth, type Auth } from '@mosaic/auth'; import type { Db } from '@mosaic/db'; import { DB } from '../database/database.module.js'; -import { AuthController } from './auth.controller.js'; - -export const AUTH = 'AUTH'; +import { AUTH } from './auth.tokens.js'; @Global() @Module({ @@ -20,7 +18,6 @@ export const AUTH = 'AUTH'; inject: [DB], }, ], - controllers: [AuthController], exports: [AUTH], }) export class AuthModule {} diff --git a/apps/gateway/src/auth/auth.tokens.ts b/apps/gateway/src/auth/auth.tokens.ts new file mode 100644 index 0000000..e8653b8 --- /dev/null +++ b/apps/gateway/src/auth/auth.tokens.ts @@ -0,0 +1 @@ +export const AUTH = 'AUTH'; diff --git a/apps/gateway/src/brain/brain.module.ts b/apps/gateway/src/brain/brain.module.ts index 41dba54..93a730f 100644 --- a/apps/gateway/src/brain/brain.module.ts +++ b/apps/gateway/src/brain/brain.module.ts @@ -2,8 +2,7 @@ import { Global, Module } from '@nestjs/common'; import { createBrain, type Brain } from '@mosaic/brain'; import type { Db } from '@mosaic/db'; import { DB } from '../database/database.module.js'; - -export const BRAIN = 'BRAIN'; +import { BRAIN } from './brain.tokens.js'; @Global() @Module({ diff --git a/apps/gateway/src/brain/brain.tokens.ts b/apps/gateway/src/brain/brain.tokens.ts new file mode 100644 index 0000000..537da06 --- /dev/null +++ b/apps/gateway/src/brain/brain.tokens.ts @@ -0,0 +1 @@ +export const BRAIN = 'BRAIN'; diff --git a/apps/gateway/src/conversations/conversations.controller.ts b/apps/gateway/src/conversations/conversations.controller.ts index 1e64eac..8bcc2a1 100644 --- a/apps/gateway/src/conversations/conversations.controller.ts +++ b/apps/gateway/src/conversations/conversations.controller.ts @@ -13,7 +13,7 @@ import { UseGuards, } from '@nestjs/common'; import type { Brain } from '@mosaic/brain'; -import { BRAIN } from '../brain/brain.module.js'; +import { BRAIN } from '../brain/brain.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; import { CurrentUser } from '../auth/current-user.decorator.js'; import type { diff --git a/apps/gateway/src/main.ts b/apps/gateway/src/main.ts index 14d3c79..5a00788 100644 --- a/apps/gateway/src/main.ts +++ b/apps/gateway/src/main.ts @@ -4,11 +4,14 @@ import { NestFactory } from '@nestjs/core'; import { Logger } from '@nestjs/common'; import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify'; import { AppModule } from './app.module.js'; +import { mountAuthHandler } from './auth/auth.controller.js'; async function bootstrap(): Promise { const logger = new Logger('Bootstrap'); const app = await NestFactory.create(AppModule, new FastifyAdapter()); + mountAuthHandler(app); + const port = process.env['GATEWAY_PORT'] ?? 4000; await app.listen(port as number, '0.0.0.0'); logger.log(`Gateway listening on port ${port}`); diff --git a/apps/gateway/src/missions/missions.controller.ts b/apps/gateway/src/missions/missions.controller.ts index e7e4c02..0ade282 100644 --- a/apps/gateway/src/missions/missions.controller.ts +++ b/apps/gateway/src/missions/missions.controller.ts @@ -13,7 +13,7 @@ import { UseGuards, } from '@nestjs/common'; import type { Brain } from '@mosaic/brain'; -import { BRAIN } from '../brain/brain.module.js'; +import { BRAIN } from '../brain/brain.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; import type { CreateMissionDto, UpdateMissionDto } from './missions.dto.js'; diff --git a/apps/gateway/src/projects/projects.controller.ts b/apps/gateway/src/projects/projects.controller.ts index 1d6761f..7202ee2 100644 --- a/apps/gateway/src/projects/projects.controller.ts +++ b/apps/gateway/src/projects/projects.controller.ts @@ -13,7 +13,7 @@ import { UseGuards, } from '@nestjs/common'; import type { Brain } from '@mosaic/brain'; -import { BRAIN } from '../brain/brain.module.js'; +import { BRAIN } from '../brain/brain.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; import { CurrentUser } from '../auth/current-user.decorator.js'; import type { CreateProjectDto, UpdateProjectDto } from './projects.dto.js'; diff --git a/apps/gateway/src/tasks/tasks.controller.ts b/apps/gateway/src/tasks/tasks.controller.ts index 7437c73..ef7d8af 100644 --- a/apps/gateway/src/tasks/tasks.controller.ts +++ b/apps/gateway/src/tasks/tasks.controller.ts @@ -14,7 +14,7 @@ import { UseGuards, } from '@nestjs/common'; import type { Brain } from '@mosaic/brain'; -import { BRAIN } from '../brain/brain.module.js'; +import { BRAIN } from '../brain/brain.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; import type { CreateTaskDto, UpdateTaskDto } from './tasks.dto.js';