-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
102 lines (86 loc) · 2.76 KB
/
index.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
// Used when the PunditPolicy#filter() decides NOTHING should be matched.
// This serves the same purpose of ActiveRecord's `none` query method.
// See: https://apidock.com/rails/v7.1.3.4/ActiveRecord/QueryMethods/none
export const punditMatchNothing: unique symbol = Symbol("punditMatchNothing");
export type PunditMatchNothing = typeof punditMatchNothing;
export abstract class PunditPolicy<
Context,
Model,
Actions extends string,
Filter = unknown
> {
constructor(private cons: new (...args: any[]) => Model) {}
abstract authorize(
context: Context,
object: Model,
action: Actions
): Promise<boolean> | boolean;
abstract filter(
context: Context
): Filter | Promise<Filter> | typeof punditMatchNothing;
handlesModel(object: unknown): object is Model {
return object instanceof this.cons;
}
handlesModelConstructor(cons: unknown): cons is new () => Model {
return cons === this.cons;
}
}
type FindAction<C, P extends PunditPolicy<C, unknown, any>[], M> = P extends [
infer F,
...infer R
]
? F extends PunditPolicy<C, M, infer A>
? A
: R extends PunditPolicy<C, any, any>[]
? FindAction<C, R, M>
: C
: "No proper policy registered for handling this model type";
type FindFilterType<
C,
P extends PunditPolicy<C, unknown, any>[],
M
> = P extends [infer F, ...infer R]
? F extends PunditPolicy<C, M, infer _, infer Filter>
? Filter
: R extends PunditPolicy<C, any, any>[]
? FindFilterType<C, R, M>
: C
: "No proper policy registered for handling this model type";
export class Pundit<C, P extends PunditPolicy<C, unknown, any>[] = []> {
constructor(private policies: PunditPolicy<C, unknown, any>[] = []) {}
register<M, A extends string, F>(
policy: PunditPolicy<C, M, A, F>
): Pundit<C, [...P, PunditPolicy<C, M, A, F>]> {
return new Pundit(this.policies.concat(policy)) as unknown as Pundit<
C,
[...P, PunditPolicy<C, M, A, F>]
>;
}
async authorize<M>(
ctx: C,
object: M,
action: FindAction<C, P, M>
): Promise<boolean> {
const policy = this.policies.find(
(p): p is PunditPolicy<C, M, typeof action> => p.handlesModel(object)
);
if (!policy) {
throw new Error(`No policy found for model ${object}`);
}
return await policy.authorize(ctx, object, action);
}
async filter<M>(
context: C,
cons: new (...args: any[]) => M
): Promise<FindFilterType<C, P, M> | typeof punditMatchNothing> {
const policy = this.policies.find(
(p): p is PunditPolicy<C, M, any, FindFilterType<C, P, M>> => {
return p.handlesModelConstructor(cons);
}
);
if (!policy) {
throw new Error(`No policy found for model constructor ${cons}`);
}
return await Promise.resolve(policy.filter(context));
}
}