@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
218 lines (215 loc) • 15.4 kB
JavaScript
'use strict';
var tslib_es6 = require('../../../external/tslib/tslib.es6.js');
var cache_class = require('./hook/permission/cache.class.js');
require('./hook/permission/resolver.class.js');
var cache_class$1 = require('./iam/attachment/cache.class.js');
require('./iam/attachment/resolver.class.js');
var cache_class$2 = require('./iam/document/cache.class.js');
require('./iam/document/resolver.class.js');
require('./iam/document/validator.class.js');
require('./iam/engine.class.js');
require('./iam/query-planner.class.js');
var registry_class = require('./policy/registry.class.js');
var moduleOptionsToken_constant = require('../../../constant/class/authorization/module-options-token.constant.js');
var documentSourcesToken_constant = require('../../../constant/class/authorization/policy/document-sources-token.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');
require('../../../enum/class/authorization/effect.enum.js');
var mode_enum = 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');
require('../../../enum/decorator/api/route/subscriber-authorization-expectation.enum.js');
var type_enum = require('../../../enum/decorator/api/route/type.enum.js');
var common = require('@nestjs/common');
var core = require('@nestjs/core');
var exception_utility = require('../../../utility/error/exception.utility.js');
var logger_utility = require('../../../utility/logger.utility.js');
const authorizationBootstrapValidationLogger = logger_utility.LoggerUtility.getLogger("ApiAuthorizationBootstrapValidationService");
exports.ApiAuthorizationBootstrapValidationService = class ApiAuthorizationBootstrapValidationService {
discoveryService;
policyRegistry;
hookPermissionCache;
iamAttachmentCache;
iamDocumentCache;
documentSources;
moduleOptions;
constructor(discoveryService, policyRegistry, hookPermissionCache, iamAttachmentCache, iamDocumentCache, documentSources = [], moduleOptions = {}) {
this.discoveryService = discoveryService;
this.policyRegistry = policyRegistry;
this.hookPermissionCache = hookPermissionCache;
this.iamAttachmentCache = iamAttachmentCache;
this.iamDocumentCache = iamDocumentCache;
this.documentSources = documentSources;
this.moduleOptions = moduleOptions;
}
onApplicationBootstrap() {
this.hookPermissionCache.configure(this.moduleOptions.cache);
this.iamAttachmentCache.configure(this.moduleOptions.cache);
this.iamDocumentCache.configure(this.moduleOptions.cache);
const controllerWrappers = this.discoveryService.getControllers();
authorizationBootstrapValidationLogger.verbose(`Starting authorization bootstrap validation for ${controllerWrappers.length} controllers.`);
for (const controllerWrapper of controllerWrappers) {
this.validateController(controllerWrapper);
}
authorizationBootstrapValidationLogger.verbose("Authorization bootstrap validation finished.");
}
assertValidMode(mode, controllerName, routeType) {
if (mode === mode_enum.EApiAuthorizationMode.HOOKS || mode === mode_enum.EApiAuthorizationMode.IAM) {
return;
}
if (routeType) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" route "${routeType}" uses unknown authorization mode "${String(mode)}"`);
throw exception_utility.ErrorException(`Controller "${controllerName}" route "${routeType}" uses unknown authorization mode "${String(mode)}"`);
}
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" uses unknown authorization mode "${String(mode)}"`);
throw exception_utility.ErrorException(`Controller "${controllerName}" uses unknown authorization mode "${String(mode)}"`);
}
validateController(controllerWrapper) {
const metatype = controllerWrapper.metatype;
if (!metatype) {
return;
}
authorizationBootstrapValidationLogger.verbose(`Validating authorization configuration for controller "${metatype.name}".`);
const isSecurable = Boolean(Reflect.getMetadata(controller_constant.CONTROLLER_API_DECORATOR_CONSTANT.SECURABLE_METADATA_KEY, metatype));
const properties = Reflect.getMetadata(controller_constant.CONTROLLER_API_DECORATOR_CONSTANT.PROPERTIES_METADATA_KEY, metatype);
const authorization = properties?.authorization;
if (isSecurable && !authorization) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" is marked with but does not declare an authorization block`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" is marked with but does not declare an authorization block`);
}
if (!isSecurable && authorization) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" declares an authorization block but is missing `);
throw exception_utility.ErrorException(`Controller "${metatype.name}" declares an authorization block but is missing `);
}
if (!authorization || !properties) {
return;
}
this.assertValidMode(authorization.defaultMode, metatype.name);
const enabledRouteTypes = Object.values(type_enum.EApiRouteType).filter((routeType) => properties.routes[routeType]?.generation?.isEnabled !== false);
const enabledRouteModes = new Set(enabledRouteTypes.map((routeType) => properties.routes[routeType]?.security?.authorization?.mode ?? authorization.defaultMode));
const methodRouteModes = this.validateMethodLevelRouteMetadata(metatype, properties);
for (const routeMode of methodRouteModes) {
enabledRouteModes.add(routeMode);
}
const usesHooks = enabledRouteModes.has(mode_enum.EApiAuthorizationMode.HOOKS);
const usesIam = enabledRouteModes.has(mode_enum.EApiAuthorizationMode.IAM);
for (const routeType of enabledRouteTypes) {
const routeMode = properties.routes[routeType]?.security?.authorization?.mode ?? authorization.defaultMode;
this.assertValidMode(routeMode, metatype.name, routeType);
}
if (usesHooks && !this.policyRegistry.hasSubscriberForEntity(properties.entity)) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" uses hooks authorization but no is registered for entity "${properties.entity.name ?? "UnknownEntity"}"`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" uses hooks authorization but no is registered for entity "${properties.entity.name ?? "UnknownEntity"}"`);
}
if (usesIam) {
if (!authorization.policyNamespace) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" uses IAM authorization but does not declare policyNamespace`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" uses IAM authorization but does not declare policyNamespace`);
}
if (!authorization.resourceDefinition) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" uses IAM authorization but does not declare resourceDefinition`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" uses IAM authorization but does not declare resourceDefinition`);
}
if (this.documentSources.length === 0) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" uses IAM authorization but no documentSources are registered`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" uses IAM authorization but no documentSources are registered`);
}
if (authorization.resourceDefinition.namespace !== authorization.policyNamespace) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" resourceDefinition namespace must match policyNamespace`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" resourceDefinition namespace must match policyNamespace`);
}
if (authorization.resourceDefinition.entity !== properties.entity) {
authorizationBootstrapValidationLogger.error(`Controller "${metatype.name}" resourceDefinition entity must match controller entity`);
throw exception_utility.ErrorException(`Controller "${metatype.name}" resourceDefinition entity must match controller entity`);
}
this.validateResourceDefinition(metatype.name, authorization.resourceDefinition);
}
}
validateMethodLevelRouteMetadata(metatype, properties) {
const modes = new Set();
for (const methodName of Object.getOwnPropertyNames(metatype.prototype)) {
if (methodName === "constructor") {
continue;
}
const handler = metatype.prototype[methodName];
const handlerMetadata = typeof handler === "function" ? Reflect.getMetadata(method_constant.METHOD_API_DECORATOR_CONSTANT.ROUTE_METADATA_KEY, handler) : undefined;
const prototypeMetadata = Reflect.getMetadata(method_constant.METHOD_API_DECORATOR_CONSTANT.ROUTE_METADATA_KEY, metatype.prototype, methodName);
const metadata = (handlerMetadata ?? prototypeMetadata);
if (!metadata) {
continue;
}
if (!metadata.resource?.action || typeof metadata.resource.action !== "string") {
throw exception_utility.ErrorException(`Controller "${metatype.name}" method "${methodName}" must declare route resource.action`);
}
if (metadata.resource.entity !== properties.entity) {
throw exception_utility.ErrorException(`Controller "${metatype.name}" method "${methodName}" route resource.entity must match controller entity`);
}
const mode = metadata.security?.authorization?.mode;
if (!mode) {
throw exception_utility.ErrorException(`Controller "${metatype.name}" method "${methodName}" must declare method-level authorization mode`);
}
this.assertValidMode(mode, metatype.name);
modes.add(mode);
}
return modes;
}
validateResourceDefinition(controllerName, resourceDefinition) {
if (!resourceDefinition) {
return;
}
if (typeof resourceDefinition.namespace !== "string" || resourceDefinition.namespace.length === 0) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resourceDefinition.namespace must be a non-empty string`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resourceDefinition.namespace must be a non-empty string`);
}
if (typeof resourceDefinition.resourceType !== "string" || resourceDefinition.resourceType.length === 0) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resourceDefinition.resourceType must be a non-empty string`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resourceDefinition.resourceType must be a non-empty string`);
}
if (typeof resourceDefinition.resourcePath !== "string" || resourceDefinition.resourcePath.length === 0) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resourceDefinition.resourcePath must be a non-empty string`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resourceDefinition.resourcePath must be a non-empty string`);
}
const fieldPaths = new Set();
const queryPaths = new Set();
for (const field of resourceDefinition.fields) {
if (!field.path.startsWith("resource.")) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resource field path "${field.path}" must start with "resource."`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resource field path "${field.path}" must start with "resource."`);
}
if (field.path.length === 0 || field.queryPath.length === 0) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resource field mappings must use non-empty path and queryPath`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resource field mappings must use non-empty path and queryPath`);
}
if (fieldPaths.has(field.path)) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resourceDefinition contains duplicate field path "${field.path}"`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resourceDefinition contains duplicate field path "${field.path}"`);
}
if (queryPaths.has(field.queryPath)) {
authorizationBootstrapValidationLogger.error(`Controller "${controllerName}" resourceDefinition contains duplicate queryPath "${field.queryPath}"`);
throw exception_utility.ErrorException(`Controller "${controllerName}" resourceDefinition contains duplicate queryPath "${field.queryPath}"`);
}
fieldPaths.add(field.path);
queryPaths.add(field.queryPath);
}
}
};
exports.ApiAuthorizationBootstrapValidationService = tslib_es6.__decorate([
common.Injectable(),
tslib_es6.__param(5, common.Inject(documentSourcesToken_constant.AUTHORIZATION_POLICY_DOCUMENT_SOURCES_TOKEN)),
tslib_es6.__param(5, common.Optional()),
tslib_es6.__param(6, common.Inject(moduleOptionsToken_constant.AUTHORIZATION_MODULE_OPTIONS_TOKEN)),
tslib_es6.__param(6, common.Optional()),
tslib_es6.__metadata("design:paramtypes", [core.DiscoveryService,
registry_class.ApiAuthorizationPolicyRegistry,
cache_class.ApiAuthorizationHookPermissionCache,
cache_class$1.ApiAuthorizationIamAttachmentCache,
cache_class$2.ApiAuthorizationIamDocumentCache, Array, Object])
], exports.ApiAuthorizationBootstrapValidationService);
//# sourceMappingURL=bootstrap-validation.service.class.js.map