import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Reflector } from '@nestjs/core'; import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; /** * ServiceKeyGuard — accepts X-Service-Key header for automation tools (n8n, cron scripts). * Injects tenantId from N8N_DEFAULT_TENANT_ID env var. * Only activates when a valid key is present; falls through to JWT guard otherwise. */ @Injectable() export class ServiceKeyGuard implements CanActivate { private readonly serviceKey: string; private readonly defaultTenantId: string; constructor( private readonly config: ConfigService, private readonly reflector: Reflector, ) { this.serviceKey = config.get('N8N_SERVICE_KEY', ''); this.defaultTenantId = config.get('N8N_DEFAULT_TENANT_ID', ''); } canActivate(ctx: ExecutionContext): boolean { // Skip on @Public() routes const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ ctx.getHandler(), ctx.getClass(), ]); if (isPublic) return true; if (!this.serviceKey) return false; // service key not configured → fall through const req = ctx.switchToHttp().getRequest(); const provided = req.headers['x-service-key'] ?? ''; if (!provided || provided !== this.serviceKey) return false; if (!this.defaultTenantId) { throw new UnauthorizedException('N8N_DEFAULT_TENANT_ID not configured'); } // Inject synthetic session so @CurrentSession() works req.session = { tenantId: this.defaultTenantId, userId: 'n8n-service', isService: true }; return true; } }