import { CanActivate, ExecutionContext, Injectable, ServiceUnavailableException, SetMetadata, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { SetupStateService } from './setup-state.service'; const SETUP_EXEMPT_KEY = 'setupExempt'; /** * Marks routes that stay reachable while the instance still requires the * first-run setup: the wizard itself, health probes, and the session * routes (so a mid-wizard admin who lost the cookie can sign back in). */ export const SetupExempt = (): MethodDecorator & ClassDecorator => SetMetadata(SETUP_EXEMPT_KEY, true); /** * Global first-line guard (registered before AuthGuard via module order): * while setup is pending every non-exempt route answers 503 * `setup_required`, so clients — including anonymous ones — always learn * the instance state instead of a misleading 401 (issue #80). */ @Injectable() export class SetupGuard implements CanActivate { constructor( private readonly reflector: Reflector, private readonly state: SetupStateService, ) {} async canActivate(context: ExecutionContext): Promise { const exempt = this.reflector.getAllAndOverride(SETUP_EXEMPT_KEY, [ context.getHandler(), context.getClass(), ]); if (exempt) return true; if (await this.state.isPending()) { throw new ServiceUnavailableException({ code: 'setup_required' }); } return true; } }