NestJS (6 services): implement @RequirePermission decorator with SetMetadata+Reflector, register APP_GUARD globally, fix as assertions to type guards, add explicit return types, fix import type for express, fix /metrics implicit any, replace native Error with ApplicationError, remove typeorm remnants, register LifecycleService. teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward real userId to downstream, log downstream failures, migrate health controller to shared/health. Go (2 services): interface to any, doc comments, CORS dev whitelist, JWT secret fail-fast, push-gateway internal API auth, metrics and readyz endpoints, remove dead code. Python (2 services): lifespan return type, dev_mode to bool, data-ana APIRouter, ai POST body model, ClickHouse async wrapping.
72 lines
2.0 KiB
TypeScript
72 lines
2.0 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 type Permission =
|
|
"CLASSES_CREATE" | "CLASSES_READ" | "CLASSES_UPDATE" | "CLASSES_DELETE";
|
|
|
|
export const Permissions = {
|
|
CLASSES_CREATE: "CLASSES_CREATE" as const,
|
|
CLASSES_READ: "CLASSES_READ" as const,
|
|
CLASSES_UPDATE: "CLASSES_UPDATE" as const,
|
|
CLASSES_DELETE: "CLASSES_DELETE" as const,
|
|
};
|
|
|
|
export const PERMISSIONS_KEY = "permissions";
|
|
export const RequirePermission = (...permissions: Permission[]) =>
|
|
SetMetadata(PERMISSIONS_KEY, permissions);
|
|
|
|
const ROLE_PERMISSIONS: Record<string, Permission[]> = {
|
|
admin: [
|
|
Permissions.CLASSES_CREATE,
|
|
Permissions.CLASSES_READ,
|
|
Permissions.CLASSES_UPDATE,
|
|
Permissions.CLASSES_DELETE,
|
|
],
|
|
teacher: [
|
|
Permissions.CLASSES_CREATE,
|
|
Permissions.CLASSES_READ,
|
|
Permissions.CLASSES_UPDATE,
|
|
],
|
|
student: [Permissions.CLASSES_READ],
|
|
parent: [Permissions.CLASSES_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(", "));
|
|
}
|
|
}
|