@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
152 lines (149 loc) • 7.82 kB
JavaScript
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