@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,348 @@
1
+ // `aventaraAccess()`: Access as a Better Auth plugin, for an app that builds Better
2
+ // Auth itself (`betterAuth({ plugins: [aventaraAccess({ policies })] })`). It is the
3
+ // other way in beside `createAccess`, and runs the same code: the `policies` block
4
+ // and `principal` checked when it is called; Better Auth's options checked when
5
+ // Better Auth initialises the plugin, as `createAccess` checks its `betterAuth` block
6
+ // (the fixed issuer, the session cookie's site, the claims' sources, which Resource
7
+ // is which of Better Auth's tables, no second `jwt()`); and tokens issued by Better
8
+ // Auth's own `jwt()`, which the plugin carries inside it, with `sub` and the declared
9
+ // claims only. `accessOf(auth)` then gives the framework its `access` option.
10
+ //
11
+ // Passwords are hashed with argon2id as under `createAccess` (the `passwords` block,
12
+ // or the app's own `emailAndPassword.password`, which passes through): the plugin
13
+ // puts the hook in Better Auth's context when Better Auth initialises it.
14
+ //
15
+ // The audit log is here as under `createAccess` (`audit: true` or `{ sink }`): sign-ins,
16
+ // sign-outs, refused sign-ins and tokens from Better Auth's hooks, and the decisions
17
+ // of `accessOf(auth).policy`.
18
+ //
19
+ // The registry is here too, as under `createAccess` (`registry: { enabled: true }`),
20
+ // which makes the app the hub other services push to and pull from: its tables are
21
+ // this plugin's schema, so Better Auth's `migrate`/`generate` create them through the
22
+ // app's own adapter, and its routes are this plugin's endpoints, so Better Auth serves
23
+ // them wherever the app mounts it. A service reaches them only with its namespace's
24
+ // key; the operator's API (namespaces, keys, the org's guardrails) is
25
+ // `accessOf(auth).registry`, in the app's own process, and no route opens it. What
26
+ // else `createAccess` adds beside Better Auth (profiles, its `handler`, the standalone
27
+ // host) is not here: the app owns its Better Auth instance and mounts it as it
28
+ // already does.
29
+ //
30
+ // `jwt()` is carried inside this plugin, under the Access plugin's id. A Better Auth
31
+ // plugin that looks `jwt()` up by its id (`ctx.getPlugin("jwt")`, as the OAuth
32
+ // provider does for its issuer and keys) finds the carried one: the plugin answers
33
+ // for that id in Better Auth's context (`getPlugin`, `hasPlugin`) when Better Auth
34
+ // starts it, so a plugin that reads it as it starts comes after this one in
35
+ // `plugins`.
36
+ //
37
+ // With `mcp` or `audience`, Access reads the tokens Better Auth's OAuth provider (in
38
+ // `plugins` after this plugin) issues, as under `createAccess`: PKCE is required of
39
+ // every authorization request, `accessOf(auth).mcp` is the framework's
40
+ // `mcp.authorization`, and with `audience` the service takes only bearer tokens
41
+ // addressed to it. Access issues no token and serves no OAuth route.
42
+ import { AccessError } from "../access/access.error.js";
43
+ import { ACCESS_PLUGIN_ID, cookieCacheOn, crossSiteSessionCookie, issuerOf, JWT_PLUGIN_ID, normalisedId, refuseInstalledPlugins, refuseUnverifiedInvitations, } from "../access/better-auth.hardening.js";
44
+ import { loadBetterAuth, } from "../access/better-auth.loader.js";
45
+ import { issuedClaims, } from "../access/claims.mapper.js";
46
+ import { betterAuthTablesFor, checkPoliciesBlock, } from "../access/policies-block.checker.js";
47
+ import { sessionStillLive } from "../access/request.state.js";
48
+ import { sessionWatch } from "../access/session.hooks.js";
49
+ import { auditDestinationOf, createAuditLog, } from "../audit/audit.log.js";
50
+ import { auditSchema } from "../audit/audit.schema.js";
51
+ import { authorizationServerOf, checkMcpOptions, } from "../mcp/mcp-options.js";
52
+ import { userInfoAudience } from "../oauth/access-token.verifier.js";
53
+ import { checkOAuthProvider, checkResourceUrl, oauthUseOf, pkceGuard, } from "../oauth/oauth-provider.hardening.js";
54
+ import { checkPasswordsOptions, passwordHookOf, passwordRehashHook, } from "../passwords/passwords.options.js";
55
+ import { isRecord } from "../policies.compiler.js";
56
+ import { createRegistry, } from "../registry/access.registry.js";
57
+ import { registryEndpoints, registryOnRequest, } from "../registry/registry.endpoints.js";
58
+ import { registryOptionsOf, } from "../registry/registry.options.js";
59
+ import { registrySchema } from "../registry/registry.schema.js";
60
+ import { checkResourcesOptions, } from "../resources/auth-tables.guard.js";
61
+ import { bridgedClaims, checkBridge, checkBridgePlugins, } from "./organization.bridge.js";
62
+ /**
63
+ * What each plugin `aventaraAccess()` built was built with, by plugin: a map this
64
+ * module alone holds, so no object but one it returned passes for the plugin.
65
+ */
66
+ const KERNELS = new WeakMap();
67
+ const TOP_LEVEL = new Set([
68
+ "policies",
69
+ "principal",
70
+ "resources",
71
+ "organization",
72
+ "audit",
73
+ "passwords",
74
+ "registry",
75
+ "mcp",
76
+ "audience",
77
+ ]);
78
+ /** The plugins `aventaraAccess()` carries itself, by id: a second one in `plugins` would answer for its routes. */
79
+ const CARRIED = { [JWT_PLUGIN_ID]: "jwt()" };
80
+ /**
81
+ * The Access plugin for Better Auth: `betterAuth({ plugins: [aventaraAccess({ policies })] })`.
82
+ * It carries Better Auth's `jwt()` (whose tokens carry `sub` and the declared claims,
83
+ * under the app's base URL as issuer and audience), so `jwt()` is not added beside
84
+ * it. `accessOf(auth)` is the framework's `access` option. `better-auth` is loaded
85
+ * here, and must be in Access's range, `~1.7.12` (1.7.12 or a later 1.7 patch).
86
+ */
87
+ export function aventaraAccess(options) {
88
+ if (!isRecord(options)) {
89
+ throw new AccessError("ACCESS_OPTIONS_INVALID", "aventaraAccess takes one object: `{ policies, principal?, resources?, organization?, audit?, passwords?, registry?, mcp?, audience? }`.");
90
+ }
91
+ for (const key of Object.keys(options)) {
92
+ if (!TOP_LEVEL.has(key)) {
93
+ throw new AccessError("ACCESS_OPTIONS_INVALID", `aventaraAccess does not take \`${key}\`; it takes \`policies\`, \`principal\`, \`resources\`, \`organization\`, \`audit\`, \`passwords\`, \`registry\`, \`mcp\` and \`audience\`. Better Auth's own options go to betterAuth({...}) beside it.`);
94
+ }
95
+ }
96
+ const given = options;
97
+ if (given.policies === undefined) {
98
+ throw new AccessError("ACCESS_OPTIONS_INVALID", "the `policies` option is missing: pass `{ rules, claims }`, the rules (`definePolicies(model)…`) and the claims they read.");
99
+ }
100
+ const checked = checkPoliciesBlock(given.policies, given.principal);
101
+ const resources = checkResourcesOptions(given.resources);
102
+ const bridge = checkBridge(given.organization, checked.claims);
103
+ const passwords = checkPasswordsOptions(given.passwords);
104
+ const registrySettings = registryOptionsOf(given.registry);
105
+ // The block's shape now; its resource against the issuer when Better Auth starts.
106
+ const mcp = checkMcpOptions(given.mcp, "mcp", "");
107
+ const oauthOn = oauthUseOf(mcp, given.audience === undefined
108
+ ? undefined
109
+ : checkResourceUrl(given.audience, "audience", ""), undefined);
110
+ const modules = loadBetterAuth();
111
+ // The bridge owns its two claims: neither the user's field of the same name nor
112
+ // `claimsOf` fills them.
113
+ const bridged = bridge === undefined ? [] : [bridge.claim, bridge.organizationClaim];
114
+ const ownClaims = {
115
+ ...checked.claims,
116
+ };
117
+ for (const claim of bridged) {
118
+ delete ownClaims[claim];
119
+ }
120
+ const claimsOf = checked.claimsOf === undefined || bridge === undefined
121
+ ? checked.claimsOf
122
+ : async (source) => {
123
+ const answer = await checked.claimsOf(source);
124
+ if (!isRecord(answer)) {
125
+ return answer;
126
+ }
127
+ const kept = { ...answer };
128
+ for (const claim of bridged) {
129
+ delete kept[claim];
130
+ }
131
+ return kept;
132
+ };
133
+ const claimsFor = async (context, source) => {
134
+ const claims = await issuedClaims(source, bridge === undefined ? checked.claims : ownClaims, claimsOf);
135
+ if (bridge === undefined) {
136
+ return claims;
137
+ }
138
+ const fromBridge = await bridgedClaims(bridge, (await context()), source);
139
+ return fromBridge === undefined ? claims : { ...claims, ...fromBridge };
140
+ };
141
+ const start = (block, own) => {
142
+ const plugins = refuseInstalledPlugins(block.plugins, {
143
+ by: "aventaraAccess()",
144
+ at: "plugins",
145
+ installs: CARRIED,
146
+ own,
147
+ registry: registrySettings !== undefined,
148
+ });
149
+ for (const plugin of plugins) {
150
+ const id = plugin?.id;
151
+ if (plugin !== own && normalisedId(id) === ACCESS_PLUGIN_ID) {
152
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `plugins holds a second Access plugin (plugin id "${String(id)}") beside aventaraAccess(): Better Auth runs one. Remove it.`);
153
+ }
154
+ }
155
+ if (resources.members) {
156
+ refuseUnverifiedInvitations(plugins, "`resources: { members: true }`");
157
+ }
158
+ if (bridge !== undefined) {
159
+ checkBridgePlugins(plugins, bridge);
160
+ }
161
+ const issuer = issuerOf(block, modules, "baseURL");
162
+ const checkedMcp = checkMcpOptions(given.mcp, "mcp", issuer);
163
+ const audience = given.audience === undefined
164
+ ? undefined
165
+ : checkResourceUrl(given.audience, "audience", issuer);
166
+ const userInfo = userInfoAudience(block, modules);
167
+ const use = oauthUseOf(checkedMcp, audience, userInfo);
168
+ if (use !== undefined) {
169
+ checkOAuthProvider(plugins, use, { at: "plugins", own });
170
+ }
171
+ return {
172
+ ...(checkedMcp === undefined
173
+ ? {}
174
+ : {
175
+ mcp: {
176
+ resource: checkedMcp.resource,
177
+ server: authorizationServerOf(issuer, "baseURL"),
178
+ },
179
+ }),
180
+ ...(audience === undefined ? {} : { audience }),
181
+ userInfo,
182
+ issuer,
183
+ crossSite: crossSiteSessionCookie(block, "trustedOrigins"),
184
+ facts: betterAuthTablesFor({
185
+ options: block,
186
+ modules,
187
+ checked,
188
+ resources,
189
+ bridged,
190
+ }),
191
+ };
192
+ };
193
+ // Fixed when Better Auth initialises the plugin, from its options: the tokens'
194
+ // issuer and audience, and the context the claims, the session check and the
195
+ // audit log read. `jwt()` hands its payload function the session alone, never
196
+ // Better Auth's context, so these are the plugin's, not each context's: a plugin
197
+ // started again (Better Auth's migrations build another context from the same
198
+ // options) must find every one of them as it was, or it is refused.
199
+ let first;
200
+ const contextOf = async () => first?.context;
201
+ const auditTo = auditDestinationOf(given.audit);
202
+ const auditLog = auditTo === undefined
203
+ ? undefined
204
+ : createAuditLog(auditTo, async () => (await contextOf()));
205
+ const watch = sessionWatch({
206
+ kind: "web",
207
+ auditLog,
208
+ modules,
209
+ plugins: () => first?.plugins ?? [],
210
+ });
211
+ const carried = modules.jwt({
212
+ jwt: {
213
+ get issuer() {
214
+ return first?.issuer;
215
+ },
216
+ get audience() {
217
+ return first?.issuer;
218
+ },
219
+ expirationTime: `${checked.tokenTtl}s`,
220
+ definePayload: async (source) => {
221
+ if (first?.cookieCache === true) {
222
+ await sessionStillLive(contextOf, source.session, () => new modules.APIError("UNAUTHORIZED", {
223
+ message: "The session has ended.",
224
+ }));
225
+ }
226
+ return claimsFor(contextOf, source);
227
+ },
228
+ },
229
+ });
230
+ // The registry's routes store through the adapter of the Better Auth that started
231
+ // the plugin: every instance it is in has the same database (`init` refuses one
232
+ // that does not), and no route runs before Better Auth has started it.
233
+ const registryStore = registrySettings === undefined
234
+ ? undefined
235
+ : createRegistry(async () => {
236
+ const context = first?.context;
237
+ if (context?.adapter === undefined) {
238
+ throw new AccessError("ACCESS_OPTIONS_INVALID", "the registry was asked before Better Auth started aventaraAccess(): build Better Auth with it in its plugins first.");
239
+ }
240
+ return context.adapter;
241
+ }, registrySettings);
242
+ const registryRoutes = registryStore === undefined
243
+ ? undefined
244
+ : registryEndpoints(registryStore, modules.createAuthEndpoint);
245
+ const carriedHooks = carried.hooks;
246
+ const plugin = {
247
+ id: ACCESS_PLUGIN_ID,
248
+ version: carried.version,
249
+ init(ctx) {
250
+ const { options: block } = ctx;
251
+ const started = start(block, plugin);
252
+ const now = {
253
+ issuer: started.issuer,
254
+ crossSite: started.crossSite,
255
+ cookieCache: cookieCacheOn(block),
256
+ database: block.database,
257
+ secondaryStorage: block.secondaryStorage,
258
+ };
259
+ if (first === undefined) {
260
+ first = {
261
+ ...now,
262
+ context: ctx,
263
+ plugins: Array.isArray(block.plugins) ? block.plugins : [],
264
+ };
265
+ }
266
+ else if (now.issuer !== first.issuer ||
267
+ now.crossSite !== first.crossSite ||
268
+ now.cookieCache !== first.cookieCache ||
269
+ now.database !== first.database ||
270
+ now.secondaryStorage !== first.secondaryStorage) {
271
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", "one aventaraAccess() plugin is in two Better Auth instances whose options differ (database, secondary storage, base URL, session cookie or cookie cache): call aventaraAccess() for each betterAuth({...}).");
272
+ }
273
+ // Better Auth's password hook, argon2id unless the app set its own: put in
274
+ // Better Auth's context, which Better Auth built from its options before
275
+ // initialising its plugins.
276
+ const hook = passwordHookOf(passwords, block.emailAndPassword, modules, contextOf, "emailAndPassword");
277
+ const watched = watch.init?.();
278
+ // The carried `jwt()`, found by its own id in Better Auth's context, as a plugin
279
+ // beside it would be: the OAuth provider reads its issuer and keys there.
280
+ const { getPlugin, hasPlugin } = ctx;
281
+ const jwtLookup = {
282
+ getPlugin: (id) => id === JWT_PLUGIN_ID ? carried : getPlugin(id),
283
+ hasPlugin: (id) => id === JWT_PLUGIN_ID || hasPlugin(id),
284
+ };
285
+ const { password } = ctx;
286
+ return {
287
+ ...watched,
288
+ context: {
289
+ ...watched?.context,
290
+ ...jwtLookup,
291
+ ...(hook === undefined
292
+ ? {}
293
+ : {
294
+ password: {
295
+ ...password,
296
+ hash: hook.hash,
297
+ verify: hook.verify,
298
+ },
299
+ }),
300
+ },
301
+ };
302
+ },
303
+ endpoints: registryRoutes === undefined
304
+ ? carried.endpoints
305
+ : { ...carried.endpoints, ...registryRoutes },
306
+ // A push's key is checked before Better Auth reads its body, on any mount.
307
+ ...(registryStore === undefined
308
+ ? {}
309
+ : { onRequest: registryOnRequest(registryStore) }),
310
+ hooks: {
311
+ before: [
312
+ ...(carriedHooks.before ?? []),
313
+ ...(oauthOn === undefined ? [] : [pkceGuard(modules)]),
314
+ ...(watch.hooks?.before ?? []),
315
+ ],
316
+ after: [
317
+ ...(carriedHooks.after ?? []),
318
+ ...(watch.hooks?.after ?? []),
319
+ // Inert unless Better Auth's password hook is Access's (the app's own passes through).
320
+ passwordRehashHook(modules),
321
+ ],
322
+ },
323
+ schema: {
324
+ ...carried.schema,
325
+ ...(registrySettings === undefined ? {} : registrySchema),
326
+ ...(auditTo?.kind === "table" ? auditSchema : {}),
327
+ },
328
+ $Infer: {},
329
+ };
330
+ const kernel = {
331
+ checked,
332
+ resources,
333
+ bridge,
334
+ modules,
335
+ auditLog,
336
+ registry: registrySettings,
337
+ start,
338
+ claimsFor,
339
+ };
340
+ KERNELS.set(plugin, kernel);
341
+ // The declared return type states it; a cast restating it costs the type-checker
342
+ // the whole plugin type again.
343
+ return plugin;
344
+ }
345
+ /** What `aventaraAccess()` was built with, when `plugin` is one; `undefined` for any other plugin. */
346
+ export const pluginKernelOf = (plugin) => typeof plugin === "object" && plugin !== null
347
+ ? KERNELS.get(plugin)
348
+ : undefined;
@@ -0,0 +1,80 @@
1
+ import type { AccessEngine } from "../access.engine.js";
2
+ import type { AccessMcpAuthorization } from "../mcp/mcp-options.js";
3
+ import { type AccessRegistry } from "../registry/access.registry.js";
4
+ type GovernedShape = {
5
+ readonly [resource: string]: readonly string[] | {
6
+ readonly [field: string]: "read" | "write" | "both";
7
+ };
8
+ };
9
+ /** The headers of a request, as the framework hands them to `state`. */
10
+ type RequestHeaders = {
11
+ get(name: string): string | null;
12
+ has(name: string): boolean;
13
+ forEach(callback: (value: string, name: string) => void): void;
14
+ };
15
+ /** What `accessOf` reads of a Better Auth instance: its options, its API and its context. */
16
+ export type AccessAuth = {
17
+ readonly options: {
18
+ readonly plugins?: readonly unknown[] | undefined;
19
+ };
20
+ readonly api: unknown;
21
+ readonly $context: Promise<unknown>;
22
+ };
23
+ /** The `aventaraAccess()` plugin among an instance's plugins, by type. */
24
+ type AccessPluginOf<A extends AccessAuth> = Extract<NonNullable<A["options"]["plugins"]>[number], {
25
+ readonly id: "aventara-access";
26
+ }>;
27
+ /** `accessOf(auth).registry`: the operator's API when the plugin serves the registry, absent when it does not. */
28
+ type AccessProviderRegistry<E extends boolean> = [E] extends [true] ? {
29
+ /** The registry's operator API: namespaces, keys and the org's guardrails. */
30
+ readonly registry: AccessRegistry;
31
+ } : [E] extends [false] ? {
32
+ readonly registry?: undefined;
33
+ } : {
34
+ readonly registry?: AccessRegistry | undefined;
35
+ };
36
+ /** What `accessOf(auth)` returns: the framework's `access` option, and its types. */
37
+ export type AccessProvider<Claims, Gov extends GovernedShape, M, E extends boolean = false, P extends boolean = false> = {
38
+ /** The policy engine, asked about every client and agent request. */
39
+ readonly policy: AccessEngine<Gov>;
40
+ /** A request's state: `me`, from its bearer token or its session cookie, else `null`. */
41
+ state(input: {
42
+ readonly headers: RequestHeaders;
43
+ readonly request?: unknown;
44
+ }): Promise<{
45
+ readonly me: M | null;
46
+ }>;
47
+ /** Types only, as Better Auth's `$Infer`: `typeof access.$Infer.Me` is the principal. */
48
+ readonly $Infer: {
49
+ readonly Me: M;
50
+ readonly Claims: Claims;
51
+ };
52
+ } & AccessProviderRegistry<E> & AccessProviderMcp<Claims, P>;
53
+ /** `accessOf(auth).mcp`: the framework's `mcp.authorization` when the plugin has `mcp`, absent when it has not. */
54
+ type AccessProviderMcp<Claims, P extends boolean> = [P] extends [true] ? {
55
+ /** The framework's `mcp.authorization`: `mcp: { path: "/mcp", authorization: access.mcp }`. */
56
+ readonly mcp: AccessMcpAuthorization<Claims>;
57
+ } : [P] extends [false] ? {
58
+ readonly mcp?: undefined;
59
+ } : {
60
+ readonly mcp?: AccessMcpAuthorization<Claims> | undefined;
61
+ };
62
+ /** `accessOf`'s answer for an instance, typed from its `aventaraAccess()` plugin's `$Infer`. */
63
+ export type AccessProviderOf<A extends AccessAuth> = AccessPluginOf<A> extends {
64
+ readonly $Infer: infer I extends {
65
+ readonly AccessMe: unknown;
66
+ readonly AccessClaims: unknown;
67
+ readonly AccessGoverned: GovernedShape;
68
+ readonly AccessRegistryEnabled: boolean;
69
+ readonly AccessMcp: boolean;
70
+ };
71
+ } ? AccessProvider<I["AccessClaims"], I["AccessGoverned"], I["AccessMe"], I["AccessRegistryEnabled"], I["AccessMcp"]> : AccessProvider<{
72
+ readonly [claim: string]: string | number | boolean;
73
+ }, GovernedShape, unknown, boolean, boolean>;
74
+ /**
75
+ * The framework's `access` option from Better Auth built with `aventaraAccess()` in
76
+ * its plugins: `createFramework({ ...config, access: accessOf(auth) })`. Refuses an
77
+ * instance without the plugin, and the configurations the plugin refuses.
78
+ */
79
+ export declare function accessOf<A extends AccessAuth>(given: A): AccessProviderOf<A>;
80
+ export {};
@@ -0,0 +1,100 @@
1
+ // `accessOf(auth)`: the framework's `access` option from a Better Auth instance built
2
+ // with `aventaraAccess()`. It satisfies the framework's access provider by shape
3
+ // (`policy`, `state`) without importing it, as `createAccess`'s instance does, and
4
+ // is built from the same modules: the engine with Access's built-in rules over
5
+ // Better Auth's tables, and the request state (a bearer token against the key set,
6
+ // or the session cookie from a trusted site). It checks `auth.options` as the
7
+ // plugin's `init` does, so a configuration Access refuses fails the framework's
8
+ // construction, not the first request. When the plugin serves the registry (the app
9
+ // is the hub), `registry` is the operator's API, as `createAccess`'s instance has it:
10
+ // it stores through the instance's own adapter, in this process, behind no route.
11
+ // With the plugin's `mcp`, it gives the framework's `mcp.authorization` too
12
+ // (`accessOf(auth).mcp`), and its state reads an agent's access token as
13
+ // `createAccess`'s does; with the plugin's `audience`, it takes only bearer tokens
14
+ // addressed to the service.
15
+ import { AccessError } from "../access/access.error.js";
16
+ import { guardedPolicy } from "../access/policies-block.checker.js";
17
+ import { requestIdentity, requestState } from "../access/request.state.js";
18
+ import { createTokenVerifier } from "../access/token.verifier.js";
19
+ import { createAccessTokenVerifier, mcpAuthorizationOf, } from "../oauth/access-token.verifier.js";
20
+ import { createRegistry, } from "../registry/access.registry.js";
21
+ import { pluginKernelOf } from "./access.plugin.js";
22
+ /**
23
+ * The framework's `access` option from Better Auth built with `aventaraAccess()` in
24
+ * its plugins: `createFramework({ ...config, access: accessOf(auth) })`. Refuses an
25
+ * instance without the plugin, and the configurations the plugin refuses.
26
+ */
27
+ export function accessOf(given) {
28
+ const auth = given;
29
+ const plugins = Array.isArray(auth?.options?.plugins)
30
+ ? auth.options.plugins
31
+ : [];
32
+ let own;
33
+ for (const plugin of plugins) {
34
+ if (own === undefined && pluginKernelOf(plugin) !== undefined) {
35
+ own = plugin;
36
+ }
37
+ }
38
+ const kernel = pluginKernelOf(own);
39
+ if (kernel === undefined) {
40
+ throw new AccessError("ACCESS_OPTIONS_INVALID", "accessOf takes a Better Auth instance built with aventaraAccess() in its plugins: `betterAuth({ plugins: [aventaraAccess({ policies })] })`.");
41
+ }
42
+ const { checked, resources } = kernel;
43
+ const start = kernel.start(auth.options, own);
44
+ let guarded;
45
+ const keys = async () => (await auth.api.getJwks());
46
+ const tokensFor = (resource) => createAccessTokenVerifier({
47
+ keys,
48
+ issuer: start.issuer,
49
+ resource,
50
+ userInfo: start.userInfo,
51
+ context: () => auth.$context,
52
+ claimsFor: (session) => kernel.claimsFor(() => auth.$context, session),
53
+ });
54
+ const agents = start.mcp === undefined ? undefined : tokensFor(start.mcp.resource);
55
+ const ownTokens = start.audience === undefined ? undefined : tokensFor(start.audience);
56
+ const provider = {
57
+ $Infer: {},
58
+ ...(agents === undefined || start.mcp === undefined
59
+ ? {}
60
+ : { mcp: mcpAuthorizationOf(agents, start.mcp.server) }),
61
+ state: requestState(requestIdentity({
62
+ auth: () => auth,
63
+ verifier: createTokenVerifier({
64
+ claims: checked.claims,
65
+ keys,
66
+ issuer: start.issuer,
67
+ tokenTtl: checked.tokenTtl,
68
+ }),
69
+ ...(agents === undefined ? {} : { agents }),
70
+ ...(ownTokens === undefined ? {} : { audience: ownTokens }),
71
+ kind: "web",
72
+ crossSite: start.crossSite,
73
+ claimsFor: (session) => kernel.claimsFor(() => auth.$context, session),
74
+ }), checked.principal),
75
+ ...(kernel.registry === undefined
76
+ ? {}
77
+ : {
78
+ registry: Object.freeze({
79
+ ...createRegistry(async () => (await auth.$context).adapter, kernel.registry).operator,
80
+ }),
81
+ }),
82
+ };
83
+ // Built on first read, which is the framework's construction: a `resources` key
84
+ // whose tables are missing refuses the app's start. Not enumerable, so a walk
85
+ // over the provider runs no startup check.
86
+ Object.defineProperty(provider, "policy", {
87
+ get: () => {
88
+ guarded ??= guardedPolicy({
89
+ checked,
90
+ auditLog: kernel.auditLog,
91
+ resources,
92
+ tables: start.facts.tables,
93
+ naming: start.facts.naming,
94
+ });
95
+ return guarded;
96
+ },
97
+ enumerable: false,
98
+ });
99
+ return provider;
100
+ }
@@ -0,0 +1,7 @@
1
+ export type { AccessIdentity } from "../access/claims.mapper.js";
2
+ export { type AccessAgent, agentOf } from "../mcp/agent.grant.js";
3
+ export type { AccessAgentIdentity, AccessMcpAuthorization, AccessMcpOptions, } from "../mcp/mcp-options.js";
4
+ export { type AccessCanError, type AccessCanResult, type AccessQuestion, type AventaraAccessClientOptions, type AventaraAccessClientPlugin, aventaraAccessClient, } from "./access.client-plugin.js";
5
+ export { type AventaraAccessOptions, type AventaraAccessPlugin, aventaraAccess, } from "./access.plugin.js";
6
+ export { type AccessAuth, type AccessProvider, type AccessProviderOf, accessOf, } from "./access.provider.js";
7
+ export type { AccessOrganizationBridge } from "./organization.bridge.js";
@@ -0,0 +1,4 @@
1
+ export { agentOf } from "../mcp/agent.grant.js";
2
+ export { aventaraAccessClient, } from "./access.client-plugin.js";
3
+ export { aventaraAccess, } from "./access.plugin.js";
4
+ export { accessOf, } from "./access.provider.js";
@@ -0,0 +1,66 @@
1
+ import type { AccessClaimsDeclaration, AccessClaimsSource } from "../access/claims.mapper.js";
2
+ /** The claims of a declaration that are strings, by name: those the bridge can fill. */
3
+ type StringClaimOf<C extends AccessClaimsDeclaration> = {
4
+ [K in keyof C]: C[K] extends "string" ? K : never;
5
+ }[keyof C] & string;
6
+ /**
7
+ * The `organization` option: which claims a member's role fills, and with what. A
8
+ * member whose roles `roles` does not name (or no member, or no active organization)
9
+ * gets neither claim.
10
+ */
11
+ export type AccessOrganizationBridge<C extends AccessClaimsDeclaration = AccessClaimsDeclaration> = {
12
+ /** The claim the role's mapped value fills: one `policies.claims` declares as `"string"`. */
13
+ readonly claim: StringClaimOf<C>;
14
+ /**
15
+ * The claim the organization's id fills, beside the role: one `policies.claims`
16
+ * declares as `"string"`. Each grant on the bridged role is held to it.
17
+ */
18
+ readonly organizationClaim: StringClaimOf<C>;
19
+ /**
20
+ * Better Auth organization roles (`owner`, `admin`, `member`, or the app's own
21
+ * static roles), in order, each with the value the claim takes: a member with
22
+ * several roles gets the value of the first one listed here.
23
+ */
24
+ readonly roles: readonly (readonly [role: string, value: string])[];
25
+ };
26
+ /** The bridge, checked: its two claims and its roles in order. */
27
+ export type CheckedBridge = {
28
+ readonly claim: string;
29
+ readonly organizationClaim: string;
30
+ readonly roles: readonly (readonly [role: string, value: string])[];
31
+ };
32
+ /** The `organization` option, checked against the declared claims; `undefined` when it is not given. */
33
+ export declare const checkBridge: (value: unknown, claims: AccessClaimsDeclaration) => CheckedBridge | undefined;
34
+ /**
35
+ * Refuses a bridge over Better Auth without its organization plugin, and over one
36
+ * that lets a member be made otherwise than the app decided: by an unverified email
37
+ * accepting an invitation, by any user creating an organization, or (with dynamic
38
+ * access control) under a role an organization's admins name.
39
+ */
40
+ export declare const checkBridgePlugins: (plugins: readonly unknown[], bridge: CheckedBridge) => void;
41
+ /** What the bridge reads of Better Auth's context: its database adapter. */
42
+ export type BridgeContext = {
43
+ readonly adapter: {
44
+ findOne(input: {
45
+ readonly model: string;
46
+ readonly where: readonly {
47
+ readonly field: string;
48
+ readonly value: string;
49
+ }[];
50
+ }): Promise<{
51
+ readonly [field: string]: unknown;
52
+ } | null>;
53
+ };
54
+ };
55
+ /**
56
+ * The bridged claims for a session: the mapped value of the member's role in the
57
+ * session's active organization, and that organization's id, read from Better Auth's
58
+ * store; `undefined` when there is no active organization, no membership, or no role
59
+ * the bridge maps. The member's roles are read as Better Auth reads them, split on
60
+ * `,` and taken as they are: a stored role with whitespace in it (`member, owner`) is
61
+ * one Better Auth does not grant, and gives nothing here.
62
+ */
63
+ export declare const bridgedClaims: (bridge: CheckedBridge, context: BridgeContext, source: AccessClaimsSource) => Promise<{
64
+ readonly [claim: string]: string;
65
+ } | undefined>;
66
+ export {};