@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,420 @@
1
+ // The checks Access holds Better Auth's options to, whichever way Access is in:
2
+ // `createAccess` runs them on its `betterAuth` block when it is built, and the
3
+ // `aventaraAccess()` plugin on the options Better Auth hands its `init` (and
4
+ // `accessOf(auth)` on `auth.options`, so a framework's construction refuses the same
5
+ // configuration before the first request). One module, so the two ways in cannot
6
+ // drift apart.
7
+ import { ME_PATH } from "../client/access-verbs.schema.js";
8
+ import { KIOSK_ENDPOINT_KEYS, KIOSK_PREFIX, } from "../kiosk/kiosk-verbs.schema.js";
9
+ import { isIpv4Literal, isRangeEdge, lanRangeOf, writtenHost, } from "../kiosk/lan-range.policy.js";
10
+ import { isRecord } from "../policies.compiler.js";
11
+ import { REGISTRY_ENDPOINT_NAMES, REGISTRY_ROUTE_PATHS, REGISTRY_ROUTES, } from "../registry/registry.endpoints.js";
12
+ import { AccessError } from "./access.error.js";
13
+ /** The id of the Access plugin: `aventaraAccess()`, or the one `createAccess` installs. */
14
+ export const ACCESS_PLUGIN_ID = "aventara-access";
15
+ /** The id of Better Auth's `jwt()` plugin. */
16
+ export const JWT_PLUGIN_ID = "jwt";
17
+ const refuse = (message) => {
18
+ throw new AccessError("ACCESS_OPTIONS_INVALID", message);
19
+ };
20
+ /**
21
+ * The endpoints of `jwt()`, which Access installs (beside the Access plugin under
22
+ * `createAccess`, inside it as `aventaraAccess()`): by name in Better Auth's API,
23
+ * and by path. A plugin of the app's that defines one would answer for it: hand out
24
+ * other keys, or tokens Access did not issue.
25
+ */
26
+ const JWT_ENDPOINTS = new Set([
27
+ "getJwks",
28
+ "getToken",
29
+ "signJWT",
30
+ "verifyJWT",
31
+ ]);
32
+ const JWT_PATHS = new Set(["/jwks", "/token"]);
33
+ /**
34
+ * The Access plugin's own `me` route, by name and path, which `createAccess` serves:
35
+ * a plugin of the app's that defines it would answer `$access.me()` with another
36
+ * identity.
37
+ */
38
+ const ACCESS_ENDPOINTS = new Set(["accessMe"]);
39
+ const ACCESS_PATHS = new Set([ME_PATH]);
40
+ /**
41
+ * The registry's routes, by name and by path (everything under `/access/v1`): a
42
+ * plugin of the app's that defines one would answer a service's push or pull, and
43
+ * read the key it sends.
44
+ */
45
+ const REGISTRY_ENDPOINTS = new Set(REGISTRY_ENDPOINT_NAMES);
46
+ const isRegistryPath = (path) => path === REGISTRY_ROUTES || path.startsWith(`${REGISTRY_ROUTES}/`);
47
+ /**
48
+ * A route path as Better Auth's router reads it: one leading slash and no trailing
49
+ * one (`access/v1/key` is served at `/access/v1/key`). Every path is checked in this
50
+ * form, so no spelling of a route slips past the checks.
51
+ */
52
+ const routePath = (path) => `/${path.replace(/^\/+|\/+$/g, "")}`;
53
+ const REGISTRY_SEGMENTS = REGISTRY_ROUTES.split("/").filter((segment) => segment !== "");
54
+ /**
55
+ * Whether a middleware's path pattern can match a registry route: `**` matches the
56
+ * rest, a `*` or `:param` segment matches any one segment (any segment with a `*` is
57
+ * taken as one), and a pattern that reaches past `/access/v1` matches inside it.
58
+ */
59
+ const reachesRegistry = (pattern) => {
60
+ const segments = routePath(pattern)
61
+ .split("/")
62
+ .filter((segment) => segment !== "");
63
+ for (const [index, segment] of segments.entries()) {
64
+ if (segment.startsWith("**") || index >= REGISTRY_SEGMENTS.length) {
65
+ return true;
66
+ }
67
+ const any = segment.includes("*") || segment.startsWith(":");
68
+ if (!any && segment !== REGISTRY_SEGMENTS[index]) {
69
+ return false;
70
+ }
71
+ }
72
+ return segments.length >= REGISTRY_SEGMENTS.length;
73
+ };
74
+ /** Whether a route segment matches any one segment: a `:param`, or one with a `*`. */
75
+ const anySegment = (segment) => segment.startsWith(":") || segment.includes("*");
76
+ /**
77
+ * Whether an endpoint's path pattern can match a request one of the registry's
78
+ * routes also matches: segment by segment, a `:param` or `*` (on either side)
79
+ * matches any segment, `**` the rest, anything else itself (in any case). A route
80
+ * with parameters where the registry's has static segments (`/:a/v1/key`) is
81
+ * refused, not left to the router's static-first order.
82
+ */
83
+ const overlapsRegistry = (pattern) => {
84
+ const segments = routePath(pattern)
85
+ .split("/")
86
+ .filter((segment) => segment !== "");
87
+ return REGISTRY_ROUTE_PATHS.some((registryPath) => {
88
+ const route = registryPath.split("/").filter((segment) => segment !== "");
89
+ for (let index = 0; index < segments.length; index += 1) {
90
+ const segment = segments[index];
91
+ if (segment.startsWith("**")) {
92
+ return true;
93
+ }
94
+ const other = route[index];
95
+ if (other === undefined) {
96
+ return false;
97
+ }
98
+ if (!anySegment(segment) &&
99
+ !anySegment(other) &&
100
+ segment.toLowerCase() !== other.toLowerCase()) {
101
+ return false;
102
+ }
103
+ }
104
+ return segments.length === route.length;
105
+ });
106
+ };
107
+ /** A plugin id as Better Auth's checks could take it: no case, no surrounding space. */
108
+ export const normalisedId = (id) => typeof id === "string" ? id.trim().toLowerCase() : undefined;
109
+ /**
110
+ * The plugins of a Better Auth configuration, refusing one the installer puts in
111
+ * itself (under any case or spacing of its id) and one that defines `jwt()`'s
112
+ * endpoints. `ACCESS_PLUGIN_DUPLICATE`, with a sentence naming the plugin.
113
+ */
114
+ export const refuseInstalledPlugins = (plugins, installer) => {
115
+ const list = plugins ?? [];
116
+ if (!Array.isArray(list)) {
117
+ return refuse(`\`${installer.at}\` is an array of Better Auth plugins.`);
118
+ }
119
+ for (const plugin of list) {
120
+ if (plugin === installer.own) {
121
+ continue;
122
+ }
123
+ const id = plugin?.id;
124
+ const installed = normalisedId(id);
125
+ if (installed !== undefined &&
126
+ Object.hasOwn(installer.installs, installed)) {
127
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `${installer.at} holds ${installer.installs[installed]} (plugin id "${String(id)}"), which ${installer.by} installs itself: remove it from ${installer.at}.`);
128
+ }
129
+ if (installer.registry === true && Array.isArray(plugin?.middlewares)) {
130
+ for (const middleware of plugin.middlewares) {
131
+ const path = middleware?.path;
132
+ if (typeof path === "string" && reachesRegistry(path)) {
133
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `${installer.at} holds plugin "${String(id)}", whose middleware at ${path} reaches the registry's routes, which ${installer.by} serves: it would answer a service's push or pull and read its key. Remove the plugin, or narrow its path.`);
134
+ }
135
+ }
136
+ }
137
+ const endpoints = plugin?.endpoints;
138
+ if (!isRecord(endpoints)) {
139
+ continue;
140
+ }
141
+ for (const [name, endpoint] of Object.entries(endpoints)) {
142
+ const path = endpoint?.path;
143
+ const at = typeof path === "string" ? routePath(path) : undefined;
144
+ if (installer.me === true &&
145
+ (ACCESS_ENDPOINTS.has(name) ||
146
+ (at !== undefined && ACCESS_PATHS.has(at)))) {
147
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `${installer.at} holds plugin "${String(id)}", whose endpoint \`${name}\`${typeof path === "string" ? ` (${path})` : ""} is Access's own \`me\` route: it would answer \`$access.me()\` with another identity. Remove the plugin, or rename its endpoint.`);
148
+ }
149
+ if (installer.kiosk === true &&
150
+ (KIOSK_ENDPOINT_KEYS.has(name) ||
151
+ (at !== undefined && `${at}/`.startsWith(KIOSK_PREFIX)))) {
152
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `${installer.at} holds plugin "${String(id)}", whose endpoint \`${name}\`${typeof path === "string" ? ` (${path})` : ""} is one of the kiosk profile's routes: it would answer for a PIN switch or a device. Remove the plugin, or rename its endpoint.`);
153
+ }
154
+ if (installer.registry === true &&
155
+ (REGISTRY_ENDPOINTS.has(name) ||
156
+ (at !== undefined && (isRegistryPath(at) || overlapsRegistry(at))))) {
157
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `${installer.at} holds plugin "${String(id)}", whose endpoint \`${name}\`${typeof path === "string" ? ` (${path})` : ""} is one of the registry's routes, which ${installer.by} serves: it would answer a service's push or pull and read its key. Remove the plugin, or rename its endpoint.`);
158
+ }
159
+ if (JWT_ENDPOINTS.has(name) || (at !== undefined && JWT_PATHS.has(at))) {
160
+ throw new AccessError("ACCESS_PLUGIN_DUPLICATE", `${installer.at} holds plugin "${String(id)}", whose endpoint \`${name}\`${typeof path === "string" ? ` (${path})` : ""} is one of jwt()'s, which ${installer.by} installs: it would answer for Access's key set or tokens. Remove the plugin, or rename its endpoint.`);
161
+ }
162
+ }
163
+ }
164
+ return list;
165
+ };
166
+ /**
167
+ * The issuer and audience of every token: the origin of Better Auth's base URL,
168
+ * fixed when Access starts, as Better Auth resolves it (`baseURL`, else its
169
+ * environment variables, `BETTER_AUTH_URL` first), or a dynamic `baseURL`'s
170
+ * `fallback`. Better Auth's own default follows the request's host when the base URL
171
+ * is dynamic or unset; a fixed one keeps a token from one host or app from passing
172
+ * as another's.
173
+ */
174
+ export const issuerOf = (block, modules, baseURLAt) => new URL(baseURLOf(block, modules, baseURLAt)).origin;
175
+ /** Better Auth's base URL as {@link issuerOf} resolves it, as it was written (before the URL parser reads it). */
176
+ export const baseURLOf = (block, modules, baseURLAt) => {
177
+ const { baseURL, basePath } = block;
178
+ const path = typeof basePath === "string" ? basePath : undefined;
179
+ const resolved = modules.isDynamicBaseURLConfig(baseURL)
180
+ ? typeof baseURL.fallback === "string"
181
+ ? modules.getBaseURL(baseURL.fallback, path)
182
+ : undefined
183
+ : modules.getBaseURL(typeof baseURL === "string" ? baseURL : undefined, path);
184
+ if (resolved === undefined || resolved === "") {
185
+ return refuse(`Access issues its tokens with a fixed issuer and audience, the app's base URL, and Better Auth has none: set \`${baseURLAt}\` (or \`BETTER_AUTH_URL\`), or give a dynamic \`baseURL\` its \`fallback\`.`);
186
+ }
187
+ return resolved;
188
+ };
189
+ /** Whether `trustedOrigins` names any origin: a list with one, or a function. */
190
+ const namesTrustedOrigins = (trusted) => typeof trusted === "function" ||
191
+ (Array.isArray(trusted) && trusted.some((origin) => origin));
192
+ /**
193
+ * Whether the session cookie is `sameSite: "none"`: what Better Auth sets it with,
194
+ * `advanced.cookies.session_token.attributes` over `advanced.defaultCookieAttributes`.
195
+ * Refuses it when Better Auth trusts no origin: a session cookie a browser sends
196
+ * from any site is read only from the origins Better Auth trusts, so there must be
197
+ * some.
198
+ */
199
+ export const crossSiteSessionCookie = (block, trustedOriginsAt) => {
200
+ const advanced = (block.advanced ?? {});
201
+ const sameSite = advanced.cookies?.session_token?.attributes?.sameSite ??
202
+ advanced.defaultCookieAttributes?.sameSite;
203
+ const crossSite = typeof sameSite === "string" && sameSite.toLowerCase() === "none";
204
+ if (crossSite &&
205
+ !namesTrustedOrigins(block.trustedOrigins) &&
206
+ !globalThis.process?.env?.BETTER_AUTH_TRUSTED_ORIGINS) {
207
+ refuse(`the session cookie is \`sameSite: "none"\`, so a browser sends it on requests from any site, and \`${trustedOriginsAt}\` names none to accept them from. Keep the default (\`"lax"\`), or list the origins your app is served from in \`trustedOrigins\`.`);
208
+ }
209
+ return crossSite;
210
+ };
211
+ /** Whether Better Auth's cookie cache is on: a session read from its cookie may have been revoked since. */
212
+ export const cookieCacheOn = (block) => block.session
213
+ ?.cookieCache?.enabled === true;
214
+ /**
215
+ * The kiosk profile's refusals of Better Auth's options, each a way around the
216
+ * device binding of a session:
217
+ *
218
+ * - the cookie cache: a session the device's lock ended would still be answered
219
+ * from its cookie;
220
+ * - `secondaryStorage` without `session.storeSessionInDatabase`: a device's sessions
221
+ * must be found in the database to be ended;
222
+ * - `bearer()`: it hands every session's token to page code (`set-auth-token`) and
223
+ * reads it back from `Authorization`, where no device credential goes with it;
224
+ * - `multiSession()`: it keeps every session the browser signed into and switches
225
+ * back to one by its token, so a cashier would restore the manager before them;
226
+ * - `crossSubDomainCookies` and `SameSite=None`: the device's credential and the
227
+ * session would be sent to other hosts, or from other sites;
228
+ * - a plain http: base URL but on a loopback host, or (`profile.insecureLan`) on an
229
+ * IPv4 address inside one of the till network's ranges: the device's credential
230
+ * and the sessions would cross the network in the clear. On such a LAN address no
231
+ * cookie may be `Secure` (or named `__Secure-` or `__Host-`): a browser drops it
232
+ * over http, and the till would silently hold no session.
233
+ *
234
+ * Answers the ranges an http: base URL is served on under `profile.insecureLan`,
235
+ * for the startup warning; `undefined` when it is https: or loopback.
236
+ */
237
+ export const refuseKioskOptions = (block, plugins, cookieCache,
238
+ /** The app's base URL as written (`baseURLOf`), and where the app set it. */
239
+ written, baseURLAt,
240
+ /** `profile.insecureLan`, read. */
241
+ insecureLan) => {
242
+ if (cookieCache) {
243
+ refuse("the kiosk profile ends a PIN session the moment its device locks, and Better Auth's cookie cache would keep answering it from the cookie: turn `betterAuth.session.cookieCache` off.");
244
+ }
245
+ const session = (block.session ?? {});
246
+ if (block.secondaryStorage !== undefined &&
247
+ session.storeSessionInDatabase !== true) {
248
+ refuse("the kiosk profile finds a device's sessions in the database to end them, and with `secondaryStorage` Better Auth keeps them out of it: set `betterAuth.session.storeSessionInDatabase: true`.");
249
+ }
250
+ for (const plugin of plugins) {
251
+ const id = normalisedId(plugin?.id);
252
+ if (id === "bearer") {
253
+ refuse("the kiosk profile binds every session on an enrolled device to that device, and Better Auth's `bearer()` hands a session's token to page code and takes it back from an `Authorization` header, where the device's credential does not go: remove `bearer()` from `betterAuth.plugins`, or run the bearer clients on another instance.");
254
+ }
255
+ if (id === "multi-session") {
256
+ refuse("the kiosk profile ends the session before a PIN switch, and Better Auth's `multiSession()` keeps it in the browser and switches back to it, so a cashier would restore the manager before them: remove `multiSession()` from `betterAuth.plugins`.");
257
+ }
258
+ }
259
+ const advanced = (isRecord(block.advanced) ? block.advanced : {});
260
+ const crossSubDomain = advanced.crossSubDomainCookies;
261
+ if (isRecord(crossSubDomain) && crossSubDomain.enabled === true) {
262
+ refuse("the kiosk profile keeps a device's credential and its sessions on the app's own host, and `betterAuth.advanced.crossSubDomainCookies` would send them to every subdomain: turn it off.");
263
+ }
264
+ const sameSiteNone = (attributes) => isRecord(attributes) &&
265
+ typeof attributes.sameSite === "string" &&
266
+ attributes.sameSite.trim().toLowerCase() === "none";
267
+ const cookies = isRecord(advanced.cookies) ? advanced.cookies : {};
268
+ const named = Object.entries(cookies).find(([, cookie]) => sameSiteNone(isRecord(cookie) ? cookie.attributes : undefined));
269
+ if (sameSiteNone(advanced.defaultCookieAttributes) || named !== undefined) {
270
+ refuse(`the kiosk profile's device credential and sessions are not sent from other sites, and \`SameSite=None\` would send them${named === undefined ? "" : ` (\`betterAuth.advanced.cookies.${named[0]}\`)`}: use \`"lax"\`, the default.`);
271
+ }
272
+ const base = new URL(written);
273
+ const plainBase = base.protocol !== "https:" && !isLoopback(base);
274
+ // A dynamic base URL is served on each allowed host, by the request's protocol
275
+ // unless `protocol: "https"` fixes it: its fallback alone says nothing of them.
276
+ const dynamic = isRecord(block.baseURL) ? block.baseURL : undefined;
277
+ if (dynamic !== undefined) {
278
+ const hosts = Array.isArray(dynamic.allowedHosts)
279
+ ? dynamic.allowedHosts
280
+ : [];
281
+ const plainHosts = dynamic.protocol !== "https" &&
282
+ hosts.some((host) => !isLoopbackPattern(host));
283
+ if (plainHosts || plainBase) {
284
+ return refuse(insecureLan !== undefined
285
+ ? `\`profile.insecureLan\` takes a fixed \`${baseURLAt}\`, the server's address on the till network (such as \`"http://192.168.1.10:3000"\`): a dynamic one serves plain http: on hosts this check does not read.`
286
+ : plainHosts
287
+ ? `the kiosk profile's device credential and PIN sessions are cookies, and the dynamic \`${baseURLAt}\` serves its allowed hosts over plain http: (its \`protocol\` is ${dynamic.protocol === undefined ? "unset, which follows the request" : JSON.stringify(dynamic.protocol)}), where they would cross the network in the clear: set \`protocol: "https"\` (http: is taken only when every allowed host is a loopback host: localhost, 127.0.0.0/8 or [::1]).`
288
+ : `the kiosk profile's device credential and PIN sessions are cookies, and on \`${base.origin}\` (plain http:, the dynamic \`${baseURLAt}\`'s fallback) they would cross the network in the clear: make its fallback the app's https: URL.`);
289
+ }
290
+ return undefined;
291
+ }
292
+ if (!plainBase) {
293
+ return undefined;
294
+ }
295
+ if (insecureLan === undefined) {
296
+ return refuse(`the kiosk profile's device credential and PIN sessions are cookies, and on \`${base.origin}\` (plain http:) they would cross the network in the clear: serve the app on https: and set \`${baseURLAt}\` to its https: URL (http: is taken only on a loopback host: localhost, 127.0.0.0/8 or [::1], or, for a till network of its own, on an address \`profile.insecureLan\` lists).`);
297
+ }
298
+ const listed = insecureLan.map((range) => range.cidr).join(", ");
299
+ if (!isIpv4Literal(base.hostname)) {
300
+ return refuse(`\`profile.insecureLan\` takes a plain http: base URL on an IPv4 address only, and \`${base.origin}\` names a host by name or by IPv6, which a resolver or the network could point elsewhere: set \`${baseURLAt}\` to the server's IPv4 address on the till network, inside ${listed} (such as \`http://192.168.1.10:3000\`).`);
301
+ }
302
+ // The address as the operator reads it is the one the browser uses.
303
+ const host = writtenHost(written);
304
+ if (host !== base.hostname) {
305
+ return refuse(`\`${baseURLAt}\` writes the server's address as \`${host ?? written}\`, which the URL parser (and a browser) reads as \`${base.hostname}\`: write it so, \`${base.origin}\`.`);
306
+ }
307
+ const range = lanRangeOf(base.hostname, insecureLan);
308
+ if (range === undefined) {
309
+ return refuse(`\`${base.origin}\` is plain http: and outside \`profile.insecureLan\` (${listed}): set \`${baseURLAt}\` to the server's address inside one of them, or list the till network's range.`);
310
+ }
311
+ if (isRangeEdge(base.hostname, range)) {
312
+ return refuse(`\`${base.origin}\` is the network or broadcast address of \`${range.cidr}\`, which no server has: set \`${baseURLAt}\` to the server's own address.`);
313
+ }
314
+ const secureCookie = secureCookieSetting(advanced);
315
+ if (secureCookie !== undefined) {
316
+ return refuse(`\`betterAuth.advanced.${secureCookie}\` makes a cookie \`Secure\` (or names it \`__Secure-\` or \`__Host-\`), and a browser on \`${base.origin}\` (plain http:) drops such a cookie, so the till would never hold its session or its credential: remove it under \`profile.insecureLan\`.`);
317
+ }
318
+ return insecureLan;
319
+ };
320
+ /** A secure-cookie prefix, which a browser takes only from an https: page (or a loopback one). */
321
+ const SECURE_PREFIX = /^__(?:secure|host)-/i;
322
+ /**
323
+ * Where Better Auth's `advanced` options make a cookie `Secure` or give it a name a
324
+ * browser takes only over https:, as a path under `advanced`; `undefined` when none
325
+ * does.
326
+ */
327
+ const secureCookieSetting = (advanced) => {
328
+ if (advanced.useSecureCookies === true) {
329
+ return "useSecureCookies";
330
+ }
331
+ if (typeof advanced.cookiePrefix === "string" &&
332
+ SECURE_PREFIX.test(advanced.cookiePrefix)) {
333
+ return "cookiePrefix";
334
+ }
335
+ if (isRecord(advanced.defaultCookieAttributes) &&
336
+ advanced.defaultCookieAttributes.secure === true) {
337
+ return "defaultCookieAttributes.secure";
338
+ }
339
+ const cookies = isRecord(advanced.cookies) ? advanced.cookies : {};
340
+ for (const [name, cookie] of Object.entries(cookies)) {
341
+ if (!isRecord(cookie)) {
342
+ continue;
343
+ }
344
+ if (isRecord(cookie.attributes) && cookie.attributes.secure === true) {
345
+ return `cookies.${name}.attributes.secure`;
346
+ }
347
+ if (typeof cookie.name === "string" && SECURE_PREFIX.test(cookie.name)) {
348
+ return `cookies.${name}.name`;
349
+ }
350
+ }
351
+ return undefined;
352
+ };
353
+ /**
354
+ * Whether a dynamic base URL's allowed-host pattern (`host[:port]`, perhaps with a
355
+ * scheme) names a loopback host and nothing else: a wildcard never does.
356
+ */
357
+ const isLoopbackPattern = (pattern) => {
358
+ if (typeof pattern !== "string" || /[*?]/.test(pattern)) {
359
+ return false;
360
+ }
361
+ try {
362
+ return isLoopback(new URL(`http://${pattern.replace(/^https?:\/\//i, "")}`));
363
+ }
364
+ catch {
365
+ return false;
366
+ }
367
+ };
368
+ /** Whether a URL's host is a loopback address: `localhost`, 127.0.0.0/8 or `::1`. */
369
+ const isLoopback = (url) => url.hostname === "localhost" ||
370
+ url.hostname === "[::1]" ||
371
+ /^127\.(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})$/.test(url.hostname);
372
+ /**
373
+ * Better Auth's Prisma adapter's naming, when `database` is that adapter: read from
374
+ * the adapter the factory builds (its id, and `usePlural`). Building it touches no
375
+ * database, and Better Auth builds its own from the factory again when it starts,
376
+ * with its own options, so this one leaves nothing behind.
377
+ */
378
+ export const prismaAdapterOf = (database, options) => {
379
+ if (typeof database !== "function") {
380
+ return undefined;
381
+ }
382
+ const adapter = database(options);
383
+ return adapter?.id === "prisma"
384
+ ? { usePlural: adapter.options?.adapterConfig?.usePlural === true }
385
+ : undefined;
386
+ };
387
+ /** The id of Better Auth's organization plugin. */
388
+ const ORGANIZATION_PLUGIN_ID = "organization";
389
+ /** The organization plugin among `plugins`, with its options; `undefined` when it is not installed. */
390
+ export const organizationPluginOf = (plugins) => {
391
+ for (const plugin of plugins) {
392
+ const { id, options } = (plugin ?? {});
393
+ if (normalisedId(id) === ORGANIZATION_PLUGIN_ID) {
394
+ return { options: isRecord(options) ? options : {} };
395
+ }
396
+ }
397
+ return undefined;
398
+ };
399
+ /**
400
+ * Refuses an organization plugin whose invitations a session with an unverified
401
+ * email may accept, when Access trusts its memberships (`reads` says how).
402
+ *
403
+ * Better Auth's advisory GHSA-fmh4-wcc4-5jm3: from better-auth 1.6.14 on, with
404
+ * Better Auth's own invitation ids, `acceptInvitation` takes a session whose email
405
+ * matches the invitation's and is not verified, unless the organization plugin sets
406
+ * `requireEmailVerificationOnInvitation: true`. Anyone who signs up with the invited
407
+ * address first and learns the invitation's id (a forwarded email, an admin screen,
408
+ * an invitation list) joins at the invited role. Better Auth ships no fix (the
409
+ * advisory lists `>= 1.6.14` with none; the GitHub advisory database's entry still
410
+ * says `< 1.6.11` only), so Access refuses to trust a membership the plugin let in
411
+ * that way. Fail closed: only `true`, given explicitly, passes.
412
+ */
413
+ export const refuseUnverifiedInvitations = (plugins, reads) => {
414
+ const organization = organizationPluginOf(plugins);
415
+ if (organization === undefined ||
416
+ organization.options.requireEmailVerificationOnInvitation === true) {
417
+ return;
418
+ }
419
+ refuse(`${reads} trusts the organization plugin's memberships, and with its options a signed-up user whose email is not verified can accept an invitation sent to that address (Better Auth advisory GHSA-fmh4-wcc4-5jm3, unfixed as of better-auth 1.7.14). Pass \`organization({ requireEmailVerificationOnInvitation: true })\`.`);
420
+ };
@@ -0,0 +1,67 @@
1
+ import type { BetterAuthOptions, betterAuth } from "better-auth";
2
+ import type { createAuthEndpoint } from "better-auth/api";
3
+ import type { BetterAuthAPIError, BetterAuthBaseURL, BetterAuthMiddlewareFactory, BetterAuthOAuthStateReader, BetterAuthPasswords, BetterAuthSessionCookies, BetterAuthSessionReader } from "./better-auth.require.js";
4
+ /**
5
+ * Better Auth's `jwt()`, as Access calls it: the options Access gives it in, the
6
+ * parts of the plugin Access installs out. Its own signature is generic in its
7
+ * options, and the types Access states name `jwt()`'s parts by its default.
8
+ */
9
+ export type JwtFactory = (options: {
10
+ /** `true` on the kiosk profile: `/get-session` hands out no token beside the session. */
11
+ readonly disableSettingJwtHeader?: boolean;
12
+ readonly jwt: {
13
+ readonly issuer: string | undefined;
14
+ readonly audience: string | undefined;
15
+ readonly expirationTime: string;
16
+ readonly definePayload: (source: {
17
+ readonly user: {
18
+ readonly id: string;
19
+ };
20
+ readonly session: {
21
+ readonly [field: string]: unknown;
22
+ };
23
+ }) => Promise<unknown>;
24
+ };
25
+ }) => {
26
+ readonly id: string;
27
+ readonly version: string;
28
+ readonly endpoints: unknown;
29
+ readonly hooks: unknown;
30
+ readonly schema: unknown;
31
+ };
32
+ /** What `createAccess` takes from Better Auth. */
33
+ export type BetterAuthModules = {
34
+ readonly betterAuth: typeof betterAuth;
35
+ /** Better Auth's tables for a set of options: its own, and its plugins'. */
36
+ readonly getAuthTables: (options: BetterAuthOptions) => unknown;
37
+ /** The base URL Better Auth resolves: the one given, else its environment variables'. */
38
+ readonly getBaseURL: BetterAuthBaseURL;
39
+ readonly isDynamicBaseURLConfig: (baseURL: unknown) => boolean;
40
+ readonly jwt: JwtFactory;
41
+ readonly createAuthEndpoint: typeof createAuthEndpoint;
42
+ /** A hook's handler, as plugins write theirs. */
43
+ readonly createAuthMiddleware: BetterAuthMiddlewareFactory;
44
+ /** The error a hook refuses a request with. */
45
+ readonly APIError: BetterAuthAPIError;
46
+ /** What hashing passwords takes: Node.js's `node:crypto` and Better Auth's scrypt verify. */
47
+ readonly passwords: BetterAuthPasswords;
48
+ /**
49
+ * The origin check Better Auth's router runs before every route (the `Origin` of
50
+ * a request with a cookie, and its `callbackURL` and `redirectTo`, against
51
+ * `trustedOrigins`), as a hook's handler.
52
+ */
53
+ readonly originCheckMiddleware: unknown;
54
+ /** The client address Better Auth's options read from a request's headers (`advanced.ipAddress`). */
55
+ readonly getIP: (headers: Headers, options: unknown) => string | null;
56
+ /** The session an endpoint's request carries, as Better Auth's own routes read it. */
57
+ readonly getSessionFromCtx: BetterAuthSessionReader;
58
+ /** The OAuth sign-in state of the request being handled. */
59
+ readonly getOAuthState: BetterAuthOAuthStateReader;
60
+ /** Better Auth's session cookies: set one for a new session, or delete them. */
61
+ readonly cookies: BetterAuthSessionCookies;
62
+ };
63
+ /**
64
+ * The installed Better Auth, checked against {@link BETTER_AUTH_RANGE}. Throws
65
+ * `ACCESS_BETTER_AUTH_VERSION` when it is missing or of a version outside it.
66
+ */
67
+ export declare const loadBetterAuth: () => BetterAuthModules;
@@ -0,0 +1,65 @@
1
+ // Loads Better Auth when `createAccess` runs, and only then. `better-auth` is an
2
+ // optional peer: the engine (`definePolicies`, `createAccessEngine`) never needs it,
3
+ // so importing `@aventara/access` must work without it installed. The load goes
4
+ // through the package's own `#better-auth.require` import: under the `node` condition
5
+ // it is Node.js's `require` (`better-auth.require.node.ts`), which a bundler for
6
+ // Node.js also reads, so a Next.js server bundle carries Better Auth; everywhere else
7
+ // it names no Node.js API and loads nothing. The load is synchronous: it reads Better
8
+ // Auth's ES modules into the same module map an `import` uses, so the app's
9
+ // `import { username } from "better-auth/plugins"` and the instance built here share
10
+ // one copy.
11
+ //
12
+ // Access runs on Better Auth 1.7.12 and its later 1.7 patches (`BETTER_AUTH_RANGE`).
13
+ // Another version is refused at construction, before any request runs on it.
14
+ import { requireBetterAuth } from "#better-auth.require";
15
+ import { AccessError } from "./access.error.js";
16
+ import { BETTER_AUTH_RANGE, BETTER_AUTH_RANGE_SAID, inBetterAuthRange, } from "./better-auth.range.js";
17
+ const INSTALL = `npm install better-auth@${BETTER_AUTH_RANGE}`;
18
+ let loaded;
19
+ const isMissing = (error) => {
20
+ const { code, message } = (error ?? {});
21
+ return ((code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") &&
22
+ typeof message === "string" &&
23
+ /['"]better-auth(?:\/[^'"]*)?['"]/.test(message));
24
+ };
25
+ /**
26
+ * The installed Better Auth, checked against {@link BETTER_AUTH_RANGE}. Throws
27
+ * `ACCESS_BETTER_AUTH_VERSION` when it is missing or of a version outside it.
28
+ */
29
+ export const loadBetterAuth = () => {
30
+ if (loaded !== undefined) {
31
+ return loaded;
32
+ }
33
+ const required = requireBetterAuth();
34
+ if (required === undefined) {
35
+ throw new AccessError("ACCESS_BETTER_AUTH_VERSION", "createAccess loads better-auth through Node.js's module loader, which this runtime does not provide; run it on Node.js ^22.23.4 || >=24.22.1.");
36
+ }
37
+ if ("error" in required) {
38
+ if (isMissing(required.error)) {
39
+ throw new AccessError("ACCESS_BETTER_AUTH_VERSION", `createAccess runs on better-auth ${BETTER_AUTH_RANGE}, which is not installed: ${INSTALL}`);
40
+ }
41
+ throw required.error;
42
+ }
43
+ const { core, plugin, api, cookies, passwords } = required;
44
+ const installed = plugin.jwt().version;
45
+ if (!inBetterAuthRange(installed)) {
46
+ throw new AccessError("ACCESS_BETTER_AUTH_VERSION", `createAccess runs on better-auth ${BETTER_AUTH_RANGE_SAID}, and this app has better-auth ${installed ?? "of an unknown version"}: ${INSTALL}`);
47
+ }
48
+ loaded = {
49
+ betterAuth: core.betterAuth,
50
+ getAuthTables: core.getAuthTables,
51
+ getBaseURL: core.getBaseURL,
52
+ isDynamicBaseURLConfig: core.isDynamicBaseURLConfig,
53
+ jwt: plugin.jwt,
54
+ createAuthEndpoint: api.createAuthEndpoint,
55
+ createAuthMiddleware: api.createAuthMiddleware,
56
+ APIError: api.APIError,
57
+ passwords,
58
+ originCheckMiddleware: api.originCheckMiddleware,
59
+ getIP: api.getIP,
60
+ getSessionFromCtx: api.getSessionFromCtx,
61
+ getOAuthState: api.getOAuthState,
62
+ cookies,
63
+ };
64
+ return loaded;
65
+ };
@@ -0,0 +1,9 @@
1
+ /** The Better Auth versions Access runs on, as npm writes the range: `~1.7.12`. */
2
+ export declare const BETTER_AUTH_RANGE: string;
3
+ /** {@link BETTER_AUTH_RANGE} in words, as a refusal names it: `~1.7.12 (1.7.12 or a later 1.7 patch)`. */
4
+ export declare const BETTER_AUTH_RANGE_SAID: string;
5
+ /**
6
+ * Whether `version` is in {@link BETTER_AUTH_RANGE}: a release (no prerelease or
7
+ * build tag) of the floor's major and minor, at the floor's patch or later.
8
+ */
9
+ export declare const inBetterAuthRange: (version: unknown) => version is string;
@@ -0,0 +1,28 @@
1
+ // The Better Auth versions this release of Access runs on: 1.7.12 and every later
2
+ // 1.7 patch. A patch is no breaking change, so Access takes it as it ships; a minor
3
+ // or a prerelease is refused. Better Auth releases `better-auth`,
4
+ // `@better-auth/oauth-provider` and `@better-auth/cimd` together under one version,
5
+ // so the one range holds for the three. The package's peer ranges are this range,
6
+ // and the gates hold them to it.
7
+ /** The oldest Better Auth Access runs on; every later patch of its minor is taken. */
8
+ const FLOOR = { major: 1, minor: 7, patch: 12 };
9
+ /** The Better Auth versions Access runs on, as npm writes the range: `~1.7.12`. */
10
+ export const BETTER_AUTH_RANGE = `~${FLOOR.major}.${FLOOR.minor}.${FLOOR.patch}`;
11
+ /** {@link BETTER_AUTH_RANGE} in words, as a refusal names it: `~1.7.12 (1.7.12 or a later 1.7 patch)`. */
12
+ export const BETTER_AUTH_RANGE_SAID = `${BETTER_AUTH_RANGE} (${FLOOR.major}.${FLOOR.minor}.${FLOOR.patch} or a later ${FLOOR.major}.${FLOOR.minor} patch)`;
13
+ /**
14
+ * Whether `version` is in {@link BETTER_AUTH_RANGE}: a release (no prerelease or
15
+ * build tag) of the floor's major and minor, at the floor's patch or later.
16
+ */
17
+ export const inBetterAuthRange = (version) => {
18
+ if (typeof version !== "string") {
19
+ return false;
20
+ }
21
+ const parts = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
22
+ if (parts === null) {
23
+ return false;
24
+ }
25
+ return (Number(parts[1]) === FLOOR.major &&
26
+ Number(parts[2]) === FLOOR.minor &&
27
+ Number(parts[3]) >= FLOOR.patch);
28
+ };