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).
39 lines
1.2 KiB
TypeScript
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;
|
|
}
|
|
}
|