UNPKG

@elsikora/nestjs-crud-automator

Version:

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

152 lines (149 loc) 7.93 kB
'use strict'; var tslib_es6 = require('../../../external/tslib/tslib.es6.js'); var runtime_class = require('./runtime.class.js'); var metadataDecision_constant = require('../../../constant/class/authorization/metadata-decision.constant.js'); var controller_constant = require('../../../constant/decorator/api/controller.constant.js'); require('../../../constant/decorator/api/function.constant.js'); var method_constant = require('../../../constant/decorator/api/method.constant.js'); require('../../../enum/class/authorization/cache-mode.enum.js'); require('../../../enum/class/authorization/decision-type.enum.js'); var effect_enum = require('../../../enum/class/authorization/effect.enum.js'); require('../../../enum/class/authorization/mode.enum.js'); require('../../../enum/class/authorization/permission-match.enum.js'); require('../../../enum/class/authorization/policy/attachment-type.enum.js'); require('../../../enum/class/authorization/policy/on-type.enum.js'); require('../../../enum/class/authorization/policy/source-type.enum.js'); require('../../../enum/class/authorization/policy/status.enum.js'); require('../../../enum/class/authorization/principal-type.enum.js'); var common = require('@nestjs/common'); var exception_utility = require('../../../utility/error/exception.utility.js'); var get_utility = require('../../../utility/api/controller/identity/plan/get.utility.js'); var logger_utility = require('../../../utility/logger.utility.js'); const authorizationGuardLogger = logger_utility.LoggerUtility.getLogger("ApiAuthorizationGuard"); exports.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 exception_utility.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 = get_utility.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 === effect_enum.EApiPolicyEffect.DENY) { authorizationGuardLogger.warn(`Access denied for entity "${entityConstructor.name}" action "${action}" principal "${decision.principal.id}"`); throw new common.ForbiddenException(); } authorizationGuardLogger.verbose(`Access granted for entity "${entityConstructor.name}" action "${action}" principal "${decision.principal.id}"`); return true; } attachDecisionToRequest(request, decision) { request.authorizationDecision = decision; request[metadataDecision_constant.AUTHORIZATION_DECISION_METADATA_CONSTANT.REQUEST_KEY] = decision; } isControllerSecurable(context) { return Boolean(Reflect.getMetadata(controller_constant.CONTROLLER_API_DECORATOR_CONSTANT.SECURABLE_METADATA_KEY, context.getClass())); } resolveControllerProperties(context) { return Reflect.getMetadata(controller_constant.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_constant.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 exception_utility.ErrorException(`ApiControllerSecurable handler "${handlerName}" requires method-level ApiRouteMetadata`); } }; exports.ApiAuthorizationGuard = tslib_es6.__decorate([ common.Injectable(), tslib_es6.__metadata("design:paramtypes", [runtime_class.ApiAuthorizationRuntime]) ], exports.ApiAuthorizationGuard); //# sourceMappingURL=guard.class.js.map