UNPKG

@elsikora/nestjs-crud-automator

Version:

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

205 lines (201 loc) 9.02 kB
'use strict'; var executor_class = require('./executor.class.js'); var subscriberWrapper_class = require('./subscriber-wrapper.class.js'); var decorator_constant = require('../../../../constant/class/authorization/policy/decorator.constant.js'); var resolveDefaultPrincipal_utility = require('../../../../utility/authorization/resolve-default-principal.utility.js'); var generateEntityInformation_utility = require('../../../../utility/generate-entity-information.utility.js'); var logger_utility = require('../../../../utility/logger.utility.js'); const policyRegistryLogger = logger_utility.LoggerUtility.getLogger("ApiAuthorizationPolicyRegistry"); class ApiAuthorizationPolicyRegistry { cacheOptions; POLICY_REGISTRY; POLICY_RULE_CACHE; constructor() { this.POLICY_RULE_CACHE = new Map(); this.POLICY_REGISTRY = new Map(); this.cacheOptions = { isEnabled: false }; } async buildAggregatedPolicy(entity, action, options = {}) { const entityName = this.getEntityName(entity); policyRegistryLogger.debug(`Building aggregated policy for entity "${entityName}" action "${action}"`); const registrationWrapper = this.POLICY_REGISTRY.get(entityName); const registrations = registrationWrapper?.registrations ?? []; policyRegistryLogger.debug(`Found ${registrations.length} registration(s) for entity "${entityName}"`); policyRegistryLogger.debug(`All registered entities: [${[...this.POLICY_REGISTRY.values()].map((wrapper) => wrapper.getName()).join(", ")}]`); if (registrations.length === 0) { return undefined; } const entityMetadata = generateEntityInformation_utility.GenerateEntityInformation(entity); const { authenticationRequest, permissions = [], principal: principalOverride, principalResolver, requestMetadata, routeType: routeTypeOverride } = options; const principal = await this.resolvePrincipal(authenticationRequest, principalOverride, principalResolver); const contextData = { action, authenticationRequest, ...requestMetadata, entity, entityMetadata, permissions, principal, routeType: routeTypeOverride, }; const aggregatedRules = []; const policyIds = new Set(); for (const registration of registrations) { const context = { ...contextData, DATA: contextData, }; const rules = await this.resolvePolicyRules(registration, action, context, entityName); if (rules.length === 0) { continue; } policyIds.add(registration.policyId); const normalizedRules = rules.map((rule) => this.normalizeRule(registration.policyId, registration.priority ?? 0, rule, action)); aggregatedRules.push(...normalizedRules); } if (aggregatedRules.length === 0) { return undefined; } aggregatedRules.sort((a, b) => b.priority - a.priority); const policyDescription = registrations.find((registration) => Boolean(registration.description))?.description; const policyIdList = [...policyIds]; const policy = { action, description: policyDescription, entity, policyId: this.resolvePolicyId(entity), policyIds: policyIdList, rules: aggregatedRules, }; return policy; } clear() { this.POLICY_RULE_CACHE.clear(); this.POLICY_REGISTRY.clear(); } configureCache(options = {}) { this.cacheOptions = { isEnabled: Boolean(options.isEnabled), ttlMs: options.ttlMs, }; } hasSubscriberForEntity(entity) { return (this.POLICY_REGISTRY.get(this.getEntityName(entity))?.registrations.length ?? 0) > 0; } invalidateCache(entity) { if (!entity) { this.POLICY_RULE_CACHE.clear(); return; } this.invalidateCacheForEntity(this.getEntityName(entity)); } registerSubscriber(registration) { const normalizedRegistration = { cache: registration.cache, description: registration.description, entity: registration.entity, policyId: registration.policyId, priority: registration.priority ?? 0, subscriber: registration.subscriber, }; const entityName = this.getEntityName(normalizedRegistration.entity); policyRegistryLogger.verbose(`Registering policy subscriber for entity "${entityName}" with policyId "${normalizedRegistration.policyId}" and priority ${normalizedRegistration.priority}`); let wrapper = this.POLICY_REGISTRY.get(entityName); if (!wrapper) { wrapper = new subscriberWrapper_class.PolicySubscriberWrapper(entityName); this.POLICY_REGISTRY.set(entityName, wrapper); } wrapper.addRegistration(normalizedRegistration); policyRegistryLogger.debug(`Total registrations for entity "${entityName}": ${wrapper.getRegistrationCount()}`); this.invalidateCache(normalizedRegistration.entity); } cacheRules(cacheKey, rules, cacheOptions) { if (!cacheOptions.isEnabled) { return; } this.POLICY_RULE_CACHE.set(cacheKey, { cachedAt: Date.now(), rules: rules }); } createPolicyCacheKey(entityName, registration, action, routeType) { const subscriberName = this.getSubscriberName(registration.subscriber); return `${entityName}::${registration.policyId}::${subscriberName}::${(routeType ?? "custom").toLowerCase()}::${action.toLowerCase()}`; } getCachedRules(cacheKey, cacheOptions) { if (!cacheOptions.isEnabled) { return undefined; } const cachedEntry = this.POLICY_RULE_CACHE.get(cacheKey); if (!cachedEntry) { return undefined; } if (this.isCacheExpired(cachedEntry.cachedAt, cacheOptions.ttlMs)) { this.POLICY_RULE_CACHE.delete(cacheKey); return undefined; } return cachedEntry.rules; } getEntityName(entity) { return (entity.name ?? "UnknownResource").toLowerCase(); } getSubscriberName(subscriber) { return subscriber.constructor?.name ?? "UnknownPolicySubscriber"; } invalidateCacheForEntity(entityName) { for (const cacheKey of this.POLICY_RULE_CACHE.keys()) { if (cacheKey.startsWith(`${entityName}::`)) { this.POLICY_RULE_CACHE.delete(cacheKey); } } } isCacheExpired(cachedAt, ttlMs) { if (ttlMs === undefined) { return false; } return Date.now() - cachedAt > ttlMs; } normalizeRule(policyId, subscriberPriority, rule, action) { const rulePriority = rule.priority ?? 0; return { action, condition: rule.condition, description: rule.description, effect: rule.effect, policyId, priority: subscriberPriority + rulePriority, resultTransform: rule.resultTransform, scope: rule.scope, }; } resolveCacheOptions(options) { return { isEnabled: options?.isEnabled ?? this.cacheOptions.isEnabled, ttlMs: options?.ttlMs ?? this.cacheOptions.ttlMs, }; } resolvePolicyId(entity) { return `${this.getEntityName(entity)}${decorator_constant.AUTHORIZATION_POLICY_DECORATOR_CONSTANT.DEFAULT_POLICY_ID_SUFFIX}`; } async resolvePolicyRules(registration, action, context, entityName) { const cacheOptions = this.resolveCacheOptions(registration.cache); const cacheKey = this.createPolicyCacheKey(entityName, registration, action, context.routeType); const cachedRules = this.getCachedRules(cacheKey, cacheOptions); if (cachedRules) { return cachedRules; } const rules = await executor_class.ApiAuthorizationPolicyExecutor.execute(registration.subscriber, action, context); this.cacheRules(cacheKey, rules, cacheOptions); return rules; } async resolvePrincipal(authenticationRequest, principalOverride, principalResolver) { if (principalOverride) { return principalOverride; } if (principalResolver) { return await principalResolver.resolve(authenticationRequest?.user, authenticationRequest); } return resolveDefaultPrincipal_utility.AuthorizationResolveDefaultPrincipal(authenticationRequest?.user); } } const apiAuthorizationPolicyRegistry = new ApiAuthorizationPolicyRegistry(); exports.ApiAuthorizationPolicyRegistry = ApiAuthorizationPolicyRegistry; exports.apiAuthorizationPolicyRegistry = apiAuthorizationPolicyRegistry; //# sourceMappingURL=registry.class.js.map