UNPKG

@elsikora/nestjs-crud-automator

Version:

A library for automating the creation of CRUD operations in NestJS.

152 lines (149 loc) 7.82 kB
import { __decorate, __metadata } from '../../../external/tslib/tslib.es6.js'; import { ApiAuthorizationRuntime } from './runtime.class.js'; import { AUTHORIZATION_DECISION_METADATA_CONSTANT } from '../../../constant/class/authorization/metadata-decision.constant.js'; import { CONTROLLER_API_DECORATOR_CONSTANT } from '../../../constant/decorator/api/controller.constant.js'; import '../../../constant/decorator/api/function.constant.js'; import { METHOD_API_DECORATOR_CONSTANT } from '../../../constant/decorator/api/method.constant.js'; import '../../../enum/class/authorization/cache-mode.enum.js'; import '../../../enum/class/authorization/decision-type.enum.js'; import { EApiPolicyEffect } from '../../../enum/class/authorization/effect.enum.js'; import '../../../enum/class/authorization/mode.enum.js'; import '../../../enum/class/authorization/permission-match.enum.js'; import '../../../enum/class/authorization/policy/attachment-type.enum.js'; import '../../../enum/class/authorization/policy/on-type.enum.js'; import '../../../enum/class/authorization/policy/source-type.enum.js'; import '../../../enum/class/authorization/policy/status.enum.js'; import '../../../enum/class/authorization/principal-type.enum.js'; import { Injectable, ForbiddenException } from '@nestjs/common'; import { ErrorException } from '../../../utility/error/exception.utility.js'; import { ApiControllerIdentityPlanGet } from '../../../utility/api/controller/identity/plan/get.utility.js'; import { LoggerUtility } from '../../../utility/logger.utility.js'; const authorizationGuardLogger = LoggerUtility.getLogger("ApiAuthorizationGuard"); let ApiAuthorizationGuard = class ApiAuthorizationGuard { runtime; constructor(runtime) { this.runtime = runtime; } async canActivate(context) { if (!this.isControllerSecurable(context)) { authorizationGuardLogger.debug("Controller is not marked as @ApiControllerSecurable, skipping authorization"); return true; } const properties = this.resolveControllerProperties(context); if (!properties?.authorization) { authorizationGuardLogger.error("ApiControllerSecurable requires an authorization block in @ApiController(...)"); throw ErrorException("ApiControllerSecurable requires an authorization block in @ApiController(...)"); } const routeMetadata = this.resolveRouteMetadata(context); const entityConstructor = routeMetadata.resource.entity; const action = routeMetadata.resource.action; authorizationGuardLogger.verbose(`Evaluating authorization for entity "${entityConstructor.name}" action "${action}"`); const request = context.switchToHttp().getRequest(); const authenticationRequest = request; const identityPlan = ApiControllerIdentityPlanGet(context.getHandler()); const requestMetadata = this.resolveRequestMetadata(request, identityPlan); const decision = await this.runtime.evaluate({ action, authenticationRequest, authorization: properties.authorization, entity: entityConstructor, requestMetadata, resource: undefined, routeAuthorization: routeMetadata.security?.authorization, routeType: routeMetadata.route.type, }); this.attachDecisionToRequest(request, decision); if (decision.effect === EApiPolicyEffect.DENY) { authorizationGuardLogger.warn(`Access denied for entity "${entityConstructor.name}" action "${action}" principal "${decision.principal.id}"`); throw new ForbiddenException(); } authorizationGuardLogger.verbose(`Access granted for entity "${entityConstructor.name}" action "${action}" principal "${decision.principal.id}"`); return true; } attachDecisionToRequest(request, decision) { request.authorizationDecision = decision; request[AUTHORIZATION_DECISION_METADATA_CONSTANT.REQUEST_KEY] = decision; } isControllerSecurable(context) { return Boolean(Reflect.getMetadata(CONTROLLER_API_DECORATOR_CONSTANT.SECURABLE_METADATA_KEY, context.getClass())); } resolveControllerProperties(context) { return Reflect.getMetadata(CONTROLLER_API_DECORATOR_CONSTANT.PROPERTIES_METADATA_KEY, context.getClass()); } resolveHeaders(headers) { if (!headers) { return undefined; } const resolvedHeaders = {}; for (const [key, value] of Object.entries(headers)) { if (typeof value === "string") { resolvedHeaders[key] = value; } if (Array.isArray(value)) { const resolvedHeaderValue = value.find((headerValue) => typeof headerValue === "string"); if (resolvedHeaderValue) { resolvedHeaders[key] = resolvedHeaderValue; } } } return resolvedHeaders; } resolveIdentityParameters(parameters, identityPlan) { if (!parameters || typeof parameters !== "object") { return undefined; } const resolvedParameters = {}; for (const key of Reflect.ownKeys(parameters)) { const descriptor = Object.getOwnPropertyDescriptor(parameters, key); if (!descriptor?.enumerable || !("value" in descriptor)) { continue; } Object.defineProperty(resolvedParameters, key, { // eslint-disable-next-line @elsikora/typescript/naming-convention configurable: true, // eslint-disable-next-line @elsikora/typescript/naming-convention enumerable: true, value: descriptor.value, // eslint-disable-next-line @elsikora/typescript/naming-convention writable: true, }); } const identityDescriptor = Object.getOwnPropertyDescriptor(resolvedParameters, identityPlan.parameter); if (identityDescriptor && "value" in identityDescriptor) { Object.defineProperty(resolvedParameters, identityPlan.field, { // eslint-disable-next-line @elsikora/typescript/naming-convention configurable: true, // eslint-disable-next-line @elsikora/typescript/naming-convention enumerable: true, value: identityDescriptor.value, // eslint-disable-next-line @elsikora/typescript/naming-convention writable: true, }); } return resolvedParameters; } resolveRequestMetadata(request, identityPlan) { return { body: request.body, headers: this.resolveHeaders(request.headers), ip: typeof request.ip === "string" ? request.ip : undefined, parameters: identityPlan ? this.resolveIdentityParameters(request.params, identityPlan) : request.params, query: request.query, }; } resolveRouteMetadata(context) { const metadata = Reflect.getMetadata(METHOD_API_DECORATOR_CONSTANT.ROUTE_METADATA_KEY, context.getHandler()); if (metadata) { return metadata; } const handlerName = context.getHandler().name || "unknown"; authorizationGuardLogger.error(`ApiControllerSecurable handler "${handlerName}" requires method-level ApiRouteMetadata`); throw ErrorException(`ApiControllerSecurable handler "${handlerName}" requires method-level ApiRouteMetadata`); } }; ApiAuthorizationGuard = __decorate([ Injectable(), __metadata("design:paramtypes", [ApiAuthorizationRuntime]) ], ApiAuthorizationGuard); export { ApiAuthorizationGuard }; //# sourceMappingURL=guard.class.js.map