49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
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<boolean>(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;
|
|
}
|
|
}
|