@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
156 lines (153 loc) • 6.7 kB
JavaScript
import { AUTHORIZATION_POLICY_DECORATOR_CONSTANT } from '../../../../constant/class/authorization/policy/decorator.constant.js';
import '../../../../external/@elsikora/cladi/dist/esm/domain/enum/logger-log-level.enum.js';
import '../../../../external/@elsikora/cladi/dist/esm/infrastructure/constant/console-logger-default-options.constant.js';
import { createRegistry } from '../../../../external/@elsikora/cladi/dist/esm/presentation/utility/create/registry.utility.js';
import { EApiRouteType } from '../../../../enum/decorator/api/route-type.enum.js';
import { GenerateEntityInformation } from '../../../../utility/generate-entity-information.utility.js';
import { LoggerUtility } from '../../../../utility/logger.utility.js';
import { ApiAuthorizationPolicyExecutor } from './executor.class.js';
const policyRegistryLogger = LoggerUtility.getLogger("ApiAuthorizationPolicyRegistry");
class ApiAuthorizationPolicyRegistry {
POLICY_CACHE;
POLICY_REGISTRY;
constructor() {
this.POLICY_CACHE = new Map();
this.POLICY_REGISTRY = createRegistry({});
}
async buildAggregatedPolicy(entity, action) {
const entityName = this.getEntityName(entity);
const cacheKey = this.createCacheKey(entity, action);
policyRegistryLogger.debug(`Building aggregated policy for entity "${entityName}" action "${action}" (cache key: ${cacheKey})`);
const cachedPolicy = this.POLICY_CACHE.get(cacheKey);
if (cachedPolicy) {
policyRegistryLogger.debug(`Returning cached policy for "${cacheKey}"`);
return cachedPolicy;
}
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.getAll()
.map((wrapper) => wrapper.getName())
.join(", ")}]`);
if (registrations.length === 0) {
return undefined;
}
const entityMetadata = GenerateEntityInformation(entity);
const routeType = this.resolveRouteType(action);
const aggregatedRules = [];
for (const registration of registrations) {
const context = {
action,
entity,
entityMetadata,
routeType,
};
const rules = await ApiAuthorizationPolicyExecutor.execute(registration.subscriber, action, context);
if (rules.length === 0) {
continue;
}
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 policy = {
action,
description: policyDescription,
entity,
policyId: this.resolvePolicyId(entity),
rules: aggregatedRules,
};
this.cachePolicy(cacheKey, policy);
return policy;
}
clear() {
this.POLICY_CACHE.clear();
this.POLICY_REGISTRY.clear();
}
registerSubscriber(registration) {
const normalizedRegistration = {
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 PolicySubscriberWrapper(entityName);
this.POLICY_REGISTRY.register(wrapper);
}
wrapper.addRegistration(normalizedRegistration);
policyRegistryLogger.debug(`Total registrations for entity "${entityName}": ${wrapper.getRegistrationCount()}`);
this.invalidateCacheForEntity(entityName);
}
cachePolicy(cacheKey, policy) {
this.POLICY_CACHE.set(cacheKey, this.toBasePolicy(policy));
}
createCacheKey(entity, action) {
return `${this.getEntityName(entity)}::${action.toLowerCase()}`;
}
getEntityName(entity) {
return (entity.name ?? "UnknownResource").toLowerCase();
}
invalidateCacheForEntity(entityName) {
for (const cacheKey of this.POLICY_CACHE.keys()) {
if (cacheKey.startsWith(`${entityName}::`)) {
this.POLICY_CACHE.delete(cacheKey);
}
}
}
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,
};
}
resolvePolicyId(entity) {
return `${this.getEntityName(entity)}${AUTHORIZATION_POLICY_DECORATOR_CONSTANT.DEFAULT_POLICY_ID_SUFFIX}`;
}
resolveRouteType(action) {
const routeTypes = Object.values(EApiRouteType);
return routeTypes.find((routeType) => routeType === action);
}
toBasePolicy(policy) {
return policy;
}
}
const apiAuthorizationPolicyRegistry = new ApiAuthorizationPolicyRegistry();
class PolicySubscriberWrapper {
name;
registrations;
constructor(name, registrations = []) {
this.name = name;
this.registrations = registrations;
}
addRegistration(registration) {
this.registrations.push(registration);
this.registrations.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
}
getName() {
return this.getNormalizedName();
}
getRegistrationCount() {
return this.registrations.length;
}
getNormalizedName() {
return this.name;
}
}
export { ApiAuthorizationPolicyRegistry, apiAuthorizationPolicyRegistry };
//# sourceMappingURL=registry.class.js.map