Files
tessera-ctl/apps/api/src/tenant/tenant.guard.ts
T
schalli 8320a34035
Tessera CI/CD / Lint & Type Check (push) Waiting to run
Tessera CI/CD / Tests (push) Blocked by required conditions
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
fix(07): replace TenantMiddleware with TenantGuard to fix tenant context
Middleware runs before guards in NestJS — req.user was always undefined
when TenantMiddleware executed, so req.tenantId was never set.

Convert to TenantGuard (APP_GUARD, registered after JwtAuthGuard) so it
runs after JWT validation and can read req.user.tenantId correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 20:57:03 +02:00

51 lines
1.4 KiB
TypeScript

import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { forTenant } from '../prisma/prisma-tenant.extension';
import { PrismaService } from '../prisma/prisma.service';
/**
* Runs AFTER JwtAuthGuard (guard execution order follows APP_GUARD registration order).
* At this point req.user is populated — middleware ran too early to access it.
*
* Sets req.tenantId and req.tenantPrisma for downstream controllers.
* Super-Admin can override tenant via x-tenant-id header (D-10).
*/
@Injectable()
export class TenantGuard implements CanActivate {
constructor(private readonly prisma: PrismaService) {}
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const user = req.user;
if (!user) {
// Public route (login, health) — skip tenant context
return true;
}
let tenantId: string | undefined = user.tenantId;
if (user.role === 'SUPER_ADMIN' && req.headers['x-tenant-id']) {
tenantId = req.headers['x-tenant-id'] as string;
}
if (!tenantId && user.role !== 'SUPER_ADMIN') {
throw new ForbiddenException('No tenant context');
}
if (tenantId) {
req.tenantPrisma = forTenant(this.prisma, tenantId);
req.tenantId = tenantId;
} else {
req.tenantPrisma = this.prisma;
req.tenantId = null;
}
return true;
}
}