import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { ACCESS_KEY, AccessRequirement } from '../decorators/access.decorator'; import { PrismaService } from '../../prisma/prisma.service'; @Injectable() export class AccessGuard implements CanActivate { constructor( private reflector: Reflector, private prisma: PrismaService, ) {} async canActivate(context: ExecutionContext): Promise { const requirement = this.reflector.getAllAndOverride(ACCESS_KEY, [ context.getHandler(), context.getClass(), ]); // No @RequireAccess decorator → allow if (!requirement) return true; const request = context.switchToHttp().getRequest(); const user = request.user; if (!user) return false; // Super admin bypasses all access checks if (user.roles?.includes('super_admin')) return true; // Resolve user's tenant role permissions from DB const assignments = await this.prisma.userTenantRole.findMany({ where: { userId: user.sub }, include: { tenantRole: { include: { permissions: true }, }, }, }); // Check if any assigned role grants the required module+action for (const a of assignments) { if (!a.tenantRole.isActive || a.tenantRole.deletedAt) continue; for (const p of a.tenantRole.permissions) { if (p.module === requirement.module && p[requirement.action]) { return true; } } } throw new ForbiddenException( `Access denied: requires ${requirement.action} on ${requirement.module}`, ); } }