@aventara/access 0.0.0-stage → 1.0.0-rc.1

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 (248) hide show
  1. package/CHANGELOG.md +877 -0
  2. package/LICENSE +91 -0
  3. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  4. package/README.md +2536 -2
  5. package/dist/access/access-options.d.ts +74 -0
  6. package/dist/access/access-options.js +5 -0
  7. package/dist/access/access.error.d.ts +35 -0
  8. package/dist/access/access.error.js +11 -0
  9. package/dist/access/access.factory.d.ts +275 -0
  10. package/dist/access/access.factory.js +440 -0
  11. package/dist/access/better-auth.hardening.d.ts +120 -0
  12. package/dist/access/better-auth.hardening.js +420 -0
  13. package/dist/access/better-auth.loader.d.ts +67 -0
  14. package/dist/access/better-auth.loader.js +65 -0
  15. package/dist/access/better-auth.range.d.ts +9 -0
  16. package/dist/access/better-auth.range.js +28 -0
  17. package/dist/access/better-auth.require.d.ts +90 -0
  18. package/dist/access/better-auth.require.js +6 -0
  19. package/dist/access/better-auth.require.node.d.ts +7 -0
  20. package/dist/access/better-auth.require.node.js +52 -0
  21. package/dist/access/claims.mapper.d.ts +79 -0
  22. package/dist/access/claims.mapper.js +152 -0
  23. package/dist/access/key-set.cache.d.ts +10 -0
  24. package/dist/access/key-set.cache.js +63 -0
  25. package/dist/access/policies-block.checker.d.ts +64 -0
  26. package/dist/access/policies-block.checker.js +138 -0
  27. package/dist/access/principal.subject.d.ts +22 -0
  28. package/dist/access/principal.subject.js +89 -0
  29. package/dist/access/profile.policy.d.ts +74 -0
  30. package/dist/access/profile.policy.js +110 -0
  31. package/dist/access/request.state.d.ts +89 -0
  32. package/dist/access/request.state.js +266 -0
  33. package/dist/access/session.hooks.d.ts +114 -0
  34. package/dist/access/session.hooks.js +258 -0
  35. package/dist/access/token.verifier.d.ts +35 -0
  36. package/dist/access/token.verifier.js +69 -0
  37. package/dist/access-schema.d.ts +48 -0
  38. package/dist/access-schema.js +5 -0
  39. package/dist/access-warning.emitter.d.ts +4 -0
  40. package/dist/access-warning.emitter.js +24 -0
  41. package/dist/access.builder.d.ts +340 -0
  42. package/dist/access.builder.js +75 -0
  43. package/dist/access.engine.d.ts +155 -0
  44. package/dist/access.engine.js +477 -0
  45. package/dist/audit/audit-event.d.ts +135 -0
  46. package/dist/audit/audit-event.js +8 -0
  47. package/dist/audit/audit.log.d.ts +122 -0
  48. package/dist/audit/audit.log.js +345 -0
  49. package/dist/audit/audit.schema.d.ts +143 -0
  50. package/dist/audit/audit.schema.js +50 -0
  51. package/dist/auth/index.d.ts +12 -0
  52. package/dist/auth/index.js +2 -0
  53. package/dist/aventara-where.translator.d.ts +19 -0
  54. package/dist/aventara-where.translator.js +87 -0
  55. package/dist/beacon/beacon.check.d.ts +59 -0
  56. package/dist/beacon/beacon.check.js +128 -0
  57. package/dist/beacon/beacon.schema.d.ts +41 -0
  58. package/dist/beacon/beacon.schema.js +48 -0
  59. package/dist/beacon/beacon.store.d.ts +29 -0
  60. package/dist/beacon/beacon.store.js +78 -0
  61. package/dist/better-auth/access.client-plugin.d.ts +75 -0
  62. package/dist/better-auth/access.client-plugin.js +133 -0
  63. package/dist/better-auth/access.plugin.d.ts +164 -0
  64. package/dist/better-auth/access.plugin.js +348 -0
  65. package/dist/better-auth/access.provider.d.ts +80 -0
  66. package/dist/better-auth/access.provider.js +100 -0
  67. package/dist/better-auth/index.d.ts +7 -0
  68. package/dist/better-auth/index.js +4 -0
  69. package/dist/better-auth/organization.bridge.d.ts +66 -0
  70. package/dist/better-auth/organization.bridge.js +144 -0
  71. package/dist/canonical-json.writer.d.ts +12 -0
  72. package/dist/canonical-json.writer.js +30 -0
  73. package/dist/cli/avaccess.bin.d.ts +2 -0
  74. package/dist/cli/avaccess.bin.js +26 -0
  75. package/dist/cli/avaccess.program.d.ts +15 -0
  76. package/dist/cli/avaccess.program.js +166 -0
  77. package/dist/cli/beacon-pins.file.d.ts +14 -0
  78. package/dist/cli/beacon-pins.file.js +62 -0
  79. package/dist/cli/beacon.loader.d.ts +20 -0
  80. package/dist/cli/beacon.loader.js +71 -0
  81. package/dist/cli/cli.context.d.ts +49 -0
  82. package/dist/cli/cli.context.js +66 -0
  83. package/dist/cli/diff.command.d.ts +2 -0
  84. package/dist/cli/diff.command.js +66 -0
  85. package/dist/cli/generate.command.d.ts +2 -0
  86. package/dist/cli/generate.command.js +82 -0
  87. package/dist/cli/keys.command.d.ts +2 -0
  88. package/dist/cli/keys.command.js +96 -0
  89. package/dist/cli/lock.file.d.ts +31 -0
  90. package/dist/cli/lock.file.js +181 -0
  91. package/dist/cli/module.loader.d.ts +13 -0
  92. package/dist/cli/module.loader.js +63 -0
  93. package/dist/cli/policies.diff.d.ts +16 -0
  94. package/dist/cli/policies.diff.js +101 -0
  95. package/dist/cli/pull.command.d.ts +2 -0
  96. package/dist/cli/pull.command.js +234 -0
  97. package/dist/cli/push.command.d.ts +2 -0
  98. package/dist/cli/push.command.js +59 -0
  99. package/dist/cli/registry.client.d.ts +63 -0
  100. package/dist/cli/registry.client.js +287 -0
  101. package/dist/cli/schema.command.d.ts +2 -0
  102. package/dist/cli/schema.command.js +60 -0
  103. package/dist/cli/serve.command.d.ts +6 -0
  104. package/dist/cli/serve.command.js +200 -0
  105. package/dist/cli/status.command.d.ts +2 -0
  106. package/dist/cli/status.command.js +31 -0
  107. package/dist/cli/translator.flag.d.ts +6 -0
  108. package/dist/cli/translator.flag.js +24 -0
  109. package/dist/cli/verify.command.d.ts +2 -0
  110. package/dist/cli/verify.command.js +40 -0
  111. package/dist/client/access-client.factory.d.ts +92 -0
  112. package/dist/client/access-client.factory.js +269 -0
  113. package/dist/client/access-verbs.schema.d.ts +66 -0
  114. package/dist/client/access-verbs.schema.js +159 -0
  115. package/dist/client/verb-rate.limiter.d.ts +116 -0
  116. package/dist/client/verb-rate.limiter.js +358 -0
  117. package/dist/foreign/foreign-facts.d.ts +30 -0
  118. package/dist/foreign/foreign-facts.js +75 -0
  119. package/dist/foreign/foreign.merger.d.ts +10 -0
  120. package/dist/foreign/foreign.merger.js +52 -0
  121. package/dist/foreign-references.check.d.ts +6 -0
  122. package/dist/foreign-references.check.js +131 -0
  123. package/dist/generator/client.emitter.d.ts +28 -0
  124. package/dist/generator/client.emitter.js +411 -0
  125. package/dist/generator/copied-modules.reader.d.ts +14 -0
  126. package/dist/generator/copied-modules.reader.js +80 -0
  127. package/dist/generator/namespace.facts.d.ts +28 -0
  128. package/dist/generator/namespace.facts.js +32 -0
  129. package/dist/generator/orm.translator.d.ts +48 -0
  130. package/dist/generator/orm.translator.js +29 -0
  131. package/dist/generator/prisma7/prisma-schema.reader.d.ts +9 -0
  132. package/dist/generator/prisma7/prisma-schema.reader.js +394 -0
  133. package/dist/generator/prisma7/prisma-where.translator.d.ts +73 -0
  134. package/dist/generator/prisma7/prisma-where.translator.js +138 -0
  135. package/dist/generator/prisma7/prisma7.translator.d.ts +2 -0
  136. package/dist/generator/prisma7/prisma7.translator.js +87 -0
  137. package/dist/generator/schema.emitter.d.ts +14 -0
  138. package/dist/generator/schema.emitter.js +23 -0
  139. package/dist/generator/tsconfig.reader.d.ts +44 -0
  140. package/dist/generator/tsconfig.reader.js +110 -0
  141. package/dist/generator/typescript-literal.writer.d.ts +5 -0
  142. package/dist/generator/typescript-literal.writer.js +42 -0
  143. package/dist/guardrails/guardrails-contract.d.ts +45 -0
  144. package/dist/guardrails/guardrails-contract.js +12 -0
  145. package/dist/guardrails/guardrails.builder.d.ts +48 -0
  146. package/dist/guardrails/guardrails.builder.js +32 -0
  147. package/dist/guardrails/guardrails.compiler.d.ts +10 -0
  148. package/dist/guardrails/guardrails.compiler.js +254 -0
  149. package/dist/guardrails/guardrails.composer.d.ts +7 -0
  150. package/dist/guardrails/guardrails.composer.js +95 -0
  151. package/dist/guardrails/guardrails.hash.d.ts +10 -0
  152. package/dist/guardrails/guardrails.hash.js +38 -0
  153. package/dist/index.d.ts +16 -0
  154. package/dist/index.js +6 -0
  155. package/dist/kiosk/kiosk-verbs.schema.d.ts +19 -0
  156. package/dist/kiosk/kiosk-verbs.schema.js +61 -0
  157. package/dist/kiosk/kiosk.plugin.d.ts +217 -0
  158. package/dist/kiosk/kiosk.plugin.js +877 -0
  159. package/dist/kiosk/kiosk.schema.d.ts +176 -0
  160. package/dist/kiosk/kiosk.schema.js +101 -0
  161. package/dist/kiosk/kiosk.store.d.ts +171 -0
  162. package/dist/kiosk/kiosk.store.js +478 -0
  163. package/dist/kiosk/lan-range.policy.d.ts +31 -0
  164. package/dist/kiosk/lan-range.policy.js +113 -0
  165. package/dist/kiosk/pin.policy.d.ts +9 -0
  166. package/dist/kiosk/pin.policy.js +143 -0
  167. package/dist/kiosk/session-use.marker.d.ts +4 -0
  168. package/dist/kiosk/session-use.marker.js +16 -0
  169. package/dist/lock/lock-contract.d.ts +38 -0
  170. package/dist/lock/lock-contract.js +67 -0
  171. package/dist/lock/lock.verifier.d.ts +27 -0
  172. package/dist/lock/lock.verifier.js +62 -0
  173. package/dist/mcp/agent-scope.gate.d.ts +13 -0
  174. package/dist/mcp/agent-scope.gate.js +75 -0
  175. package/dist/mcp/agent.grant.d.ts +40 -0
  176. package/dist/mcp/agent.grant.js +92 -0
  177. package/dist/mcp/mcp-options.d.ts +60 -0
  178. package/dist/mcp/mcp-options.js +72 -0
  179. package/dist/oauth/access-token.verifier.d.ts +66 -0
  180. package/dist/oauth/access-token.verifier.js +201 -0
  181. package/dist/oauth/oauth-provider.hardening.d.ts +62 -0
  182. package/dist/oauth/oauth-provider.hardening.js +261 -0
  183. package/dist/passwords/argon2id.hasher.d.ts +97 -0
  184. package/dist/passwords/argon2id.hasher.js +256 -0
  185. package/dist/passwords/node-rs-argon2.adapter.d.ts +25 -0
  186. package/dist/passwords/node-rs-argon2.adapter.js +42 -0
  187. package/dist/passwords/passwords.options.d.ts +79 -0
  188. package/dist/passwords/passwords.options.js +212 -0
  189. package/dist/policies-contract.d.ts +219 -0
  190. package/dist/policies-contract.js +8 -0
  191. package/dist/policies.compiler.d.ts +38 -0
  192. package/dist/policies.compiler.js +1433 -0
  193. package/dist/policies.decider.d.ts +103 -0
  194. package/dist/policies.decider.js +390 -0
  195. package/dist/policies.hash.d.ts +10 -0
  196. package/dist/policies.hash.js +54 -0
  197. package/dist/predicate.evaluator.d.ts +62 -0
  198. package/dist/predicate.evaluator.js +378 -0
  199. package/dist/random-secret.generator.d.ts +2 -0
  200. package/dist/random-secret.generator.js +15 -0
  201. package/dist/read-governed-unique.check.d.ts +3 -0
  202. package/dist/read-governed-unique.check.js +21 -0
  203. package/dist/reference-reads.check.d.ts +16 -0
  204. package/dist/reference-reads.check.js +158 -0
  205. package/dist/registry/access.registry.d.ts +173 -0
  206. package/dist/registry/access.registry.js +865 -0
  207. package/dist/registry/beacon.ledger.d.ts +30 -0
  208. package/dist/registry/beacon.ledger.js +93 -0
  209. package/dist/registry/ownership.check.d.ts +54 -0
  210. package/dist/registry/ownership.check.js +221 -0
  211. package/dist/registry/registry.endpoints.d.ts +50 -0
  212. package/dist/registry/registry.endpoints.js +193 -0
  213. package/dist/registry/registry.key.d.ts +7 -0
  214. package/dist/registry/registry.key.js +13 -0
  215. package/dist/registry/registry.options.d.ts +13 -0
  216. package/dist/registry/registry.options.js +28 -0
  217. package/dist/registry/registry.refusal.d.ts +6 -0
  218. package/dist/registry/registry.refusal.js +11 -0
  219. package/dist/registry/registry.schema.d.ts +238 -0
  220. package/dist/registry/registry.schema.js +127 -0
  221. package/dist/resources/auth-tables.catalog.d.ts +96 -0
  222. package/dist/resources/auth-tables.catalog.js +110 -0
  223. package/dist/resources/auth-tables.guard.d.ts +106 -0
  224. package/dist/resources/auth-tables.guard.js +462 -0
  225. package/dist/scope.resolver.d.ts +144 -0
  226. package/dist/scope.resolver.js +529 -0
  227. package/dist/sha256.digest.d.ts +2 -0
  228. package/dist/sha256.digest.js +81 -0
  229. package/dist/standalone/access.server.d.ts +41 -0
  230. package/dist/standalone/access.server.js +301 -0
  231. package/dist/standalone/index.d.ts +1 -0
  232. package/dist/standalone/index.js +1 -0
  233. package/package.json +102 -3
  234. package/skill/SKILL.md +170 -0
  235. package/skill/reference/callers.md +99 -0
  236. package/skill/reference/errors.md +89 -0
  237. package/skill/reference/recipes.md +281 -0
  238. package/skill/reference/rules.md +153 -0
  239. package/skill/reference/setup.md +334 -0
  240. package/src/aventara-where.translator.ts +116 -0
  241. package/src/beacon/beacon.check.ts +204 -0
  242. package/src/canonical-json.writer.ts +51 -0
  243. package/src/generator/prisma7/prisma-where.translator.ts +243 -0
  244. package/src/policies-contract.ts +215 -0
  245. package/src/policies.decider.ts +662 -0
  246. package/src/predicate.evaluator.ts +574 -0
  247. package/src/scope.resolver.ts +819 -0
  248. package/src/sha256.digest.ts +85 -0
@@ -0,0 +1,144 @@
1
+ import type { AccessAction, AccessComparison, AccessDenyRule, AccessGrantRule, AccessLiteral, AccessOrigin, AccessPolicies, AccessPredicate, AccessPrincipalCondition } from "./policies-contract.js";
2
+ /**
3
+ * A row condition after the principal fold. `and` is a condition the author wrote
4
+ * as one object (the translator may write it back as one); `all` is a conjunction
5
+ * the engine builds (grant parts, a scope's grants and denies), always written as
6
+ * `AND`.
7
+ */
8
+ export type Resolved = {
9
+ readonly k: "true";
10
+ } | {
11
+ readonly k: "false";
12
+ } | {
13
+ readonly k: "and" | "all" | "or";
14
+ readonly args: readonly Resolved[];
15
+ } | {
16
+ readonly k: "not";
17
+ readonly arg: Resolved;
18
+ } | {
19
+ readonly k: "cmp";
20
+ readonly field: string;
21
+ readonly cmp: AccessComparison;
22
+ readonly value: AccessLiteral | readonly AccessLiteral[];
23
+ } | {
24
+ readonly k: "rel";
25
+ readonly relation: string;
26
+ readonly target: string;
27
+ readonly where: Resolved;
28
+ } | {
29
+ readonly k: "some" | "every" | "none";
30
+ readonly relation: string;
31
+ readonly target: string;
32
+ readonly where: Resolved;
33
+ };
34
+ export declare const TRUE: Resolved;
35
+ export declare const FALSE: Resolved;
36
+ /** A conjunction the author wrote as one object. */
37
+ export declare const andOf: (args: readonly Resolved[]) => Resolved;
38
+ /** A conjunction the engine builds. */
39
+ export declare const allOf: (args: readonly Resolved[]) => Resolved;
40
+ export declare const orOf: (args: readonly Resolved[]) => Resolved;
41
+ export declare const notOf: (arg: Resolved) => Resolved;
42
+ type Schema = AccessPolicies["schema"];
43
+ type Facts = Schema[string];
44
+ /**
45
+ * A record's own member: `undefined` for a key the record only inherits
46
+ * (`constructor`, `toString`, `__proto__`), so no name in a file, a row or a principal
47
+ * ever reads `Object.prototype`. One signature per record of the file, not a generic
48
+ * one: each call would instantiate it, and the lookups are many.
49
+ */
50
+ export declare function ownOf(record: Schema | undefined, key: string): Facts | undefined;
51
+ export declare function ownOf(record: AccessPolicies["roles"] | undefined, key: string): AccessPolicies["roles"][string] | undefined;
52
+ export declare function ownOf(record: Facts["relations"] | undefined, key: string): Facts["relations"][string] | undefined;
53
+ export declare function ownOf(record: Facts["types"] | undefined, key: string): NonNullable<Facts["types"]>[string] | undefined;
54
+ export declare function ownOf(record: AccessPolicies["claims"] | undefined, key: string): AccessPolicies["claims"][string] | undefined;
55
+ export declare function ownOf(record: {
56
+ readonly [key: string]: readonly string[];
57
+ } | undefined, key: string): readonly string[] | undefined;
58
+ /**
59
+ * A Resource's governed fields for one access: each field `govern` lists, except one
60
+ * `governModes` governs for the other access only (`"write"` is read as any other
61
+ * field, `"read"` written as any other). A field with no mode is governed for both.
62
+ */
63
+ export declare const governedFor: (policies: Pick<AccessPolicies, "govern" | "governModes">, resource: string, access: "read" | "write") => readonly string[];
64
+ /** A plain object: made by `{}` or JSON, not an array, a `Date`, a `Map` or a class instance. */
65
+ export declare const isPlainObject: (value: unknown) => value is {
66
+ readonly [key: string]: unknown;
67
+ };
68
+ /** What every question states about who asks. */
69
+ export type PrincipalFacts = {
70
+ /** The principal: an object, or `null` (anonymous). */
71
+ readonly principal: {
72
+ readonly [key: string]: unknown;
73
+ } | null;
74
+ readonly origin: AccessOrigin;
75
+ };
76
+ /**
77
+ * Reads the principal off a question: any object, an instance of a class included
78
+ * (an entity), is a principal; an array, a function or a primitive is anonymous.
79
+ */
80
+ export declare const principalFactsOf: (principal: unknown, origin: AccessOrigin) => PrincipalFacts;
81
+ /**
82
+ * Folds a condition on the principal alone (claims, origin) to a boolean. A claim
83
+ * the principal lacks, or holds as another type, fails closed: a grant's global role
84
+ * is not held, and a deny's `if` holds, whatever value it compares with. Access mints
85
+ * every declared boolean claim, `false` when unset, so a token it issued lacks none;
86
+ * a foreign or stale token without `banned` is denied by `banned: true`. The
87
+ * anonymous caller is the one exception: a comparison with `true` never holds for it.
88
+ */
89
+ export declare const foldPrincipalCondition: (condition: AccessPrincipalCondition, facts: PrincipalFacts, rule: "grant" | "deny") => boolean;
90
+ /**
91
+ * How a rule reads a `me` value the principal lacks. A grant drops: it never
92
+ * matches, whatever `NOT` surrounds the value. A deny matches as a whole, whatever
93
+ * `not` or `NOT` surrounds the value. Both fail closed.
94
+ */
95
+ export type MissingMode = "drop-rule" | "match-rule";
96
+ /**
97
+ * Substitutes the principal into a row condition. A `me` value the principal lacks
98
+ * makes the whole rule `false` for a grant (`drop-rule`) and `true` for a deny
99
+ * (`match-rule`).
100
+ */
101
+ export declare const resolvePredicate: (policies: AccessPolicies, predicate: AccessPredicate, facts: PrincipalFacts, mode: MissingMode) => Resolved;
102
+ /**
103
+ * The grants and denies a question about `action` on `resource` reads. A grant on
104
+ * `"*"` reaches the file's own Resources only: never a foreign one, and never one
105
+ * its schema does not list. A deny on `"*"` reaches every Resource.
106
+ */
107
+ export declare const rulesFor: (policies: AccessPolicies, resource: string, action: AccessAction) => {
108
+ grants: AccessGrantRule[];
109
+ denies: AccessDenyRule[];
110
+ };
111
+ /** One grant, for one of its subjects, folded for the principal. */
112
+ export type FoldedGrant = {
113
+ readonly grant: AccessGrantRule;
114
+ readonly subject: string;
115
+ readonly condition: Resolved;
116
+ };
117
+ /**
118
+ * Each grant's condition per subject: its `where`, then the subject's (its row
119
+ * role, through `in`), then, with `after`, the `after` condition. A grant that reads
120
+ * a `me` value the principal lacks drops.
121
+ */
122
+ export declare const foldGrants: (policies: AccessPolicies, grants: readonly AccessGrantRule[], facts: PrincipalFacts, withAfter?: boolean) => FoldedGrant[];
123
+ /** One deny, folded for the principal. */
124
+ export type FoldedDeny = {
125
+ readonly deny: AccessDenyRule;
126
+ readonly condition: Resolved;
127
+ };
128
+ /**
129
+ * Each deny's row condition: `false` when its `if` does not hold for this principal,
130
+ * `true` when its `where` reads a `me` value the principal lacks.
131
+ */
132
+ export declare const foldDenies: (policies: AccessPolicies, denies: readonly AccessDenyRule[], facts: PrincipalFacts) => FoldedDeny[];
133
+ /** The rows a principal may `action`: OR of the grants, AND NOT each deny. */
134
+ export declare const scopeCondition: (grants: readonly FoldedGrant[], denies: readonly FoldedDeny[]) => Resolved;
135
+ /**
136
+ * A folded condition on `resource` in the form a database reads as `can` does.
137
+ * `can` reads a comparison on a NULL field as false, so its `NOT` keeps the row; SQL
138
+ * reads it as unknown, and its `NOT` drops the row. Each negated comparison on a
139
+ * nullable field (the schema entry's `nullable`) gets its own guard, `NOT (f = v) OR
140
+ * f IS NULL`, and a negated to-one step whose reference is nullable gets `OR fk IS
141
+ * NULL`. A condition with neither is returned as it is.
142
+ */
143
+ export declare const nullSafe: (policies: AccessPolicies, resource: string, node: Resolved) => Resolved;
144
+ export {};
@@ -0,0 +1,529 @@
1
+ // The principal fold: substitute the principal into every rule, fold what
2
+ // the principal alone decides (claims, origin, `signedIn`), expand roles, and keep only
3
+ // row conditions. Grants combine with OR, denies with AND NOT. What leaves this module
4
+ // holds literals only.
5
+ export const TRUE = { k: "true" };
6
+ export const FALSE = { k: "false" };
7
+ const junction = (k, args) => {
8
+ const kept = [];
9
+ for (const arg of args) {
10
+ if (arg.k === "false") {
11
+ return FALSE;
12
+ }
13
+ if (arg.k !== "true") {
14
+ kept.push(arg);
15
+ }
16
+ }
17
+ if (kept.length === 0) {
18
+ return TRUE;
19
+ }
20
+ return kept.length === 1 ? kept[0] : { k, args: kept };
21
+ };
22
+ /** A conjunction the author wrote as one object. */
23
+ export const andOf = (args) => junction("and", args);
24
+ /** A conjunction the engine builds. */
25
+ export const allOf = (args) => junction("all", args);
26
+ export const orOf = (args) => {
27
+ const kept = [];
28
+ for (const arg of args) {
29
+ if (arg.k === "true") {
30
+ return TRUE;
31
+ }
32
+ if (arg.k !== "false") {
33
+ kept.push(arg);
34
+ }
35
+ }
36
+ if (kept.length === 0) {
37
+ return FALSE;
38
+ }
39
+ return kept.length === 1 ? kept[0] : { k: "or", args: kept };
40
+ };
41
+ export const notOf = (arg) => arg.k === "true" ? FALSE : arg.k === "false" ? TRUE : { k: "not", arg };
42
+ export function ownOf(record, key) {
43
+ return record !== undefined && Object.hasOwn(record, key)
44
+ ? record[key]
45
+ : undefined;
46
+ }
47
+ /**
48
+ * A Resource's governed fields for one access: each field `govern` lists, except one
49
+ * `governModes` governs for the other access only (`"write"` is read as any other
50
+ * field, `"read"` written as any other). A field with no mode is governed for both.
51
+ */
52
+ export const governedFor = (policies, resource, access) => {
53
+ const fields = ownOf(policies.govern, resource);
54
+ const modes = policies.governModes !== undefined &&
55
+ Object.hasOwn(policies.governModes, resource)
56
+ ? policies.governModes[resource]
57
+ : undefined;
58
+ if (fields === undefined || modes === undefined) {
59
+ return fields ?? [];
60
+ }
61
+ const other = access === "read" ? "write" : "read";
62
+ return fields.filter((field) => !(Object.hasOwn(modes, field) && modes[field] === other));
63
+ };
64
+ /** A plain object: made by `{}` or JSON, not an array, a `Date`, a `Map` or a class instance. */
65
+ export const isPlainObject = (value) => {
66
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
67
+ return false;
68
+ }
69
+ const prototype = Object.getPrototypeOf(value);
70
+ return prototype === Object.prototype || prototype === null;
71
+ };
72
+ /**
73
+ * Reads the principal off a question: any object, an instance of a class included
74
+ * (an entity), is a principal; an array, a function or a primitive is anonymous.
75
+ */
76
+ export const principalFactsOf = (principal, origin) => ({
77
+ principal: typeof principal === "object" &&
78
+ principal !== null &&
79
+ !Array.isArray(principal)
80
+ ? principal
81
+ : null,
82
+ origin,
83
+ });
84
+ /** The value at `path`, through own members only; `undefined` where one is missing. */
85
+ const readPath = (from, path) => {
86
+ let value = from;
87
+ for (const key of path) {
88
+ if (typeof value !== "object" ||
89
+ value === null ||
90
+ !Object.hasOwn(value, key)) {
91
+ return undefined;
92
+ }
93
+ value = value[key];
94
+ }
95
+ return value;
96
+ };
97
+ /**
98
+ * A `me` value the principal does not have: anonymous, a missing or null property,
99
+ * an empty string (no identity is `""`), a number JSON cannot carry, or anything but
100
+ * a string, number or boolean.
101
+ */
102
+ class MissingPrincipalValue extends Error {
103
+ }
104
+ const literalOf = (operand, facts) => {
105
+ if (operand === null || typeof operand !== "object") {
106
+ return operand;
107
+ }
108
+ const value = facts.principal === null
109
+ ? undefined
110
+ : readPath(facts.principal, operand.principal);
111
+ if (value === "" ||
112
+ (typeof value === "number" && !Number.isFinite(value)) ||
113
+ (typeof value !== "string" &&
114
+ typeof value !== "number" &&
115
+ typeof value !== "boolean")) {
116
+ throw new MissingPrincipalValue();
117
+ }
118
+ return value;
119
+ };
120
+ /** A condition on the principal, three-valued: a claim it lacks is `"unknown"`. */
121
+ const principalTruth = (condition, facts) => {
122
+ switch (condition.op) {
123
+ case "and": {
124
+ let unknown = false;
125
+ for (const arg of condition.args) {
126
+ const truth = principalTruth(arg, facts);
127
+ if (truth === false) {
128
+ return false;
129
+ }
130
+ unknown ||= truth === "unknown";
131
+ }
132
+ return unknown ? "unknown" : true;
133
+ }
134
+ case "origin":
135
+ return facts.origin === condition.value;
136
+ case "cmp": {
137
+ if (facts.principal === null) {
138
+ // The anonymous caller has no token, so none can be stale: no flag is set on
139
+ // it (a ban does not close an `anyone` grant), and any other claim is unknown.
140
+ return condition.value === true ? false : "unknown";
141
+ }
142
+ const value = readPath(facts.principal, ["claims", ...condition.claim]);
143
+ if (value === undefined || value === null) {
144
+ return "unknown";
145
+ }
146
+ return typeof value === typeof condition.value
147
+ ? value === condition.value
148
+ : "unknown";
149
+ }
150
+ }
151
+ };
152
+ /**
153
+ * Folds a condition on the principal alone (claims, origin) to a boolean. A claim
154
+ * the principal lacks, or holds as another type, fails closed: a grant's global role
155
+ * is not held, and a deny's `if` holds, whatever value it compares with. Access mints
156
+ * every declared boolean claim, `false` when unset, so a token it issued lacks none;
157
+ * a foreign or stale token without `banned` is denied by `banned: true`. The
158
+ * anonymous caller is the one exception: a comparison with `true` never holds for it.
159
+ */
160
+ export const foldPrincipalCondition = (condition, facts, rule) => {
161
+ const truth = principalTruth(condition, facts);
162
+ return truth === "unknown" ? rule === "deny" : truth;
163
+ };
164
+ const resolveIn = (policies, predicate, facts, mode) => {
165
+ switch (predicate.op) {
166
+ case "true":
167
+ return TRUE;
168
+ case "false":
169
+ return FALSE;
170
+ case "and":
171
+ return andOf(predicate.args.map((arg) => resolveIn(policies, arg, facts, mode)));
172
+ case "or":
173
+ return orOf(predicate.args.map((arg) => resolveIn(policies, arg, facts, mode)));
174
+ case "not":
175
+ return notOf(resolveIn(policies, predicate.arg, facts, mode));
176
+ case "cmp": {
177
+ const value = Array.isArray(predicate.value)
178
+ ? predicate.value.map((item) => literalOf(item, facts))
179
+ : literalOf(predicate.value, facts);
180
+ return { k: "cmp", field: predicate.field, cmp: predicate.cmp, value };
181
+ }
182
+ case "rel": {
183
+ const where = resolveIn(policies, predicate.where, facts, mode);
184
+ return where.k === "false"
185
+ ? FALSE
186
+ : {
187
+ k: "rel",
188
+ relation: predicate.relation,
189
+ target: predicate.target,
190
+ where,
191
+ };
192
+ }
193
+ case "some":
194
+ case "every":
195
+ case "none": {
196
+ const where = resolveIn(policies, predicate.where, facts, mode);
197
+ if (predicate.op === "some" && where.k === "false") {
198
+ return FALSE;
199
+ }
200
+ if (predicate.op === "none" && where.k === "false") {
201
+ return TRUE;
202
+ }
203
+ if (predicate.op === "every" && where.k === "true") {
204
+ return TRUE;
205
+ }
206
+ return {
207
+ k: predicate.op,
208
+ relation: predicate.relation,
209
+ target: predicate.target,
210
+ where,
211
+ };
212
+ }
213
+ case "role": {
214
+ const role = ownOf(policies.roles, predicate.role);
215
+ if (role === undefined || !("on" in role)) {
216
+ return FALSE;
217
+ }
218
+ return resolveIn(policies, role.where, facts, mode);
219
+ }
220
+ }
221
+ };
222
+ /**
223
+ * Substitutes the principal into a row condition. A `me` value the principal lacks
224
+ * makes the whole rule `false` for a grant (`drop-rule`) and `true` for a deny
225
+ * (`match-rule`).
226
+ */
227
+ export const resolvePredicate = (policies, predicate, facts, mode) => {
228
+ try {
229
+ return resolveIn(policies, predicate, facts, mode);
230
+ }
231
+ catch (error) {
232
+ if (error instanceof MissingPrincipalValue) {
233
+ return mode === "drop-rule" ? FALSE : TRUE;
234
+ }
235
+ throw error;
236
+ }
237
+ };
238
+ /** Whether a rule names this action on this Resource. */
239
+ const covers = (resource, action, rule) => (rule.resource === resource || rule.resource === "*") &&
240
+ (rule.actions === "*" || rule.actions.includes(action));
241
+ /**
242
+ * The grants and denies a question about `action` on `resource` reads. A grant on
243
+ * `"*"` reaches the file's own Resources only: never a foreign one, and never one
244
+ * its schema does not list. A deny on `"*"` reaches every Resource.
245
+ */
246
+ export const rulesFor = (policies, resource, action) => {
247
+ const facts = ownOf(policies.schema, resource);
248
+ const own = facts !== undefined && facts.foreign === undefined;
249
+ return {
250
+ grants: policies.grants.filter((grant) => (own || grant.resource !== "*") && covers(resource, action, grant)),
251
+ denies: policies.denies.filter((deny) => covers(resource, action, deny)),
252
+ };
253
+ };
254
+ /** What a subject contributes to a grant, folded: `true`, `false` or a row condition. */
255
+ const subjectCondition = (policies, grant, subject, facts) => {
256
+ if (subject === "anyone") {
257
+ return TRUE;
258
+ }
259
+ if (subject === "signedIn") {
260
+ return facts.principal === null ? FALSE : TRUE;
261
+ }
262
+ const role = ownOf(policies.roles, subject);
263
+ if (role === undefined) {
264
+ return FALSE;
265
+ }
266
+ if ("global" in role) {
267
+ return foldPrincipalCondition(role.global, facts, "grant") ? TRUE : FALSE;
268
+ }
269
+ const held = { op: "role", role: subject };
270
+ const predicate = grant.in === undefined
271
+ ? held
272
+ : { op: "rel", relation: grant.in, target: role.on, where: held };
273
+ return resolvePredicate(policies, predicate, facts, "drop-rule");
274
+ };
275
+ /**
276
+ * Each grant's condition per subject: its `where`, then the subject's (its row
277
+ * role, through `in`), then, with `after`, the `after` condition. A grant that reads
278
+ * a `me` value the principal lacks drops.
279
+ */
280
+ export const foldGrants = (policies, grants, facts, withAfter = false) => grants.flatMap((grant) => grant.subjects.map((subject) => {
281
+ const parts = [];
282
+ if (grant.where !== undefined) {
283
+ parts.push(resolvePredicate(policies, grant.where, facts, "drop-rule"));
284
+ }
285
+ parts.push(subjectCondition(policies, grant, subject, facts));
286
+ if (withAfter && grant.after !== undefined) {
287
+ parts.push(resolvePredicate(policies, grant.after, facts, "drop-rule"));
288
+ }
289
+ return { grant, subject, condition: allOf(parts) };
290
+ }));
291
+ /**
292
+ * Each deny's row condition: `false` when its `if` does not hold for this principal,
293
+ * `true` when its `where` reads a `me` value the principal lacks.
294
+ */
295
+ export const foldDenies = (policies, denies, facts) => denies.map((deny) => {
296
+ if (deny.if !== undefined &&
297
+ !foldPrincipalCondition(deny.if, facts, "deny")) {
298
+ return { deny, condition: FALSE };
299
+ }
300
+ return {
301
+ deny,
302
+ condition: deny.where === undefined
303
+ ? TRUE
304
+ : resolvePredicate(policies, deny.where, facts, "match-rule"),
305
+ };
306
+ });
307
+ /** The rows a principal may `action`: OR of the grants, AND NOT each deny. */
308
+ export const scopeCondition = (grants, denies) => {
309
+ const allowed = orOf(grants.map((grant) => grant.condition));
310
+ if (allowed.k === "false") {
311
+ return FALSE;
312
+ }
313
+ return allOf([allowed, ...denies.map((deny) => notOf(deny.condition))]);
314
+ };
315
+ /** Whether the schema entry lists `field` as nullable. A file without `nullable` has none. */
316
+ const nullableIn = (policies, resource, field) => ownOf(policies.schema, resource)?.nullable?.includes(field) === true;
317
+ /**
318
+ * Whether a to-one step can find no row: `"nullable"` when one of its reference
319
+ * fields is nullable, `"required"` when it holds references and none is, and
320
+ * `"unstated"` for the end that holds no references (the other end's step).
321
+ */
322
+ const stepKind = (policies, resource, node) => {
323
+ const fields = ownOf(ownOf(policies.schema, resource)?.relations, node.relation)?.referenceFields;
324
+ if (fields === undefined) {
325
+ return "unstated";
326
+ }
327
+ return fields.some((field) => nullableIn(policies, resource, field))
328
+ ? "nullable"
329
+ : "required";
330
+ };
331
+ /**
332
+ * Whether SQL can read a comparison as unknown where `can` reads it as false: a
333
+ * nullable field compared with a value (SQL's `NULL = v` is unknown; `f = NULL` is
334
+ * written `IS NULL` and never is), or an `in` list that holds `null`.
335
+ */
336
+ const unknownIn = (policies, resource, node) => Array.isArray(node.value)
337
+ ? node.value.includes(null) ||
338
+ nullableIn(policies, resource, node.field)
339
+ : node.value !== null && nullableIn(policies, resource, node.field);
340
+ /** Whether SQL reads `node` exactly as `can` does: no unknown comparison, no step that can find no row. */
341
+ const known = (policies, resource, node) => {
342
+ switch (node.k) {
343
+ case "true":
344
+ case "false":
345
+ return true;
346
+ case "cmp":
347
+ return !unknownIn(policies, resource, node);
348
+ case "and":
349
+ case "all":
350
+ case "or":
351
+ return node.args.every((arg) => known(policies, resource, arg));
352
+ case "not":
353
+ return known(policies, resource, node.arg);
354
+ case "rel":
355
+ return (stepKind(policies, resource, node) !== "nullable" &&
356
+ known(policies, node.target, node.where));
357
+ case "some":
358
+ case "every":
359
+ case "none":
360
+ return known(policies, node.target, node.where);
361
+ }
362
+ };
363
+ /**
364
+ * A comparison with the `null`s taken out of its `in` list: `can` never matches a
365
+ * NULL field through a list, and SQL's `IN (NULL, …)` is unknown for every row. A
366
+ * list left empty: `in` matches no row; `notIn` every row whose field is not NULL,
367
+ * which on a required field is every row.
368
+ */
369
+ const listWithoutNull = (policies, resource, node) => {
370
+ if (!Array.isArray(node.value)) {
371
+ return node;
372
+ }
373
+ const value = [];
374
+ for (const item of node.value) {
375
+ if (item !== null) {
376
+ value.push(item);
377
+ }
378
+ }
379
+ if (value.length > 0) {
380
+ return { k: "cmp", field: node.field, cmp: node.cmp, value };
381
+ }
382
+ if (node.cmp === "in") {
383
+ return FALSE;
384
+ }
385
+ return nullableIn(policies, resource, node.field)
386
+ ? { k: "cmp", field: node.field, cmp: "ne", value: null }
387
+ : TRUE;
388
+ };
389
+ const isNull = (field) => ({
390
+ k: "cmp",
391
+ field,
392
+ cmp: "eq",
393
+ value: null,
394
+ });
395
+ /** The same to-one step, around another condition. */
396
+ const step = (node, where) => ({
397
+ k: "rel",
398
+ relation: node.relation,
399
+ target: node.target,
400
+ where,
401
+ });
402
+ /** A quantifier over the same to-many relation. */
403
+ const quantified = (k, node, where) => ({ k, relation: node.relation, target: node.target, where });
404
+ /** One rewrite applied to each condition of a list. */
405
+ const each = (rewrite, policies, resource, nodes) => {
406
+ const out = [];
407
+ for (const node of nodes) {
408
+ out.push(rewrite(policies, resource, node));
409
+ }
410
+ return out;
411
+ };
412
+ /** The condition, true on exactly the rows where `can` reads it as true. */
413
+ const holds = (policies, resource, node) => {
414
+ if (known(policies, resource, node)) {
415
+ return node;
416
+ }
417
+ switch (node.k) {
418
+ case "true":
419
+ case "false":
420
+ return node;
421
+ case "cmp":
422
+ return listWithoutNull(policies, resource, node);
423
+ case "and":
424
+ return andOf(each(holds, policies, resource, node.args));
425
+ case "all":
426
+ return allOf(each(holds, policies, resource, node.args));
427
+ case "or":
428
+ return orOf(each(holds, policies, resource, node.args));
429
+ case "not":
430
+ return fails(policies, resource, node.arg);
431
+ case "rel":
432
+ // A step that finds no row is false, in SQL as in `can`.
433
+ return step(node, holds(policies, node.target, node.where));
434
+ case "some":
435
+ case "none":
436
+ // `EXISTS` and `NOT EXISTS`: true or false around the rows they count.
437
+ return quantified(node.k, node, holds(policies, node.target, node.where));
438
+ case "every":
439
+ // An ORM may write `every` as `NOT EXISTS (… WHERE NOT condition)`, where an
440
+ // unknown condition passes: ask that no related row fails it instead.
441
+ return quantified("none", node, fails(policies, node.target, node.where));
442
+ }
443
+ };
444
+ /** The condition's negation, true on exactly the rows where `can` reads it as false. */
445
+ const fails = (policies, resource, node) => {
446
+ if (known(policies, resource, node)) {
447
+ return notOf(node);
448
+ }
449
+ switch (node.k) {
450
+ case "true":
451
+ case "false":
452
+ return notOf(node);
453
+ case "cmp": {
454
+ const negated = notOf(listWithoutNull(policies, resource, node));
455
+ return nullableIn(policies, resource, node.field)
456
+ ? orOf([negated, isNull(node.field)])
457
+ : negated;
458
+ }
459
+ case "and":
460
+ case "all": {
461
+ // Comparisons only: `NOT (a AND b) OR a IS NULL OR b IS NULL`.
462
+ if (node.args.every((arg) => arg.k === "cmp")) {
463
+ const comparisons = node.args;
464
+ const guards = [];
465
+ const guarded = [];
466
+ for (const arg of comparisons) {
467
+ if (arg.value !== null &&
468
+ nullableIn(policies, resource, arg.field) &&
469
+ !guarded.includes(arg.field)) {
470
+ guarded.push(arg.field);
471
+ guards.push(isNull(arg.field));
472
+ }
473
+ }
474
+ const lists = [];
475
+ for (const arg of comparisons) {
476
+ lists.push(listWithoutNull(policies, resource, arg));
477
+ }
478
+ const strict = (node.k === "and" ? andOf : allOf)(lists);
479
+ return orOf([notOf(strict), ...guards]);
480
+ }
481
+ return orOf(each(fails, policies, resource, node.args));
482
+ }
483
+ case "or":
484
+ return allOf(each(fails, policies, resource, node.args));
485
+ case "not":
486
+ return holds(policies, resource, node.arg);
487
+ case "rel": {
488
+ const failing = step(node, fails(policies, node.target, node.where));
489
+ switch (stepKind(policies, resource, node)) {
490
+ case "required":
491
+ // The row is always there: the step fails where its condition does.
492
+ return failing;
493
+ case "nullable": {
494
+ // No row (a NULL reference) fails it too, said on this row's own fields.
495
+ const references = ownOf(ownOf(policies.schema, resource)?.relations, node.relation)
496
+ ?.referenceFields ?? [];
497
+ const guards = [];
498
+ for (const field of references) {
499
+ guards.push(isNull(field));
500
+ }
501
+ guards.push(failing);
502
+ return orOf(guards);
503
+ }
504
+ case "unstated":
505
+ // No reference fields here to say "no row" with: the `NOT` of the step
506
+ // covers the missing row, the failing condition the row that is there.
507
+ return orOf([
508
+ notOf(step(node, holds(policies, node.target, node.where))),
509
+ failing,
510
+ ]);
511
+ }
512
+ return failing;
513
+ }
514
+ case "some":
515
+ case "none":
516
+ return notOf(quantified(node.k, node, holds(policies, node.target, node.where)));
517
+ case "every":
518
+ return quantified("some", node, fails(policies, node.target, node.where));
519
+ }
520
+ };
521
+ /**
522
+ * A folded condition on `resource` in the form a database reads as `can` does.
523
+ * `can` reads a comparison on a NULL field as false, so its `NOT` keeps the row; SQL
524
+ * reads it as unknown, and its `NOT` drops the row. Each negated comparison on a
525
+ * nullable field (the schema entry's `nullable`) gets its own guard, `NOT (f = v) OR
526
+ * f IS NULL`, and a negated to-one step whose reference is nullable gets `OR fk IS
527
+ * NULL`. A condition with neither is returned as it is.
528
+ */
529
+ export const nullSafe = (policies, resource, node) => holds(policies, resource, node);
@@ -0,0 +1,2 @@
1
+ /** The SHA-256 digest of `text`'s UTF-8 bytes, as 64 lowercase hex digits. */
2
+ export declare const sha256Hex: (text: string) => string;