54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
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<boolean> {
|
|
const requirement = this.reflector.getAllAndOverride<AccessRequirement>(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}`,
|
|
);
|
|
}
|
|
}
|