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