/
casl-ability.ts
82 lines (67 loc) 路 2.13 KB
/
casl-ability.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
import * as sift from 'sift';
import qs from 'qs';
import { AbilityBuilder, Ability } from '@casl/ability';
import { pick, isNil, isObject } from 'lodash/fp';
import type { ParametrizedAction, PermissionRule } from '../../types';
export interface CustomAbilityBuilder {
can(permission: PermissionRule): ReturnType<AbilityBuilder<Ability>['can']>;
buildParametrizedAction: (parametrizedAction: ParametrizedAction) => string;
build(): Ability;
}
const allowedOperations = [
'$or',
'$and',
'$eq',
'$ne',
'$in',
'$nin',
'$lt',
'$lte',
'$gt',
'$gte',
'$exists',
'$elemMatch',
] as const;
const operations = pick(allowedOperations, sift);
const conditionsMatcher = (conditions: unknown) => {
return sift.createQueryTester(conditions, { operations });
};
const buildParametrizedAction = ({ name, params }: ParametrizedAction) => {
return `${name}?${qs.stringify(params)}`;
};
/**
* Casl Ability Builder.
*/
export const caslAbilityBuilder = (): CustomAbilityBuilder => {
const { can, build, ...rest } = new AbilityBuilder(Ability);
return {
can(permission: PermissionRule) {
const { action, subject, properties = {}, condition } = permission;
const { fields } = properties;
const caslAction = typeof action === 'string' ? action : buildParametrizedAction(action);
return can(
caslAction,
isNil(subject) ? 'all' : subject,
fields,
isObject(condition) ? condition : undefined
);
},
buildParametrizedAction({ name, params }: ParametrizedAction) {
return `${name}?${qs.stringify(params)}`;
},
build() {
const ability = build({ conditionsMatcher });
function decorateCan(originalCan: Ability['can']) {
return function (...args: Parameters<Ability['can']>) {
const [action, ...rest] = args;
const caslAction = typeof action === 'string' ? action : buildParametrizedAction(action);
// Call the original `can` method
return originalCan.apply(ability, [caslAction, ...rest]);
};
}
ability.can = decorateCan(ability.can);
return ability;
},
...rest,
};
};