@diister/quick-permission 0.9.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +747 -0
  3. package/aggregation.ts +114 -0
  4. package/core/filtering.ts +70 -0
  5. package/core/matching.ts +82 -0
  6. package/core/merging.ts +143 -0
  7. package/dist/aggregation.d.ts +62 -0
  8. package/dist/aggregation.d.ts.map +1 -0
  9. package/dist/aggregation.js +97 -0
  10. package/dist/aggregation.js.map +1 -0
  11. package/dist/core/filtering.d.ts +35 -0
  12. package/dist/core/filtering.d.ts.map +1 -0
  13. package/dist/core/filtering.js +62 -0
  14. package/dist/core/filtering.js.map +1 -0
  15. package/dist/core/matching.d.ts +31 -0
  16. package/dist/core/matching.d.ts.map +1 -0
  17. package/dist/core/matching.js +75 -0
  18. package/dist/core/matching.js.map +1 -0
  19. package/dist/core/merging.d.ts +29 -0
  20. package/dist/core/merging.d.ts.map +1 -0
  21. package/dist/core/merging.js +124 -0
  22. package/dist/core/merging.js.map +1 -0
  23. package/dist/indirect-aggregation.d.ts +41 -0
  24. package/dist/indirect-aggregation.d.ts.map +1 -0
  25. package/dist/indirect-aggregation.js +185 -0
  26. package/dist/indirect-aggregation.js.map +1 -0
  27. package/dist/indirect-resource.d.ts +126 -0
  28. package/dist/indirect-resource.d.ts.map +1 -0
  29. package/dist/indirect-resource.js +109 -0
  30. package/dist/indirect-resource.js.map +1 -0
  31. package/dist/mod.d.ts +25 -0
  32. package/dist/mod.d.ts.map +1 -0
  33. package/dist/mod.js +25 -0
  34. package/dist/mod.js.map +1 -0
  35. package/dist/mongo-query.d.ts +38 -0
  36. package/dist/mongo-query.d.ts.map +1 -0
  37. package/dist/mongo-query.js +88 -0
  38. package/dist/mongo-query.js.map +1 -0
  39. package/dist/permission.d.ts +57 -0
  40. package/dist/permission.d.ts.map +1 -0
  41. package/dist/permission.js +60 -0
  42. package/dist/permission.js.map +1 -0
  43. package/dist/resource.d.ts +48 -0
  44. package/dist/resource.d.ts.map +1 -0
  45. package/dist/resource.js +298 -0
  46. package/dist/resource.js.map +1 -0
  47. package/dist/rules.d.ts +106 -0
  48. package/dist/rules.d.ts.map +1 -0
  49. package/dist/rules.js +183 -0
  50. package/dist/rules.js.map +1 -0
  51. package/dist/sift/core.d.ts +104 -0
  52. package/dist/sift/core.d.ts.map +1 -0
  53. package/dist/sift/core.js +248 -0
  54. package/dist/sift/core.js.map +1 -0
  55. package/dist/sift/index.d.ts +10 -0
  56. package/dist/sift/index.d.ts.map +1 -0
  57. package/dist/sift/index.js +18 -0
  58. package/dist/sift/index.js.map +1 -0
  59. package/dist/sift/operations.d.ts +87 -0
  60. package/dist/sift/operations.d.ts.map +1 -0
  61. package/dist/sift/operations.js +257 -0
  62. package/dist/sift/operations.js.map +1 -0
  63. package/dist/sift/utils.d.ts +12 -0
  64. package/dist/sift/utils.d.ts.map +1 -0
  65. package/dist/sift/utils.js +80 -0
  66. package/dist/sift/utils.js.map +1 -0
  67. package/dist/system.d.ts +113 -0
  68. package/dist/system.d.ts.map +1 -0
  69. package/dist/system.js +712 -0
  70. package/dist/system.js.map +1 -0
  71. package/dist/target.d.ts +18 -0
  72. package/dist/target.d.ts.map +1 -0
  73. package/dist/target.js +41 -0
  74. package/dist/target.js.map +1 -0
  75. package/dist/types.d.ts +345 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +10 -0
  78. package/dist/types.js.map +1 -0
  79. package/indirect-aggregation.ts +216 -0
  80. package/indirect-resource.ts +205 -0
  81. package/mod.ts +81 -0
  82. package/mongo-query.ts +94 -0
  83. package/package.json +58 -0
  84. package/permission.ts +88 -0
  85. package/resource.ts +352 -0
  86. package/rules.ts +241 -0
  87. package/sift/MIT-LICENSE.txt +20 -0
  88. package/sift/core.ts +551 -0
  89. package/sift/index.ts +62 -0
  90. package/sift/operations.ts +449 -0
  91. package/sift/utils.ts +96 -0
  92. package/system.ts +974 -0
  93. package/target.ts +84 -0
  94. package/types.ts +408 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * `Resource` : source de donnée fetchable, dédupliquée par contexte.
3
+ *
4
+ * Une Resource est conçue pour être déclarée une fois dans son domaine
5
+ * (`userOf`, `expositionInfoOf`, etc.) et réutilisée entre permissions.
6
+ * Le moteur dédup les fetches au sein d'un context via `(id, dedupKey)`.
7
+ *
8
+ * Les méthodes `match`/`filter`/`includes`/`require*` sont du sucre :
9
+ * elles produisent des Rule via `defineRule`, avec la resource comme need.
10
+ *
11
+ * ─── Capability-mode contract ────────────────────────────────────────────
12
+ * A request target containing a wildcard (`"*"` or `"xxx:*"`) is a capability
13
+ * query — "can the subject act on ANY resource of this shape?". The engine
14
+ * skips resource fetches in this mode (no concrete resource to fetch). Each
15
+ * rule must declare what it does when its data is unavailable:
16
+ *
17
+ * match : silent-pass (cannot verify spec against absent data),
18
+ * but exposes the spec as a constraint for DB pushdown.
19
+ * filter : silent-pass — no projection happens; `output.data`
20
+ * stays undefined.
21
+ * includes : deny — membership cannot be checked without the resource.
22
+ * requireTruthy : deny — no value to test for truthiness.
23
+ * requireOwner : deny — no owner field to compare.
24
+ * requireMembership: deny — no membership list to scan.
25
+ * requireCustom : deny — predicate cannot run without `data`.
26
+ *
27
+ * Pure rules (no `needs`) like `matchPath` and `requireSelf` are unaffected:
28
+ * they read `ctx.target` / `ctx.subject` only.
29
+ */
30
+ import { deepEqual, defineRule } from "./rules.js";
31
+ import { evaluateSpec, validateSpec } from "./mongo-query.js";
32
+ class ResourceImpl {
33
+ // Spelled out rather than declared as constructor parameter properties:
34
+ // those are the one TypeScript-only construct Node's type stripping cannot
35
+ // erase, so they made this file unloadable as raw `.ts` there.
36
+ id;
37
+ fetcher;
38
+ activator;
39
+ dedupKey;
40
+ constructor(id, fetcher, activator, dedupKey) {
41
+ this.id = id;
42
+ this.fetcher = fetcher;
43
+ this.activator = activator;
44
+ this.dedupKey = dedupKey;
45
+ }
46
+ isActiveFor(grant) {
47
+ return this.activator ? this.activator(grant) : true;
48
+ }
49
+ computeDedupKey(ctx) {
50
+ if (this.dedupKey)
51
+ return `${this.id}::${this.dedupKey(ctx)}`;
52
+ // Default safe : hash de tous les inputs (subject + target + grant.with).
53
+ // Jamais de fausse dedup, mais peut être sous-optimal — d'où l'incitation
54
+ // à fournir un dedupKey explicite quand on connaît les params pertinents.
55
+ return `${this.id}::${ctx.subject.id}::${JSON.stringify(ctx.target)}::${JSON.stringify(ctx.grant.with ?? {})}`;
56
+ }
57
+ cacheKeyForTarget(target) {
58
+ // Build a synthetic FetchCtx (target-only). For `preseed()`, the
59
+ // caller knows the target shape but neither subject nor grant. We
60
+ // assume `dedupKey` only reads `target` (the recommended pattern)
61
+ // — if a resource's dedupKey reaches into subject/grant, `preseed`
62
+ // will produce a key that won't match runtime `computeDedupKey`,
63
+ // and the cache will silently miss. Document this limit.
64
+ const syntheticCtx = {
65
+ subject: { id: "__preseed__" },
66
+ target,
67
+ grant: { key: "__preseed__" },
68
+ capability: false,
69
+ };
70
+ if (this.dedupKey)
71
+ return `${this.id}::${this.dedupKey(syntheticCtx)}`;
72
+ return `${this.id}::${syntheticCtx.subject.id}::${JSON.stringify(target)}::${JSON.stringify({})}`;
73
+ }
74
+ // ─── Méthodes de sucre — toutes produites via defineRule ─────────────
75
+ match(extractor) {
76
+ const id = this.id;
77
+ const project = extractor ?? ((d) => d);
78
+ return defineRule({
79
+ kind: "match",
80
+ needs: [this],
81
+ check: ([data], _payload, ctx) => {
82
+ const spec = ctx.grant.with?.[id];
83
+ if (spec === undefined)
84
+ return { ok: true };
85
+ if (typeof spec === "object" && spec !== null) {
86
+ validateSpec(spec);
87
+ }
88
+ // Cap-mode without fetched data: silent-pass + expose spec as
89
+ // constraint (legacy behaviour — pushdown to DB for resources
90
+ // that ARE the listed collection).
91
+ //
92
+ // When `data` is present in cap-mode it means the resource was
93
+ // fetched anyway (because `Resource.targetSegment` was concrete
94
+ // — see system.ts collection logic). In that case we evaluate
95
+ // the spec like in concrete mode and DON'T emit a constraint :
96
+ // the check is fully resolved and the (potentially inappropriate)
97
+ // constraint would pollute the listed collection's `find()`.
98
+ if (ctx.capability && data === undefined) {
99
+ if (typeof spec === "object" &&
100
+ spec !== null &&
101
+ !Array.isArray(spec)) {
102
+ return { ok: true, constraint: spec };
103
+ }
104
+ return { ok: true };
105
+ }
106
+ const actual = project(data);
107
+ if (spec === null || typeof spec !== "object" || Array.isArray(spec)) {
108
+ return deepEqual(spec, actual)
109
+ ? { ok: true }
110
+ : { ok: false, reason: `match[${id}] mismatch` };
111
+ }
112
+ const passes = evaluateSpec(spec, actual);
113
+ if (!passes) {
114
+ return { ok: false, reason: `match[${id}] mismatch` };
115
+ }
116
+ // Concrete mode (or cap-mode w/ fetched data): the rule resolved
117
+ // fully via `evaluateSpec`. Emitting `constraint: spec` here is
118
+ // safe ONLY when the resource id matches the listed collection
119
+ // — for foreign resources it would push a wrong filter. Conservative
120
+ // choice : emit only in legacy concrete mode (preserves existing
121
+ // listWithPermission semantics for self-referencing resources like
122
+ // `users.read` + `userOf.match`).
123
+ if (!ctx.capability) {
124
+ return { ok: true, constraint: spec };
125
+ }
126
+ return { ok: true };
127
+ },
128
+ });
129
+ }
130
+ filter(extractor) {
131
+ const project = extractor ?? ((d) => d);
132
+ return defineRule({
133
+ kind: "filter",
134
+ needs: [this],
135
+ check: ([data], _payload, ctx) => {
136
+ if (ctx.capability)
137
+ return { ok: true };
138
+ const sub = project(data);
139
+ const filter = ctx.grant.filter;
140
+ // No filter on this grant → expose `null` spec so the system can
141
+ // collapse the cross-grant union to "all fields".
142
+ if (!filter ||
143
+ sub === null ||
144
+ typeof sub !== "object" ||
145
+ Array.isArray(sub)) {
146
+ return { ok: true, data: sub, filter: { source: sub, spec: null } };
147
+ }
148
+ const out = {};
149
+ for (const [k, v] of Object.entries(filter)) {
150
+ if (v === true && k in sub) {
151
+ out[k] = sub[k];
152
+ }
153
+ }
154
+ return {
155
+ ok: true,
156
+ data: out,
157
+ filter: { source: sub, spec: filter },
158
+ };
159
+ },
160
+ });
161
+ }
162
+ includes(grantField, extractor) {
163
+ return defineRule({
164
+ kind: "includes",
165
+ needs: [this],
166
+ // Lazy : skip si pas de spec dans le grant.
167
+ activeWhen: (grant) => grant.with?.[grantField] !== undefined,
168
+ describe: () => ({ grantField }),
169
+ check: ([data], _payload, ctx) => {
170
+ // Capability : ressource non fetchée, on ne peut pas vérifier
171
+ // l'inclusion concrète → deny.
172
+ if (ctx.capability) {
173
+ return {
174
+ ok: false,
175
+ reason: `includes[${grantField}] cannot be verified in capability query`,
176
+ };
177
+ }
178
+ const required = ctx.grant.with[grantField];
179
+ const list = extractor(data);
180
+ if (Array.isArray(required)) {
181
+ return required.some((r) => list.includes(r))
182
+ ? { ok: true }
183
+ : {
184
+ ok: false,
185
+ reason: `includes[${grantField}] none of ${JSON.stringify(required)} present`,
186
+ };
187
+ }
188
+ return list.includes(required)
189
+ ? { ok: true }
190
+ : {
191
+ ok: false,
192
+ reason: `includes[${grantField}] ${String(required)} absent`,
193
+ };
194
+ },
195
+ });
196
+ }
197
+ requireTruthy() {
198
+ const id = this.id;
199
+ return defineRule({
200
+ kind: "require-truthy",
201
+ needs: [this],
202
+ check: ([data], _payload, ctx) => {
203
+ if (ctx.capability) {
204
+ return {
205
+ ok: false,
206
+ reason: `require-truthy[${id}] cannot be verified in capability query`,
207
+ };
208
+ }
209
+ return data
210
+ ? { ok: true }
211
+ : { ok: false, reason: `require-truthy[${id}] is falsy` };
212
+ },
213
+ });
214
+ }
215
+ requireOwner(getter, opts) {
216
+ const id = this.id;
217
+ return defineRule({
218
+ kind: "require-owner",
219
+ needs: [this],
220
+ flag: opts.flag,
221
+ check: ([data], _payload, ctx) => {
222
+ if (ctx.capability) {
223
+ return {
224
+ ok: false,
225
+ reason: `require-owner[${id}] cannot be verified in capability query`,
226
+ };
227
+ }
228
+ return getter(data) === ctx.subject.id
229
+ ? { ok: true }
230
+ : {
231
+ ok: false,
232
+ reason: `not owner of ${id} (flag: ${opts.flag})`,
233
+ };
234
+ },
235
+ });
236
+ }
237
+ requireMembership(getter, opts) {
238
+ const id = this.id;
239
+ return defineRule({
240
+ kind: "require-membership",
241
+ needs: [this],
242
+ flag: opts.flag,
243
+ check: ([data], _payload, ctx) => {
244
+ if (ctx.capability) {
245
+ return {
246
+ ok: false,
247
+ reason: `require-membership[${id}] cannot be verified in capability query`,
248
+ };
249
+ }
250
+ const list = getter(data);
251
+ return list.includes(ctx.subject.id)
252
+ ? { ok: true }
253
+ : {
254
+ ok: false,
255
+ reason: `subject not member of ${id} (flag: ${opts.flag})`,
256
+ };
257
+ },
258
+ });
259
+ }
260
+ requireCustom(predicate, opts) {
261
+ const id = this.id;
262
+ return defineRule({
263
+ kind: "require-custom",
264
+ needs: [this],
265
+ flag: opts.flag,
266
+ describe: opts.descriptor ? () => opts.descriptor : undefined,
267
+ check: ([data], _payload, ctx) => {
268
+ if (ctx.capability) {
269
+ return {
270
+ ok: false,
271
+ reason: `require-custom[${id}] cannot be verified in capability query`,
272
+ };
273
+ }
274
+ return predicate(data, ctx)
275
+ ? { ok: true }
276
+ : {
277
+ ok: false,
278
+ reason: `require-custom[${id}] denied (flag: ${opts.flag})`,
279
+ };
280
+ },
281
+ });
282
+ }
283
+ }
284
+ /**
285
+ * Crée une Resource. À utiliser une fois par entité de domaine, partagée
286
+ * entre toutes les permissions qui la consomment.
287
+ *
288
+ * @example
289
+ * const userOf = resource({
290
+ * id: "user",
291
+ * fetch: ({ target }) => usersRepo.findById(target[0] as string),
292
+ * dedupKey: ({ target }) => target[0] as string,
293
+ * });
294
+ */
295
+ export function resource(opts) {
296
+ return new ResourceImpl(opts.id, opts.fetch, opts.activeWhen, opts.dedupKey);
297
+ }
298
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../resource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * `defineRule` — primitive unique de fabrication de rule.
3
+ *
4
+ * Toutes les rules de la lib (match, filter, includes, requireXxx) sont du
5
+ * sucre par-dessus cette fonction. Les devs en aval l'utilisent aussi pour
6
+ * écrire leurs rules métier réutilisables (cf. RFC §"Le rôle des devs").
7
+ */
8
+ import type { FetchCtx, Grant, Resource, ResourcesData, Rule, RuleResult } from "./types.ts";
9
+ export type DefineRuleOpts<RS extends readonly Resource<unknown>[], P> = {
10
+ /** Identifiant de famille de rule (matrix UI). */
11
+ readonly kind: string;
12
+ /**
13
+ * Resources à fetcher avant le check. L'ordre détermine l'ordre du tuple
14
+ * passé à `check`. Tableau vide = rule pure (ex: requireSelf).
15
+ */
16
+ readonly needs?: RS;
17
+ /**
18
+ * Sucre opt-in : la rule est active ssi `grant.flags?.[flag] === true`.
19
+ * Mutuellement exclusif avec `activeWhen`.
20
+ */
21
+ readonly flag?: string;
22
+ /**
23
+ * Escape hatch d'activation. Si retourne false, la rule est skip et ses
24
+ * needs ne sont pas fetchées.
25
+ */
26
+ readonly activeWhen?: (grant: Grant) => boolean;
27
+ /**
28
+ * Métadonnées additionnelles fusionnées dans le `descriptor` (matrix UI).
29
+ * Ne doit pas inclure les champs gérés automatiquement (kind/source/sources/flag).
30
+ */
31
+ readonly describe?: () => Readonly<Record<string, unknown>>;
32
+ /**
33
+ * Logique d'évaluation. `data` est un tuple aligné sur `needs`. `payload`
34
+ * est un alias de `ctx.grant.payload` casté au type générique P.
35
+ * Retourne soit un boolean (true = ok, false = deny avec raison auto),
36
+ * soit un RuleResult complet pour propager `data` ou un message custom.
37
+ */
38
+ readonly check: (data: ResourcesData<RS>, payload: P, ctx: FetchCtx) => boolean | RuleResult;
39
+ };
40
+ /**
41
+ * Fabrique une rule à partir d'une définition déclarative.
42
+ */
43
+ export declare function defineRule<const RS extends readonly Resource<unknown>[], P = unknown>(opts: DefineRuleOpts<RS, P>): Rule;
44
+ /**
45
+ * Rule pure (pas de needs) : compare un segment du target à `subject.id`.
46
+ * Opt-in via flag.
47
+ *
48
+ * @example
49
+ * "users.update": permission({...}).rules([
50
+ * userOf.match(),
51
+ * requireSelf({ flag: "selfOnly" }),
52
+ * ]),
53
+ *
54
+ * // Grant d'admin (no flag) → silent pass
55
+ * { key: "users.update", target: ["user:*"] }
56
+ * // Grant self-only (flag activé) → check target[0] === subject.id
57
+ * { key: "users.update", target: ["user:*"], flags: { selfOnly: true } }
58
+ */
59
+ /**
60
+ * Translates a grant's `target[segment]` to a Mongo `{[field]: value}`
61
+ * constraint, exposed via the rule's `constraint` for cross-grant OR
62
+ * aggregation and DB pushdown. Wildcard segments (`*` or `xxx:*`) emit
63
+ * no constraint (= no restriction).
64
+ *
65
+ * Common case for resources stored in MongoDB by `_id`:
66
+ *
67
+ * matchPath() // {_id: target[0]}
68
+ * matchPath({ field: "userId" }) // {userId: target[0]}
69
+ * matchPath({ segment: 1 }) // {_id: target[1]} (e.g., target.path)
70
+ */
71
+ export declare function matchPath(opts?: {
72
+ readonly field?: string;
73
+ readonly segment?: number;
74
+ }): Rule;
75
+ /**
76
+ * Validates `ctx.input` against `grant.inputWith` (Mongo spec). Distinct
77
+ * slot from `grant.with` so payload-shaped specs don't collide with
78
+ * resource-shaped specs in the same grant.
79
+ *
80
+ * Per-grant:
81
+ * - no `inputWith` → ok (unconditional grant)
82
+ * - `inputWith` + input → `evaluateSpec(inputWith, input)`
83
+ * - `inputWith` + no input → deny (cannot verify)
84
+ *
85
+ * Cross-grant OR aggregation: a single unconditional grant lets every check
86
+ * through (broadest wins); a UI capability check with no input only passes
87
+ * if at least one unconditional grant exists.
88
+ *
89
+ * @example
90
+ * "invitations.create": permission({
91
+ * target: target.path("exposition", "expo_organization"),
92
+ * }).rules([inputMatch()])
93
+ *
94
+ * { key: "invitations.create",
95
+ * target: [expoId, orgId],
96
+ * inputWith: { flowId: "flow:abc" } }
97
+ *
98
+ * await ctx.can("invitations.create", [expoId, orgId], { input: body })
99
+ */
100
+ export declare function inputMatch(): Rule;
101
+ export declare function requireSelf(opts: {
102
+ readonly flag: string;
103
+ readonly segment?: number;
104
+ }): Rule;
105
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
106
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../rules.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,aAAa,EACb,IAAI,EACJ,UAAU,EACX,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,cAAc,CAAC,EAAE,SAAS,SAAS,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,IAAI;IACvE,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,CACd,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC,EACvB,OAAO,EAAE,CAAC,EACV,GAAG,EAAE,QAAQ,KACV,OAAO,GAAG,UAAU,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,CAAC,EAAE,SAAS,SAAS,QAAQ,CAAC,OAAO,CAAC,EAAE,EAC7C,CAAC,GAAG,OAAO,EACX,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CA4CnC;AAED;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,IAAI,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAChE,IAAI,CAgBN;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAkBjC;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,IAAI,CAeP;AAMD,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAkBzD"}
package/dist/rules.js ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * `defineRule` — primitive unique de fabrication de rule.
3
+ *
4
+ * Toutes les rules de la lib (match, filter, includes, requireXxx) sont du
5
+ * sucre par-dessus cette fonction. Les devs en aval l'utilisent aussi pour
6
+ * écrire leurs rules métier réutilisables (cf. RFC §"Le rôle des devs").
7
+ */
8
+ import { evaluateSpec, validateSpec } from "./mongo-query.js";
9
+ /**
10
+ * Fabrique une rule à partir d'une définition déclarative.
11
+ */
12
+ export function defineRule(opts) {
13
+ if (opts.flag !== undefined && opts.activeWhen !== undefined) {
14
+ throw new Error(`defineRule(${opts.kind}): cannot specify both 'flag' and 'activeWhen'`);
15
+ }
16
+ const needs = (opts.needs ?? []);
17
+ const flag = opts.flag;
18
+ const activeWhen = flag !== undefined
19
+ ? (grant) => grant.flags?.[flag] === true
20
+ : opts.activeWhen;
21
+ const descriptor = { kind: opts.kind };
22
+ if (needs.length === 1)
23
+ descriptor.source = needs[0].id;
24
+ if (needs.length > 1)
25
+ descriptor.sources = needs.map((n) => n.id);
26
+ if (flag !== undefined)
27
+ descriptor.flag = flag;
28
+ if (opts.describe) {
29
+ const extra = opts.describe();
30
+ for (const [k, v] of Object.entries(extra)) {
31
+ // Les champs auto ne peuvent pas être écrasés.
32
+ if (k === "kind" || k === "source" || k === "sources" || k === "flag") {
33
+ continue;
34
+ }
35
+ descriptor[k] = v;
36
+ }
37
+ }
38
+ return {
39
+ descriptor: descriptor,
40
+ needs,
41
+ activeWhen,
42
+ check: (data, ctx) => {
43
+ const payload = ctx.grant.payload;
44
+ const result = opts.check(data, payload, ctx);
45
+ if (typeof result === "boolean") {
46
+ return result
47
+ ? { ok: true }
48
+ : { ok: false, reason: `${opts.kind} denied` };
49
+ }
50
+ return result;
51
+ },
52
+ };
53
+ }
54
+ /**
55
+ * Rule pure (pas de needs) : compare un segment du target à `subject.id`.
56
+ * Opt-in via flag.
57
+ *
58
+ * @example
59
+ * "users.update": permission({...}).rules([
60
+ * userOf.match(),
61
+ * requireSelf({ flag: "selfOnly" }),
62
+ * ]),
63
+ *
64
+ * // Grant d'admin (no flag) → silent pass
65
+ * { key: "users.update", target: ["user:*"] }
66
+ * // Grant self-only (flag activé) → check target[0] === subject.id
67
+ * { key: "users.update", target: ["user:*"], flags: { selfOnly: true } }
68
+ */
69
+ /**
70
+ * Translates a grant's `target[segment]` to a Mongo `{[field]: value}`
71
+ * constraint, exposed via the rule's `constraint` for cross-grant OR
72
+ * aggregation and DB pushdown. Wildcard segments (`*` or `xxx:*`) emit
73
+ * no constraint (= no restriction).
74
+ *
75
+ * Common case for resources stored in MongoDB by `_id`:
76
+ *
77
+ * matchPath() // {_id: target[0]}
78
+ * matchPath({ field: "userId" }) // {userId: target[0]}
79
+ * matchPath({ segment: 1 }) // {_id: target[1]} (e.g., target.path)
80
+ */
81
+ export function matchPath(opts = {}) {
82
+ const field = opts.field ?? "_id";
83
+ const segment = opts.segment ?? 0;
84
+ return defineRule({
85
+ kind: "match-path",
86
+ needs: [],
87
+ describe: () => ({ field, segment }),
88
+ check: (_data, _payload, ctx) => {
89
+ const target = ctx.grant.target;
90
+ if (!target || !Array.isArray(target))
91
+ return { ok: true };
92
+ const value = target[segment];
93
+ if (typeof value !== "string")
94
+ return { ok: true };
95
+ if (value === "*" || value.endsWith("*"))
96
+ return { ok: true };
97
+ return { ok: true, constraint: { [field]: value } };
98
+ },
99
+ });
100
+ }
101
+ /**
102
+ * Validates `ctx.input` against `grant.inputWith` (Mongo spec). Distinct
103
+ * slot from `grant.with` so payload-shaped specs don't collide with
104
+ * resource-shaped specs in the same grant.
105
+ *
106
+ * Per-grant:
107
+ * - no `inputWith` → ok (unconditional grant)
108
+ * - `inputWith` + input → `evaluateSpec(inputWith, input)`
109
+ * - `inputWith` + no input → deny (cannot verify)
110
+ *
111
+ * Cross-grant OR aggregation: a single unconditional grant lets every check
112
+ * through (broadest wins); a UI capability check with no input only passes
113
+ * if at least one unconditional grant exists.
114
+ *
115
+ * @example
116
+ * "invitations.create": permission({
117
+ * target: target.path("exposition", "expo_organization"),
118
+ * }).rules([inputMatch()])
119
+ *
120
+ * { key: "invitations.create",
121
+ * target: [expoId, orgId],
122
+ * inputWith: { flowId: "flow:abc" } }
123
+ *
124
+ * await ctx.can("invitations.create", [expoId, orgId], { input: body })
125
+ */
126
+ export function inputMatch() {
127
+ return defineRule({
128
+ kind: "input-match",
129
+ needs: [],
130
+ check: (_data, _payload, ctx) => {
131
+ const spec = ctx.grant.inputWith;
132
+ if (spec === undefined || Object.keys(spec).length === 0) {
133
+ return { ok: true };
134
+ }
135
+ if (ctx.input === undefined) {
136
+ return { ok: false, reason: "input-match: input required" };
137
+ }
138
+ validateSpec(spec);
139
+ return evaluateSpec(spec, ctx.input)
140
+ ? { ok: true }
141
+ : { ok: false, reason: "input-match: mismatch" };
142
+ },
143
+ });
144
+ }
145
+ export function requireSelf(opts) {
146
+ const segment = opts.segment ?? 0;
147
+ return defineRule({
148
+ kind: "require-self",
149
+ needs: [],
150
+ flag: opts.flag,
151
+ describe: () => ({ segment }),
152
+ check: (_data, _payload, ctx) => ctx.target[segment] === ctx.subject.id
153
+ ? { ok: true }
154
+ : {
155
+ ok: false,
156
+ reason: `target[${segment}] is not self (flag: ${opts.flag})`,
157
+ },
158
+ });
159
+ }
160
+ // ─────────────────────────────────────────────────────────────────────────
161
+ // Helpers internes (utilisés par les méthodes resource dans resource.ts)
162
+ // ─────────────────────────────────────────────────────────────────────────
163
+ export function deepEqual(a, b) {
164
+ if (a === b)
165
+ return true;
166
+ if (typeof a !== typeof b)
167
+ return false;
168
+ if (a === null || b === null)
169
+ return false;
170
+ if (typeof a !== "object")
171
+ return false;
172
+ if (Array.isArray(a) !== Array.isArray(b))
173
+ return false;
174
+ if (Array.isArray(a) && Array.isArray(b)) {
175
+ return a.length === b.length && a.every((x, i) => deepEqual(x, b[i]));
176
+ }
177
+ const aKeys = Object.keys(a);
178
+ const bKeys = Object.keys(b);
179
+ if (aKeys.length !== bKeys.length)
180
+ return false;
181
+ return aKeys.every((k) => deepEqual(a[k], b[k]));
182
+ }
183
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../rules.ts"],"names":[],"mappings":"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"}