@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,81 @@
1
+ // SHA-256 (FIPS 180-4) over UTF-8 text, synchronous and dependency-free, so the engine
2
+ // entry hashes a policies file the same way in Node, a browser, Electron or a build
3
+ // step, without `node:crypto` and without WebCrypto's async API. It hashes a few
4
+ // kilobytes at build and startup time; it is not on any request path.
5
+ const K = new Uint32Array([
6
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
7
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
8
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
9
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
10
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
11
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
12
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
13
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
14
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
15
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
16
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
17
+ ]);
18
+ const rotr = (x, n) => (x >>> n) | (x << (32 - n));
19
+ /** The SHA-256 digest of `text`'s UTF-8 bytes, as 64 lowercase hex digits. */
20
+ export const sha256Hex = (text) => {
21
+ const bytes = new TextEncoder().encode(text);
22
+ const bitLength = bytes.length * 8;
23
+ // Message, a 0x80 byte, zero padding, and the 64-bit big-endian bit length.
24
+ const blocks = Math.ceil((bytes.length + 9) / 64);
25
+ const padded = new Uint8Array(blocks * 64);
26
+ padded.set(bytes);
27
+ padded[bytes.length] = 0x80;
28
+ const view = new DataView(padded.buffer);
29
+ view.setUint32(padded.length - 8, Math.floor(bitLength / 0x1_0000_0000));
30
+ view.setUint32(padded.length - 4, bitLength >>> 0);
31
+ const h = new Uint32Array([
32
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,
33
+ 0x1f83d9ab, 0x5be0cd19,
34
+ ]);
35
+ const w = new Uint32Array(64);
36
+ for (let offset = 0; offset < padded.length; offset += 64) {
37
+ for (let t = 0; t < 16; t++) {
38
+ w[t] = view.getUint32(offset + t * 4);
39
+ }
40
+ for (let t = 16; t < 64; t++) {
41
+ const w15 = w[t - 15];
42
+ const w2 = w[t - 2];
43
+ const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ (w15 >>> 3);
44
+ const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ (w2 >>> 10);
45
+ w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0;
46
+ }
47
+ let a = h[0];
48
+ let b = h[1];
49
+ let c = h[2];
50
+ let d = h[3];
51
+ let e = h[4];
52
+ let f = h[5];
53
+ let g = h[6];
54
+ let hh = h[7];
55
+ for (let t = 0; t < 64; t++) {
56
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
57
+ const choose = (e & f) ^ (~e & g);
58
+ const t1 = (hh + s1 + choose + K[t] + w[t]) >>> 0;
59
+ const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
60
+ const majority = (a & b) ^ (a & c) ^ (b & c);
61
+ const t2 = (s0 + majority) >>> 0;
62
+ hh = g;
63
+ g = f;
64
+ f = e;
65
+ e = (d + t1) >>> 0;
66
+ d = c;
67
+ c = b;
68
+ b = a;
69
+ a = (t1 + t2) >>> 0;
70
+ }
71
+ h[0] = (h[0] + a) >>> 0;
72
+ h[1] = (h[1] + b) >>> 0;
73
+ h[2] = (h[2] + c) >>> 0;
74
+ h[3] = (h[3] + d) >>> 0;
75
+ h[4] = (h[4] + e) >>> 0;
76
+ h[5] = (h[5] + f) >>> 0;
77
+ h[6] = (h[6] + g) >>> 0;
78
+ h[7] = (h[7] + hh) >>> 0;
79
+ }
80
+ return Array.from(h, (word) => word.toString(16).padStart(8, "0")).join("");
81
+ };
@@ -0,0 +1,41 @@
1
+ /** `serve`'s options. */
2
+ export type AccessServeOptions = {
3
+ /** The port to listen on; `0` picks a free one. Default `3000`. */
4
+ readonly port?: number;
5
+ /**
6
+ * The interface to listen on. Default `"127.0.0.1"`, this host only; `"0.0.0.0"`
7
+ * listens on every interface (in a container, behind a proxy that terminates TLS).
8
+ */
9
+ readonly hostname?: string;
10
+ /** The largest request body taken, in bytes. Default 1 MiB (`1048576`). */
11
+ readonly maxBodyBytes?: number;
12
+ };
13
+ /** A running standalone host. */
14
+ export type AccessServer = {
15
+ /** The port it listens on (the one picked, when `port` was `0`). */
16
+ readonly port: number;
17
+ /** Its base URL, such as `http://127.0.0.1:3000`. */
18
+ readonly url: string;
19
+ /**
20
+ * Stops accepting, lets the requests in flight finish for up to 10 s, then ends
21
+ * the connections left; resolves once the server is closed.
22
+ */
23
+ close(): Promise<void>;
24
+ };
25
+ /**
26
+ * What `serve` takes: an instance with a handler (`createAccess`'s, or any), and the
27
+ * options Better Auth runs with, which it reads for the base path and how the
28
+ * client's address is read; and whether the registry is on.
29
+ */
30
+ type Served = {
31
+ readonly handler: (request: Request) => Promise<Response>;
32
+ readonly options?: object;
33
+ readonly registry?: unknown;
34
+ };
35
+ /**
36
+ * Serves an Access instance over HTTP: its routes, as `access.handler` answers them
37
+ * (Better Auth's base path, `/api/auth` unless `betterAuth.basePath` says otherwise),
38
+ * and `GET /health`. Resolves once the server listens.
39
+ */
40
+ export declare const serve: (access: Served, options?: AccessServeOptions) => Promise<AccessServer>;
41
+ export {};
@@ -0,0 +1,301 @@
1
+ // The standalone host: an Access instance as its own HTTP server. It is one more
2
+ // mount of `access.handler`, the way a Node.js app mounts Better Auth's (Better Auth's
3
+ // own `toNodeHandler`), plus a health route outside the auth routes for a load
4
+ // balancer or a container's health check. It holds no auth logic of its own; what it
5
+ // adds is what a server facing the network owes the handler behind it:
6
+ //
7
+ // - It listens on 127.0.0.1 unless told otherwise, and speaks plain HTTP: put it
8
+ // behind a proxy that terminates TLS before anything outside the host reaches it.
9
+ // - A request body is capped (1 MiB unless `maxBodyBytes` says) before any of it is
10
+ // handed on: a declared length over the cap is refused unread, a streamed one when
11
+ // it passes the cap. A write to the registry has its key checked before its body
12
+ // is read at all.
13
+ // - The client's address is the socket's. A forwarded-for header is believed only
14
+ // from a proxy Better Auth's `advanced.ipAddress.trustedProxies` names; from anyone
15
+ // else it is replaced with the socket's address, so Better Auth's rate limit counts
16
+ // each client in its own bucket and a client cannot pick its own.
17
+ // - `close()` stops accepting, lets requests in flight finish for up to 10 s, then
18
+ // ends what is left.
19
+ import { createServer, } from "node:http";
20
+ import { BlockList, isIP } from "node:net";
21
+ import { toNodeHandler } from "better-auth/node";
22
+ /** The path of the health route: `GET` or `HEAD` answers `200 {"status":"ok"}`. */
23
+ const HEALTH = "/health";
24
+ const DEFAULT_PORT = 3000;
25
+ const DEFAULT_HOSTNAME = "127.0.0.1";
26
+ const DEFAULT_MAX_BODY = 1_048_576;
27
+ const DRAIN_MS = 10_000;
28
+ /** The header a registry key travels in, and the route that names a key's namespace. */
29
+ const KEY_HEADER = "x-access-key";
30
+ const KEY_OWNER = "/access/v1/key";
31
+ /** An IPv4-mapped IPv6 address (`::ffff:127.0.0.1`) as the IPv4 address it is. */
32
+ const plainAddress = (address) => {
33
+ if (address === undefined) {
34
+ return undefined;
35
+ }
36
+ const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i.exec(address);
37
+ return mapped?.[1] ?? address;
38
+ };
39
+ /** Better Auth's `trustedProxies` (addresses and CIDR ranges) as a list `check` reads. */
40
+ const proxyList = (entries) => {
41
+ if (entries.length === 0) {
42
+ return undefined;
43
+ }
44
+ const list = new BlockList();
45
+ for (const entry of entries) {
46
+ const [address = "", prefix] = entry.split("/");
47
+ const family = isIP(address);
48
+ if (family === 0) {
49
+ continue;
50
+ }
51
+ const type = family === 4 ? "ipv4" : "ipv6";
52
+ if (prefix === undefined) {
53
+ list.addAddress(address, type);
54
+ }
55
+ else if (/^\d+$/.test(prefix)) {
56
+ list.addSubnet(address, Number(prefix), type);
57
+ }
58
+ }
59
+ return list;
60
+ };
61
+ /** Whether `address` is one of `list`'s. */
62
+ const listed = (list, address) => {
63
+ const family = isIP(address);
64
+ return (list !== undefined &&
65
+ family !== 0 &&
66
+ list.check(address, family === 4 ? "ipv4" : "ipv6"));
67
+ };
68
+ /** An absolute-form request target (RFC 9112 section 3.2.2): `http://host/path`. */
69
+ const ABSOLUTE_FORM = /^[a-z][a-z0-9+.-]*:\/\//i;
70
+ /**
71
+ * A request target in origin form: an absolute-form one (`http://host/path?query`)
72
+ * as its path and query, which a server must take (RFC 9112 section 3.2.2) and which
73
+ * the handler's mount would otherwise read as a path.
74
+ */
75
+ const originForm = (target) => {
76
+ if (!ABSOLUTE_FORM.test(target)) {
77
+ return target;
78
+ }
79
+ try {
80
+ const url = new URL(target);
81
+ return `${url.pathname}${url.search}`;
82
+ }
83
+ catch {
84
+ return "/";
85
+ }
86
+ };
87
+ /**
88
+ * A path as a router could match it: resolved as a URL is (its dot segments, `%2e`
89
+ * among them, and a backslash read as a slash, as Better Auth's mount reads it),
90
+ * decoded, its slashes collapsed, in lower case.
91
+ */
92
+ const routePath = (url) => {
93
+ let path = url.split("?")[0] ?? "/";
94
+ try {
95
+ path = new URL(path, "http://localhost").pathname;
96
+ }
97
+ catch {
98
+ // Kept as sent.
99
+ }
100
+ let decoded = path;
101
+ try {
102
+ decoded = decodeURIComponent(path);
103
+ }
104
+ catch {
105
+ // Kept as sent.
106
+ }
107
+ return decoded.replace(/\/{2,}/g, "/").toLowerCase();
108
+ };
109
+ /** Reads a request's body, up to `max` bytes; `undefined` once it passes `max`. */
110
+ const bodyOf = (request, max) => new Promise((resolve, reject) => {
111
+ const chunks = [];
112
+ let size = 0;
113
+ let settled = false;
114
+ request.on("data", (chunk) => {
115
+ if (settled) {
116
+ return;
117
+ }
118
+ size += chunk.length;
119
+ if (size > max) {
120
+ settled = true;
121
+ resolve(undefined);
122
+ return;
123
+ }
124
+ chunks.push(chunk);
125
+ });
126
+ request.on("end", () => {
127
+ if (settled) {
128
+ return;
129
+ }
130
+ settled = true;
131
+ const body = new Uint8Array(size);
132
+ let offset = 0;
133
+ for (const chunk of chunks) {
134
+ body.set(chunk, offset);
135
+ offset += chunk.length;
136
+ }
137
+ resolve(body);
138
+ });
139
+ request.on("error", (error) => {
140
+ if (!settled) {
141
+ settled = true;
142
+ reject(error);
143
+ }
144
+ });
145
+ });
146
+ const tooLarge = (max) => JSON.stringify({
147
+ code: "PAYLOAD_TOO_LARGE",
148
+ message: `Aventara Access: the request body is larger than this server takes (${max} bytes)`,
149
+ });
150
+ /** A refusal `serve` answers itself; the connection is closed after it, so no unread body is drained. */
151
+ const refuse = (request, response, status, body) => {
152
+ response.statusCode = status;
153
+ response.setHeader("content-type", "application/json");
154
+ response.setHeader("connection", "close");
155
+ response.end(body, () => {
156
+ request.destroy();
157
+ });
158
+ };
159
+ /**
160
+ * Serves an Access instance over HTTP: its routes, as `access.handler` answers them
161
+ * (Better Auth's base path, `/api/auth` unless `betterAuth.basePath` says otherwise),
162
+ * and `GET /health`. Resolves once the server listens.
163
+ */
164
+ export const serve = (access, options = {}) => {
165
+ const handle = toNodeHandler(access);
166
+ const maxBody = options.maxBodyBytes ?? DEFAULT_MAX_BODY;
167
+ const betterAuth = (access.options ?? {});
168
+ const ip = betterAuth.advanced?.ipAddress;
169
+ const ipHeaders = (ip?.ipAddressHeaders ?? ["x-forwarded-for"]).map((header) => header.toLowerCase());
170
+ const proxies = proxyList(ip?.trustedProxies ?? []);
171
+ const basePath = routePath(betterAuth.basePath ?? "/api/auth").replace(/\/+$/, "");
172
+ const registryOn = access.registry !== undefined;
173
+ let closing = false;
174
+ /**
175
+ * Sets the client's address where Better Auth reads it (its first
176
+ * `ipAddressHeaders`, `x-forwarded-for` by default): the socket's, unless the
177
+ * socket is a trusted proxy's, whose forwarded headers are then kept.
178
+ */
179
+ const addressed = (request) => {
180
+ if (ip?.disableIpTracking === true) {
181
+ return;
182
+ }
183
+ const peer = plainAddress(request.socket.remoteAddress);
184
+ const headers = request.headers;
185
+ if (peer === undefined || !listed(proxies, peer)) {
186
+ for (const header of ipHeaders) {
187
+ delete headers[header];
188
+ }
189
+ delete headers["x-forwarded-proto"];
190
+ delete headers["x-forwarded-host"];
191
+ }
192
+ const first = ipHeaders[0];
193
+ if (peer !== undefined && first !== undefined && !(first in headers)) {
194
+ headers[first] = peer;
195
+ }
196
+ };
197
+ /** The registry's refusal of a write's key, asked before its body is read; `undefined` for a key it has. */
198
+ const keyRefusal = async (request) => {
199
+ const key = request.headers[KEY_HEADER];
200
+ const forwarded = Object.fromEntries(ipHeaders.flatMap((header) => {
201
+ const value = request.headers[header];
202
+ return typeof value === "string" ? [[header, value]] : [];
203
+ }));
204
+ const answer = await access.handler(new Request(new URL(`${basePath}${KEY_OWNER}`, "http://localhost"), {
205
+ headers: {
206
+ ...forwarded,
207
+ [KEY_HEADER]: typeof key === "string" ? key : "",
208
+ },
209
+ }));
210
+ return answer.status === 200 ? undefined : answer;
211
+ };
212
+ const answer = async (request, response) => {
213
+ const method = request.method ?? "GET";
214
+ request.url = originForm(request.url ?? "/");
215
+ const path = request.url.split("?")[0];
216
+ if (path === HEALTH && (method === "GET" || method === "HEAD")) {
217
+ response.statusCode = 200;
218
+ response.setHeader("content-type", "application/json");
219
+ response.end(method === "HEAD" ? undefined : '{"status":"ok"}');
220
+ return;
221
+ }
222
+ if (closing) {
223
+ response.setHeader("connection", "close");
224
+ }
225
+ addressed(request);
226
+ if (method !== "GET" && method !== "HEAD") {
227
+ const declared = Number(request.headers["content-length"]);
228
+ if (Number.isFinite(declared) && declared > maxBody) {
229
+ refuse(request, response, 413, tooLarge(maxBody));
230
+ return;
231
+ }
232
+ if (registryOn &&
233
+ routePath(request.url ?? "/").startsWith(`${basePath}/access/`)) {
234
+ const refused = await keyRefusal(request);
235
+ if (refused !== undefined) {
236
+ refuse(request, response, refused.status, await refused.text());
237
+ return;
238
+ }
239
+ }
240
+ const body = await bodyOf(request, maxBody);
241
+ if (body === undefined) {
242
+ refuse(request, response, 413, tooLarge(maxBody));
243
+ return;
244
+ }
245
+ if (body.length > 0 && request.headers["content-type"] !== undefined) {
246
+ // Read whole: Better Auth's Node.js mount takes a body read before it.
247
+ request.body = new TextDecoder().decode(body);
248
+ }
249
+ }
250
+ await handle(request, response);
251
+ };
252
+ const server = createServer((request, response) => {
253
+ answer(request, response).catch(() => {
254
+ if (!response.headersSent) {
255
+ response.statusCode = 500;
256
+ }
257
+ response.end();
258
+ });
259
+ });
260
+ const port = options.port ?? DEFAULT_PORT;
261
+ const hostname = options.hostname ?? DEFAULT_HOSTNAME;
262
+ return new Promise((resolve, reject) => {
263
+ server.once("error", reject);
264
+ server.listen(port, hostname, () => {
265
+ server.off("error", reject);
266
+ const address = server.address();
267
+ const bound = typeof address === "object" && address !== null ? address.port : port;
268
+ const host = hostname === "0.0.0.0" || hostname === "::"
269
+ ? "localhost"
270
+ : hostname.includes(":")
271
+ ? `[${hostname}]`
272
+ : hostname;
273
+ resolve({
274
+ port: bound,
275
+ url: `http://${host}:${bound}`,
276
+ close: () => new Promise((closed, failed) => {
277
+ closing = true;
278
+ // Ends every connection still open once the drain is over.
279
+ const force = setTimeout(() => {
280
+ server.closeAllConnections();
281
+ }, DRAIN_MS);
282
+ // A connection whose request was answered is idle: ended as it gets so.
283
+ const idle = setInterval(() => {
284
+ server.closeIdleConnections();
285
+ }, 50);
286
+ server.close((error) => {
287
+ clearTimeout(force);
288
+ clearInterval(idle);
289
+ if (error) {
290
+ failed(error);
291
+ }
292
+ else {
293
+ closed();
294
+ }
295
+ });
296
+ server.closeIdleConnections();
297
+ }),
298
+ });
299
+ });
300
+ });
301
+ };
@@ -0,0 +1 @@
1
+ export { type AccessServeOptions, type AccessServer, serve, } from "./access.server.js";
@@ -0,0 +1 @@
1
+ export { serve, } from "./access.server.js";
package/package.json CHANGED
@@ -1,6 +1,105 @@
1
1
  {
2
2
  "name": "@aventara/access",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0-rc.1",
4
+ "license": "SEE LICENSE IN LICENSE",
5
+ "description": "A policy engine for Aventara: role-first rules typed from your schema, compiled to a passive policies file.",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": "^22.23.4 || >=24.22.1"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/mhmd-hatem/Aventara-Access.git",
13
+ "directory": "packages/access"
14
+ },
15
+ "sideEffects": false,
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "bin": {
19
+ "avaccess": "./dist/cli/avaccess.bin.js"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./auth": {
28
+ "types": "./dist/auth/index.d.ts",
29
+ "import": "./dist/auth/index.js",
30
+ "default": "./dist/auth/index.js"
31
+ },
32
+ "./better-auth": {
33
+ "types": "./dist/better-auth/index.d.ts",
34
+ "import": "./dist/better-auth/index.js",
35
+ "default": "./dist/better-auth/index.js"
36
+ },
37
+ "./standalone": {
38
+ "types": "./dist/standalone/index.d.ts",
39
+ "import": "./dist/standalone/index.js",
40
+ "default": "./dist/standalone/index.js"
41
+ }
42
+ },
43
+ "imports": {
44
+ "#better-auth.require": {
45
+ "node": "./dist/access/better-auth.require.node.js",
46
+ "default": "./dist/access/better-auth.require.js"
47
+ }
48
+ },
49
+ "files": [
50
+ "dist",
51
+ "src/policies-contract.ts",
52
+ "src/scope.resolver.ts",
53
+ "src/aventara-where.translator.ts",
54
+ "src/predicate.evaluator.ts",
55
+ "src/canonical-json.writer.ts",
56
+ "src/sha256.digest.ts",
57
+ "src/policies.decider.ts",
58
+ "src/beacon/beacon.check.ts",
59
+ "src/generator/prisma7/prisma-where.translator.ts",
60
+ "skill",
61
+ "LICENSE-ADDITIONAL-PERMISSION.md",
62
+ "CHANGELOG.md"
63
+ ],
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "dependencies": {
68
+ "get-tsconfig": "4.10.0",
69
+ "jiti": "2.7.0",
70
+ "jose": "6.2.13"
71
+ },
72
+ "peerDependencies": {
73
+ "@better-auth/cimd": "~1.7.12",
74
+ "@better-auth/oauth-provider": "~1.7.12",
75
+ "@node-rs/argon2": "2.2.2",
76
+ "better-auth": "~1.7.12"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "@better-auth/cimd": {
80
+ "optional": true
81
+ },
82
+ "@better-auth/oauth-provider": {
83
+ "optional": true
84
+ },
85
+ "@node-rs/argon2": {
86
+ "optional": true
87
+ },
88
+ "better-auth": {
89
+ "optional": true
90
+ }
91
+ },
92
+ "devDependencies": {
93
+ "@aventara/core": "1.0.0-rc.11",
94
+ "@better-auth/cimd": "1.7.14",
95
+ "@better-auth/expo": "1.7.14",
96
+ "@better-auth/oauth-provider": "1.7.14",
97
+ "@node-rs/argon2": "2.2.2",
98
+ "better-auth": "1.7.14"
99
+ },
100
+ "scripts": {
101
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
102
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
103
+ "test": "vitest run --typecheck"
104
+ }
6
105
  }