ceo-api/src/auth/tenant.guard.ts
valentinbvro 722113a359 feat: scaffold Identity Engine (CASL/tenant guard) and Event Fabric (outbox)
Adds tenants/memberships/consent_records tables, a CASL AbilityFactory
keyed on membership role, and a TenantGuard that derives tenant_id from
session only (never client-supplied), per blueprint 8.2/8.3/11.3.

Adds outbox_events + audit_log tables, an OutboxService for transactional
writes, and a Cron-based OutboxDispatcher that publishes pending events
to a BullMQ queue, per blueprint 9.1 (events before intelligence).
2026-07-21 02:46:35 +02:00

39 lines
1.2 KiB
TypeScript

import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import type { Request } from 'express';
export interface AuthenticatedSession {
userId: string;
tenantId: string;
role: 'owner' | 'admin' | 'member';
}
interface RequestWithSession extends Request {
session?: AuthenticatedSession;
}
/**
* Blueprint 11.3: tenant_id vine intotdeauna din sesiune, niciodata din
* body/query/params. Orice request fara sesiune valida, sau care incearca
* sa suprascrie tenant_id din client, este respins (deny-by-default).
*/
@Injectable()
export class TenantGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<RequestWithSession>();
const session = request.session;
if (!session?.tenantId) {
throw new ForbiddenException('No active tenant session');
}
const clientSuppliedTenantId =
(request.body as Record<string, unknown> | undefined)?.tenantId ??
(request.query as Record<string, unknown> | undefined)?.tenantId;
if (clientSuppliedTenantId && clientSuppliedTenantId !== session.tenantId) {
throw new ForbiddenException('tenant_id must not be supplied by the client');
}
return true;
}
}