-
Notifications
You must be signed in to change notification settings - Fork 0
/
policy.class-method.decorator.ts
120 lines (113 loc) · 4.26 KB
/
policy.class-method.decorator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { UseGuards, applyDecorators } from '@nestjs/common';
import { isArray } from 'lodash';
import { orGuard } from '../or.guard';
import { PoliciesGuard } from '../policies.guard';
import { AnyAbilityLike, GuardsList, PolicyDescriptor } from '../types';
import { addPolicyMetadata } from './proto-utils';
/**
* @see {@link Policy}
* @ignore
*/
export type Policy<TAbility extends AnyAbilityLike> = {
/**
* @see {@link Policy}
*/
( policy: PolicyDescriptor<TAbility> ): BoundPolicy<TAbility>;
/**
* @see {@link Policy.usingGuard}
*/
usingGuard( ...guards: GuardsList ): Policy<TAbility>;
}
/**
* A policy method or class decorator ready to be applied. You may add new guards using {@link BoundPolicy.usingGuard}.
*/
type BoundPolicy<TAbility extends AnyAbilityLike> =
& MethodDecorator
& ClassDecorator
& {
/**
* Add more {@link guards} to run before checking.
*
* @returns the decorator with the same ability.
*/
usingGuard: ( ...guards: GuardsList ) => BoundPolicy<TAbility>;
};
const guardsList = Symbol( 'Guards list' );
const guardsListToGuardArray = ( guards: GuardsList ) => guards
.map( g => UseGuards( ...( isArray( g ) ?
g.length > 1 ?
// If multiple guards in an array (`usingGuard([Foo, Bar])`, join them in an `OrGuard`)
[ orGuard( g ) ] :
// If single guard in an array, don't change
g :
// If single guard, use it directly
[ g ] ) ) );
const applyPolicyGuards = <TAbility extends AnyAbilityLike>(
policy: PolicyDescriptor<TAbility>,
policyHost: any,
guards: GuardsList,
target: Parameters<MethodDecorator | ClassDecorator>,
) => {
if( policy === true ){
return;
} else if( policy === false ){
addPolicyMetadata( policy )( policyHost );
return UseGuards( PoliciesGuard )( ...target as [any] );
} else {
addPolicyMetadata( policy )( policyHost );
const guardsDecorators = guardsListToGuardArray( guards );
const fullDecorator = applyDecorators(
...guardsDecorators,
UseGuards( PoliciesGuard ) );
fullDecorator( ...target as [any] );
}
};
/**
* A method & class decorator factory that you can call by passing a {@link PolicyDescriptor}.
* You can also call {@link Policy.usingGuard} to create a new {@link Policy} decorator that will always apply the given guards before checking.
*
* @category Decorators
* @param this - Context. Not a parameter.
* @param policy - The policy descriptor.
* @returns a method or class decorator.
*/
export function Policy<TAbility extends AnyAbilityLike>( this: unknown, policy: PolicyDescriptor<TAbility> ): BoundPolicy<TAbility> {
const described = ( ( ...args: Parameters<MethodDecorator | ClassDecorator> ) => {
const guards: GuardsList = Reflect.getMetadata( guardsList, described ) ?? [];
if( args.length === 3 ){
const propLabel = `${String( ( args[0] as any )?.name )}#${String( args[1] )}`;
if( !args[0] || !args[2] ){
throw new TypeError( `Invalid bind on ${propLabel}` );
} else if( typeof args[2].value !== 'function' ){
throw new TypeError( `${propLabel} is not a method` );
}
applyPolicyGuards( policy, args[2].value, guards, args );
} else if( args.length === 1 ){
applyPolicyGuards( policy, args[0], guards, args );
} else {
throw new RangeError( 'Invalid call arguments' );
}
} ) as BoundPolicy<TAbility>;
described.usingGuard = ( ...guards ) => {
const oldGuardsList: GuardsList = Reflect.getMetadata( guardsList, described ) ?? [];
const newPolicy = Policy( policy );
Reflect.defineMetadata( guardsList, oldGuardsList.concat( guards ), newPolicy );
return newPolicy;
};
Reflect.defineMetadata( guardsList, ( this ? Reflect.getMetadata( guardsList, this as any ) : null ) ?? [], described );
return described;
}
/**
* Create a new {@link Policy} decorator factory that will always use the given {@link guards} before checking.
*
* @param guards - The list of guards to use.
* @returns a new {@link Policy} decorator factory.
*/
Policy.usingGuard = function usingGuard( ...guards: GuardsList ){
const newPolicyThis = {};
const newPolicy = Policy.bind( newPolicyThis ) as typeof Policy;
newPolicy.usingGuard = Policy.usingGuard.bind( newPolicyThis );
const oldGuardsList: GuardsList = Reflect.getMetadata( guardsList, this ) ?? [];
Reflect.defineMetadata( guardsList, oldGuardsList.concat( guards ), newPolicyThis );
return newPolicy;
};