All files / auth tenant.guard.ts

100% Statements 22/22
100% Branches 12/12
100% Functions 2/2
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 671x 1x   1x 1x                                           1x 6x     6x       6x 1x     5x       5x     1x     4x 4x   4x 1x       3x     3x 2x     1x      
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import type { Request } from 'express';
import { IS_PUBLIC_KEY } from './public.decorator';
import { IS_AUTH_ONLY_KEY } from './auth-only.decorator';
import type { ExecutionContext as CeoExecutionContext } from './execution-context';
 
/**
 * Sesiunea request-ului ESTE acum ExecutionContext-ul complet (spec sectiunea 3).
 * Numele vechi ramane exportat de aici pentru ca toate modulele de domeniu il
 * importa deja; nu are rost sa schimb ~15 fisiere ca sa redenumesc un tip.
 */
export type AuthenticatedSession = CeoExecutionContext;
 
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).
 * Inregistrata global (APP_GUARD) -- rutele publice trebuie sa foloseasca
 * explicit @Public(), altfel raman blocate implicit.
 */
@Injectable()
export class TenantGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
 
  canActivate(context: ExecutionContext): boolean {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) {
      return true;
    }
 
    const isAuthOnly = this.reflector.getAllAndOverride<boolean>(IS_AUTH_ONLY_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isAuthOnly) {
      // Rutele de bootstrap (ex. POST /v1/tenants) au user dar inca nu au tenant;
      // SessionGuard a validat deja tokenul.
      return true;
    }
 
    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;
  }
}