diff --git a/server/src/nest/common/validate-route-guards.ts b/server/src/nest/common/validate-route-guards.ts index 5106a88d2..c8b01440b 100644 --- a/server/src/nest/common/validate-route-guards.ts +++ b/server/src/nest/common/validate-route-guards.ts @@ -2,6 +2,7 @@ import { CookieAuthGuard } from '../auth-core/cookie-auth.guard'; import { JwtAuthGuard } from '../auth-core/jwt-auth.guard'; import { OptionalJwtGuard } from '../auth-core/optional-jwt.guard'; import { IS_PUBLIC, OPTIONAL_AUTH } from '../auth-core/public.decorator'; +import { MANAGED_PUBLIC_ROUTES } from '../managed/managed-ext.module'; import { ApiTokenGuard } from '../public-api/api-token.guard'; import type { INestApplication } from '@nestjs/common'; import { METHOD_METADATA, PATH_METADATA, GUARDS_METADATA } from '@nestjs/common/constants'; @@ -181,7 +182,7 @@ export const ANONYMOUS_GUARDED_ROUTE_ALLOW_LIST: string[] = [ */ export function validateRouteGuards( app: INestApplication, - allowList: string[] = PUBLIC_ROUTE_ALLOW_LIST, + allowList: string[] = [...PUBLIC_ROUTE_ALLOW_LIST, ...MANAGED_PUBLIC_ROUTES], anonymousGuardedAllowList: string[] = ANONYMOUS_GUARDED_ROUTE_ALLOW_LIST, ): void { const entries = collectRouteGuards(app); diff --git a/server/src/nest/managed/managed-ext.module.ts b/server/src/nest/managed/managed-ext.module.ts index a781beb80..207dcc743 100644 --- a/server/src/nest/managed/managed-ext.module.ts +++ b/server/src/nest/managed/managed-ext.module.ts @@ -22,3 +22,13 @@ import { Module } from '@nestjs/common'; */ @Module({}) export class ManagedExtModule {} + +/** + * Routes of this module that answer without a session, as `Controller.handler`. + * + * Empty here, like the module. The boot gate (validate-route-guards.ts) adds + * them to its allow-list, so an install that replaces this file declares its + * anonymous surface in the same place it adds it, and the gate still fails on a + * route that is missing here or an entry that is stale. + */ +export const MANAGED_PUBLIC_ROUTES: readonly string[] = []; diff --git a/server/tests/unit/nest/managed-ext.test.ts b/server/tests/unit/nest/managed-ext.test.ts index f5956e682..13f79da91 100644 --- a/server/tests/unit/nest/managed-ext.test.ts +++ b/server/tests/unit/nest/managed-ext.test.ts @@ -7,7 +7,7 @@ * carries none of them. A stray controller or provider slipping in would be * invisible in review and obvious only to whoever it shipped to. */ -import { ManagedExtModule } from '../../../src/nest/managed/managed-ext.module'; +import { MANAGED_PUBLIC_ROUTES, ManagedExtModule } from '../../../src/nest/managed/managed-ext.module'; import { MODULE_METADATA } from '@nestjs/common/constants'; import { Test } from '@nestjs/testing'; @@ -25,6 +25,11 @@ describe('ManagedExtModule', () => { }).toEqual({ controllers: [], providers: [], imports: [], exports: [] }); }); + it('MANAGED-EXT-003: declares no route that answers without a session', () => { + // The boot gate adds these to its allow-list; the public build adds nothing. + expect(MANAGED_PUBLIC_ROUTES).toEqual([]); + }); + it('MANAGED-EXT-002: compiles as a module, so app.module can import it unconditionally', async () => { // Unconditionally on purpose: a conditional import would tie the module graph // to an env var, and the graph is built once per app while TREK_MANAGED is