Files
Edu/services/msg/src/middleware/permission.guard.ts

80 lines
2.3 KiB
TypeScript

import {
Injectable,
CanActivate,
ExecutionContext,
SetMetadata,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { PermissionDeniedError } from "../shared/errors/application-error.js";
import type { AuthenticatedRequest } from "./auth.middleware.js";
export const Permissions = {
MSG_NOTIFICATION_SEND: "MSG_NOTIFICATION_SEND" as const,
MSG_NOTIFICATION_READ: "MSG_NOTIFICATION_READ" as const,
MSG_NOTIFICATION_MANAGE: "MSG_NOTIFICATION_MANAGE" as const,
MSG_ANNOUNCEMENT_MANAGE: "MSG_ANNOUNCEMENT_MANAGE" as const,
MSG_ANNOUNCEMENT_READ: "MSG_ANNOUNCEMENT_READ" as const,
} as const;
export type Permission = (typeof Permissions)[keyof typeof Permissions];
export const PERMISSIONS_KEY = "permissions";
export const RequirePermission = (...permissions: Permission[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);
const ROLE_PERMISSIONS: Record<string, Permission[]> = {
admin: [
Permissions.MSG_NOTIFICATION_SEND,
Permissions.MSG_NOTIFICATION_READ,
Permissions.MSG_NOTIFICATION_MANAGE,
Permissions.MSG_ANNOUNCEMENT_MANAGE,
Permissions.MSG_ANNOUNCEMENT_READ,
],
teacher: [
Permissions.MSG_NOTIFICATION_SEND,
Permissions.MSG_NOTIFICATION_READ,
Permissions.MSG_ANNOUNCEMENT_MANAGE,
Permissions.MSG_ANNOUNCEMENT_READ,
],
student: [
Permissions.MSG_NOTIFICATION_READ,
Permissions.MSG_ANNOUNCEMENT_READ,
],
parent: [
Permissions.MSG_NOTIFICATION_READ,
Permissions.MSG_ANNOUNCEMENT_READ,
],
};
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
if (process.env.DEV_MODE === "true") {
return true;
}
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
PERMISSIONS_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredPermissions || requiredPermissions.length === 0) {
return true;
}
const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
const roles = request.userRoles ?? [];
for (const role of roles) {
const perms = ROLE_PERMISSIONS[role];
if (perms && requiredPermissions.some((p) => perms.includes(p))) {
return true;
}
}
throw new PermissionDeniedError(requiredPermissions.join(", "));
}
}