Когда я использую импорт PolicyModule.forFeature
более одного раза, следующий импорт PolicyModule
переопределяет ворота в PolicyStorage
.
Когда я пытаюсь использовать PolicyProvider
в CandidateModule
's CandidateEducationService
позвонив PolicyProvider
await this.policy.denyAccessUnlessGranted('canDelete', education);
Я получаю исключение Gate by entity 'CandidateEducationEntity' not found
.
Я вывел PolicyStorage
в CandidateEducationService
и получил ворота массива с JobPolicy
PolicyStorage {
gates:
[ { policy: [Function: JobPolicy], entity: [Function: JobEntity] } ]
}
Но я ожидал
PolicyStorage {
gates:
[ { policy: [Function: CandidateEducationPolicy], entity: [Function: CandidateEducationEntity] } ]
}
Я создал динамический модульPolicyModule
@Module({})
export class PolicyModule {
public static forFeature(gates: PolicyGate[]): DynamicModule {
const providers: Provider[] = [
...gates.map(gate => gate.policy),
{
provide: PolicyStorage,
useValue: new PolicyStorage(gates),
},
PolicyProvider,
];
return {
module: PolicyModule,
imports: [
CommonModule,
],
providers,
exports: providers,
};
}
}
PolicyStorage
@Injectable()
export class PolicyStorage {
constructor(private gates: PolicyGate[]) {
console.log(this.gates);
}
public find(name: string): PolicyGate | null {
return this.gates.find(policy => policy.entity.name === name);
}
}
PolicyProvider
@Injectable()
export class PolicyProvider<E, P> {
constructor(
private readonly moduleRef: ModuleRef,
private readonly gateStorage: PolicyStorage,
private readonly appContext: AppContextService,
) {
}
public async denyAccessUnlessGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
if (await this.denies(methodNames, entity, customData)) {
throw new ForbiddenException();
}
}
public async allowAccessIfGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
const allowed = await this.allows(methodNames, entity, customData);
if (!allowed) {
throw new ForbiddenException();
}
}
private async allows(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === true);
}
private async denies(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === false);
}
private async getPolicyResults(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean[]> {
const methodNamesArray = Array.isArray(methodNames) ? methodNames : [methodNames];
const gate = this.findByClassName(entity.constructor.name);
const user = this.appContext.get('user');
const policy = await this.moduleRef.get<P>(gate.policy, {strict: false});
const results = [];
for (const methodName of methodNamesArray) {
results.push(!!await policy[methodName as string](entity, user, customData));
}
return results;
}
private findByClassName(name: string) {
const gate = this.gateStorage.find(name);
if (!gate) {
throw new RuntimeException(`Gate by entity '${name}' not found`);
}
return gate;
}
}
Использование модуля в другом модуле.Пример:
JobsModule
@Module({
imports: [
TypeOrmModule.forFeature(
[
JobEntity,
],
),
PolicyModule.forFeature([
{
policy: JobPolicy,
entity: JobEntity,
},
]),
],
controllers: [
ManagerJobsController,
],
providers: [
ManagerJobsService,
],
})
export class JobsModule {
}
CandidateModule
@Module({
imports: [
TypeOrmModule.forFeature(
[
CandidateEducationEntity,
],
),
PolicyModule.forFeature([
{
policy: CandidateEducationPolicy,
entity: CandidateEducationEntity,
},
]),
],
controllers: [
CandidateEducationController,
],
providers: [
CandidateEducationService,
],
})
export class CandidateModule {
}