@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,40 @@
1
+ // `avaccess verify`: the pulled files on disk against `avAccess.lock.json`, offline.
2
+ // It reads the lock and the files it pins and runs the engine's own check
3
+ // (`lock.verifier.ts`): no registry, no network. It belongs at the front of a
4
+ // project's `aventara:prepare` script, so a pulled file edited by hand stops the
5
+ // build before anything is generated from it.
6
+ import { foreignOwnersOf, verifyLocked } from "../lock/lock.verifier.js";
7
+ import { LOCK_FILE } from "../lock/lock-contract.js";
8
+ import { environmentOf, verifyBeaconPins } from "./beacon-pins.file.js";
9
+ import { CliFailure, codeOf, EXIT_FAILED, EXIT_OK, messageOf, positionals, } from "./cli.context.js";
10
+ import { readForeignPinned, readLock, readPinned } from "./lock.file.js";
11
+ export const verifyCommand = async (context) => {
12
+ positionals(context, "avaccess verify [--env <environment>]", 0, 0);
13
+ const lock = await readLock(context.cwd);
14
+ const policies = await readPinned(context.cwd, lock.policies);
15
+ const guardrails = lock.guardrails === null
16
+ ? undefined
17
+ : await readPinned(context.cwd, lock.guardrails);
18
+ try {
19
+ verifyLocked(lock, {
20
+ namespace: policies
21
+ ?.namespace,
22
+ policies,
23
+ guardrails,
24
+ });
25
+ }
26
+ catch (error) {
27
+ throw new CliFailure(EXIT_FAILED, messageOf(error), codeOf(error));
28
+ }
29
+ await readForeignPinned(context.cwd, lock);
30
+ const owners = foreignOwnersOf(policies);
31
+ const environments = await verifyBeaconPins(context.cwd, lock.namespace, owners, environmentOf(context));
32
+ context.out(`verified against ${LOCK_FILE}: ${lock.policies.path} (${lock.policies.hash})${lock.guardrails === null
33
+ ? ", and no guardrails pinned"
34
+ : ` and ${lock.guardrails.path} (${lock.guardrails.hash})`}${lock.foreign === undefined
35
+ ? ""
36
+ : `, and the foreign facts ${lock.foreign.path} (${lock.foreign.hash})`}${environments.length === 0
37
+ ? ""
38
+ : `; the beacon pins of ${environments.map((environment) => `"${environment}"`).join(", ")} cover what the rules read`}`);
39
+ return EXIT_OK;
40
+ };
@@ -0,0 +1,92 @@
1
+ import type { AccessClaimsDeclaration } from "../access/claims.mapper.js";
2
+ import { type AccessProfileKind } from "../access/profile.policy.js";
3
+ import type { SignInIdentifier } from "../audit/audit.log.js";
4
+ import { type AccessKioskVerbName } from "../kiosk/kiosk-verbs.schema.js";
5
+ import { type AccessVerbSchema } from "./access-verbs.schema.js";
6
+ import type { LimitedCall, LimitSettle } from "./verb-rate.limiter.js";
7
+ /** The headers of a request, as the framework hands them to `call`. */
8
+ type RequestHeaders = {
9
+ get(name: string): string | null;
10
+ has(name: string): boolean;
11
+ forEach(callback: (value: string, name: string) => void): void;
12
+ };
13
+ /** One verb as `access.client` publishes it: its route, its shapes and its server call. */
14
+ type AccessClientVerb = {
15
+ readonly method: "GET" | "POST";
16
+ readonly path: string;
17
+ readonly args?: AccessVerbSchema;
18
+ readonly result: AccessVerbSchema;
19
+ call(input: {
20
+ readonly args: unknown;
21
+ readonly headers: RequestHeaders;
22
+ readonly request?: unknown;
23
+ }): Promise<unknown>;
24
+ };
25
+ /** The verbs `access.client` publishes: each one the instance serves. */
26
+ type AccessClientVerbs = {
27
+ signIn?: AccessClientVerb;
28
+ signUp?: AccessClientVerb;
29
+ signOut?: AccessClientVerb;
30
+ resetPassword?: AccessClientVerb;
31
+ me?: AccessClientVerb;
32
+ } & {
33
+ [V in AccessKioskVerbName]?: AccessClientVerb;
34
+ };
35
+ /** `access.client`. */
36
+ type AccessClient = {
37
+ readonly url: string;
38
+ readonly verbs: Readonly<AccessClientVerbs>;
39
+ };
40
+ /** Better Auth's refusal, as `call` throws one: a status, and a sentence. */
41
+ /** A refusal's body: Better Auth's `{ message, code }`. */
42
+ type RefusalBody = {
43
+ readonly message?: unknown;
44
+ readonly code?: unknown;
45
+ };
46
+ type RefusalError = new (status: string | number, body: RefusalBody, headers?: {
47
+ readonly [name: string]: string;
48
+ }) => Error;
49
+ /**
50
+ * Builds `access.client` for an instance: from its Better Auth options (`baseURL`,
51
+ * `basePath`, `emailAndPassword`, `disabledPaths`), its profile and its declared
52
+ * claims.
53
+ */
54
+ export declare const accessClient: (input: {
55
+ readonly block: {
56
+ readonly [key: string]: unknown;
57
+ };
58
+ readonly kind: AccessProfileKind;
59
+ readonly claims: AccessClaimsDeclaration;
60
+ /** Better Auth's base URL resolution, as `getBaseURL` resolves it. */
61
+ readonly baseOf: (baseURL: string | undefined, basePath: string | undefined) => string | undefined;
62
+ readonly isDynamicBaseURL: (baseURL: unknown) => boolean;
63
+ /** Better Auth's server API, built on first use. */
64
+ readonly api: () => unknown;
65
+ /** The request's identity, as `access.state` reads it. */
66
+ readonly identityOf: (headers: RequestHeaders, request: unknown) => Promise<unknown>;
67
+ /** Where `call` registers the requests it builds, for the Access plugin's origin check. */
68
+ readonly calls: WeakSet<object>;
69
+ readonly refusal: RefusalError;
70
+ /** How the sign-in route reads its identifier from a body (`email:<address>`). */
71
+ readonly identify: SignInIdentifier;
72
+ /** The rate limit on `signIn`, `signUp` and `resetPassword`: throws Better Auth's 429, else answers what clears the target's count once the call succeeds. */
73
+ readonly limit: (call: LimitedCall) => Promise<LimitSettle | undefined>;
74
+ /** Better Auth's context, once Better Auth is built: its plugins' `onRequest` hooks run with it. */
75
+ readonly context: () => Promise<unknown>;
76
+ }) => AccessClient;
77
+ /** Better Auth's `createAuthEndpoint`, as Access's `me` route calls it. */
78
+ export type MeEndpointFactory = (path: string, options: {
79
+ readonly method: "GET";
80
+ }, handler: (context: {
81
+ readonly headers?: Headers | undefined;
82
+ readonly request?: unknown;
83
+ }) => Promise<Response>) => unknown;
84
+ /**
85
+ * Access's `GET /me` route (under Better Auth's base path), keyed as Better Auth keys
86
+ * a plugin's endpoints (`access.api.accessMe`): the request's identity, `{ sub,
87
+ * claims }`, or `null`, as `access.client.verbs.me.call` answers it. Never cached.
88
+ */
89
+ export declare const meEndpoint: (endpoint: MeEndpointFactory, identityOf: (headers: RequestHeaders, request: unknown) => Promise<unknown>) => {
90
+ accessMe: unknown;
91
+ };
92
+ export {};
@@ -0,0 +1,269 @@
1
+ // `access.client`: what the framework's `$access` reads from an Access instance,
2
+ // through the provider member `AvAccessProvider.client`. Where Access is mounted (`url`,
3
+ // from Better Auth's `baseURL` and `basePath`), and for each verb the instance
4
+ // serves, its route, its shapes and its server call.
5
+ //
6
+ // A verb is offered only where its route answers: `signIn`, `signUp` and
7
+ // `resetPassword` with Better Auth's `emailAndPassword` on (`signUp` not with
8
+ // `disableSignUp`), no verb whose path `disabledPaths` lists, and on the `service`
9
+ // profile no verb that starts a session (`signIn`, `signUp`), by the same rule its
10
+ // hooks refuse those routes with.
11
+ //
12
+ // On the `kiosk` profile, `verbs` holds the kiosk's verbs too (`switchUser`,
13
+ // `lock`, `setPin`, `enrollDevice`, `revokeDevice`), in the same form: the
14
+ // framework's `$access` takes them as known optional verbs. A manager's device list
15
+ // (`GET /kiosk/list-devices`, and `access.kiosk` on the server) is no verb: the
16
+ // framework's set of verbs is closed.
17
+ //
18
+ // `call` is the server's way in (a Server Action, a Nest controller): it runs the
19
+ // verb in-process through Better Auth's API, so a cookie plugin such as Better
20
+ // Auth's `nextCookies()` sets the browser's cookie from the answer. It runs the
21
+ // verb as the request it stands for: Better Auth gets a request built from the
22
+ // caller's headers, so everything Better Auth and Access hold a request to holds
23
+ // here too (the `service` profile's refusals, the endpoint's own CSRF check, the
24
+ // audit log's `client` origin), and the Access plugin runs Better Auth's own origin
25
+ // check on it, the one its router runs before every route (`originCheckMiddleware`:
26
+ // the `Origin` of a request with a cookie, and every `callbackURL` and `redirectTo`
27
+ // against `trustedOrigins`). Its arguments are held to the verb's schema, and its
28
+ // answer is cut to the verb's result schema: no session token and no user field
29
+ // leaves it. `me` reads the request's identity as `access.state` does, from its
30
+ // bearer token or (profile `web`) its session cookie, and answers it, `{ sub,
31
+ // claims }`, or `null`.
32
+ import { servesRoute, } from "../access/profile.policy.js";
33
+ import { KIOSK_VERBS, } from "../kiosk/kiosk-verbs.schema.js";
34
+ import { BETTER_AUTH_VERBS, ME_PATH, meSchema, projected, schemaDefect, } from "./access-verbs.schema.js";
35
+ /** Better Auth's names for the statuses a plugin refuses with; another status is thrown as its number. */
36
+ const STATUS_NAMES = {
37
+ 400: "BAD_REQUEST",
38
+ 401: "UNAUTHORIZED",
39
+ 403: "FORBIDDEN",
40
+ 404: "NOT_FOUND",
41
+ 429: "TOO_MANY_REQUESTS",
42
+ 500: "INTERNAL_SERVER_ERROR",
43
+ };
44
+ const NO_ARGS = {
45
+ type: "object",
46
+ properties: {},
47
+ additionalProperties: false,
48
+ };
49
+ const headersOf = (given) => {
50
+ const headers = new Headers();
51
+ given.forEach((value, name) => {
52
+ headers.append(name, value);
53
+ });
54
+ return headers;
55
+ };
56
+ /** The verbs on Better Auth's routes, in the order `access.client` lists them. */
57
+ const BETTER_AUTH_VERB_NAMES = [
58
+ "signIn",
59
+ "signUp",
60
+ "signOut",
61
+ "resetPassword",
62
+ ];
63
+ /** Freezes a verb, the verbs or the client: published data, not to be changed. */
64
+ const freeze = Object.freeze;
65
+ /** A path without its trailing slashes, `/` kept as `/`. */
66
+ const trimmed = (path) => path.replace(/\/+$/, "") || "/";
67
+ /**
68
+ * Builds `access.client` for an instance: from its Better Auth options (`baseURL`,
69
+ * `basePath`, `emailAndPassword`, `disabledPaths`), its profile and its declared
70
+ * claims.
71
+ */
72
+ export const accessClient = (input) => {
73
+ const { block, kind } = input;
74
+ const basePath = typeof block.basePath === "string" ? block.basePath : undefined;
75
+ const dynamic = input.isDynamicBaseURL(block.baseURL);
76
+ // A dynamic base URL follows the request's host: the client reaches Access on its
77
+ // own origin, at the base path. A fixed one (or Better Auth's environment
78
+ // variables) is where Access is.
79
+ const absolute = dynamic
80
+ ? input.baseOf(block.baseURL.fallback, basePath)
81
+ : input.baseOf(typeof block.baseURL === "string" ? block.baseURL : undefined, basePath);
82
+ if (absolute === undefined) {
83
+ // `createAccess` refuses an instance without a base URL before it gets here.
84
+ throw new TypeError("access.client: Better Auth has no base URL");
85
+ }
86
+ const base = trimmed(absolute);
87
+ const url = dynamic ? trimmed(new URL(absolute).pathname) : base;
88
+ const emailAndPassword = (block.emailAndPassword ?? {});
89
+ const disabled = Array.isArray(block.disabledPaths)
90
+ ? block.disabledPaths
91
+ : [];
92
+ const passwords = emailAndPassword.enabled === true;
93
+ const offered = {
94
+ signIn: passwords,
95
+ signUp: passwords && emailAndPassword.disableSignUp !== true,
96
+ signOut: true,
97
+ resetPassword: passwords,
98
+ };
99
+ const argsChecked = (verb, schema, args) => {
100
+ const defect = schemaDefect(schema ?? NO_ARGS, args ?? {}, "args");
101
+ if (defect !== undefined) {
102
+ throw new input.refusal("BAD_REQUEST", {
103
+ message: `$access.${verb}: ${defect}.`,
104
+ });
105
+ }
106
+ };
107
+ /**
108
+ * The identifier a call names, for its rate limit's target: the email, read as
109
+ * the sign-in route reads it (sign-up's too, so one address is one target), or a
110
+ * password reset's token.
111
+ */
112
+ const identifierOf = (verb, args) => {
113
+ if (verb !== "resetPassword") {
114
+ return input.identify("/sign-in/email", args);
115
+ }
116
+ const { token } = args;
117
+ return `reset-token:${token}`;
118
+ };
119
+ /**
120
+ * Every plugin's `onRequest`, in the order Better Auth's router runs them before
121
+ * a route (a server call skips them): a refusal is thrown as Better Auth's
122
+ * `APIError` with the plugin's status and body; a new request takes the place of
123
+ * the old one. Answers the request the route runs with.
124
+ */
125
+ const pluginsAdmit = async (request) => {
126
+ const context = await input.context();
127
+ const plugins = context.options.plugins;
128
+ let current = request;
129
+ for (let at = 0; plugins !== undefined && at < plugins.length; at += 1) {
130
+ const onRequest = plugins[at]?.onRequest;
131
+ if (onRequest === undefined) {
132
+ continue;
133
+ }
134
+ const answer = ((await onRequest(current, context)) ?? {});
135
+ const refused = answer.response;
136
+ if (refused !== undefined) {
137
+ let body;
138
+ try {
139
+ body = (await refused.clone().json());
140
+ }
141
+ catch {
142
+ body = { message: await refused.text() };
143
+ }
144
+ const headers = {};
145
+ refused.headers.forEach((value, name) => {
146
+ headers[name] = value;
147
+ });
148
+ throw new input.refusal(STATUS_NAMES[refused.status] ?? refused.status, body, headers);
149
+ }
150
+ if (answer.request !== undefined) {
151
+ current = answer.request;
152
+ }
153
+ }
154
+ return current;
155
+ };
156
+ /**
157
+ * One verb on a route of Better Auth's router (Better Auth's own or Access's), as
158
+ * published: its route, its shapes and its `call`. `limited` puts the call under
159
+ * the rate limit first (`signOut` and `lock` are not), `identifier` names its
160
+ * target for the limit's second bucket.
161
+ */
162
+ const routed = (verb, route, limited, identifier) => {
163
+ const published = {
164
+ method: route.method,
165
+ path: route.path,
166
+ result: route.result,
167
+ call: async ({ args, headers, }) => {
168
+ argsChecked(verb, route.args, args);
169
+ const forwarded = headersOf(headers);
170
+ // The request this call stands for, as the route would get it: the
171
+ // caller's headers and the arguments as its JSON body.
172
+ forwarded.delete("content-length");
173
+ forwarded.delete("transfer-encoding");
174
+ if (args !== undefined) {
175
+ forwarded.set("content-type", "application/json");
176
+ }
177
+ const built = new Request(`${base}${route.path}`, {
178
+ method: route.method,
179
+ headers: forwarded,
180
+ body: args === undefined ? null : JSON.stringify(args),
181
+ });
182
+ // In the router's order: its rate limit, then the plugins' `onRequest`.
183
+ const settle = limited
184
+ ? await input.limit({
185
+ path: route.path,
186
+ headers: forwarded,
187
+ request: built,
188
+ identifier: identifier(args),
189
+ })
190
+ : undefined;
191
+ const request = await pluginsAdmit(built);
192
+ let body = args;
193
+ if (request !== built && args !== undefined) {
194
+ // A plugin's new request: its body is what the route reads.
195
+ body = await request.clone().json();
196
+ argsChecked(verb, route.args, body);
197
+ }
198
+ input.calls.add(request);
199
+ // A refusal keeps the target's count; a success clears it.
200
+ const answer = await input.api()[route.endpoint]({
201
+ body,
202
+ headers: request.headers,
203
+ request,
204
+ asResponse: false,
205
+ });
206
+ await settle?.();
207
+ return projected(route.result, answer);
208
+ },
209
+ };
210
+ if (route.args !== undefined) {
211
+ published.args = route.args;
212
+ }
213
+ freeze(published);
214
+ return published;
215
+ };
216
+ const verbs = {};
217
+ for (let at = 0; at < BETTER_AUTH_VERB_NAMES.length; at += 1) {
218
+ const verb = BETTER_AUTH_VERB_NAMES[at];
219
+ const route = BETTER_AUTH_VERBS[verb];
220
+ if (!offered[verb] ||
221
+ !servesRoute(kind, route.path) ||
222
+ disabled.includes(route.path)) {
223
+ continue;
224
+ }
225
+ verbs[verb] = routed(verb, route, verb !== "signOut", (args) => verb === "signOut" ? undefined : identifierOf(verb, args));
226
+ }
227
+ if (!disabled.includes(ME_PATH)) {
228
+ const me = {
229
+ method: "GET",
230
+ path: ME_PATH,
231
+ result: meSchema(input.claims),
232
+ call: async ({ args, headers, request, }) => {
233
+ argsChecked("me", undefined, args);
234
+ return input.identityOf(headers, request);
235
+ },
236
+ };
237
+ freeze(me);
238
+ verbs.me = me;
239
+ }
240
+ if (kind === "kiosk") {
241
+ for (const verb of Object.keys(KIOSK_VERBS)) {
242
+ const route = KIOSK_VERBS[verb];
243
+ if (!disabled.includes(route.path)) {
244
+ verbs[verb] = routed(verb, route, verb !== "lock", () => undefined);
245
+ }
246
+ }
247
+ }
248
+ freeze(verbs);
249
+ const client = { url, verbs };
250
+ freeze(client);
251
+ return client;
252
+ };
253
+ /**
254
+ * Access's `GET /me` route (under Better Auth's base path), keyed as Better Auth keys
255
+ * a plugin's endpoints (`access.api.accessMe`): the request's identity, `{ sub,
256
+ * claims }`, or `null`, as `access.client.verbs.me.call` answers it. Never cached.
257
+ */
258
+ export const meEndpoint = (endpoint, identityOf) => ({
259
+ accessMe: endpoint(ME_PATH, { method: "GET" }, async (context) => {
260
+ const identity = await identityOf(context.headers ?? new Headers(), context.request);
261
+ return new Response(JSON.stringify(identity), {
262
+ status: 200,
263
+ headers: {
264
+ "content-type": "application/json",
265
+ "cache-control": "no-store",
266
+ },
267
+ });
268
+ }),
269
+ });
@@ -0,0 +1,66 @@
1
+ import type { AccessClaimsDeclaration } from "../access/claims.mapper.js";
2
+ /** A verb `$access` offers through Access's routes (`can` is the framework's own). */
3
+ export type AccessVerbName = "signIn" | "signUp" | "signOut" | "resetPassword" | "me";
4
+ /** A closed object, in the ClientContract's form: its properties, the required ones, and no other key. */
5
+ type ObjectSchema = {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly [key: string]: AccessVerbSchema;
9
+ };
10
+ readonly required?: readonly string[];
11
+ readonly additionalProperties: false;
12
+ };
13
+ /** A JSON Schema in the ClientContract's forms, as Access writes them: scalars and closed objects. */
14
+ export type AccessVerbSchema = {
15
+ readonly type: "string" | "number" | "boolean";
16
+ } | ObjectSchema;
17
+ /** One verb's route: the method, the path under Better Auth's base path, and the schemas. */
18
+ export type AccessVerbRoute = {
19
+ readonly method: "GET" | "POST";
20
+ readonly path: string;
21
+ readonly args?: ObjectSchema;
22
+ readonly result: ObjectSchema;
23
+ /** The key of Better Auth's server API (`access.api.<key>`) the verb's server call runs. */
24
+ readonly endpoint: "signInEmail" | "signUpEmail" | "signOut" | "resetPassword" | "kioskSwitchUser" | "kioskLock" | "kioskSetPin" | "kioskEnrollDevice" | "kioskRevokeDevice";
25
+ };
26
+ export declare const STRING: {
27
+ readonly type: "string";
28
+ };
29
+ export declare const BOOLEAN: {
30
+ readonly type: "boolean";
31
+ };
32
+ /** A closed object schema: its properties, the required ones, and no other key. */
33
+ export declare const closed: (properties: {
34
+ readonly [key: string]: AccessVerbSchema;
35
+ }, required: readonly string[]) => ObjectSchema;
36
+ /** The signed-in user, as a result names it: its id, the token's `sub`, and nothing of its record. */
37
+ export declare const USER: ObjectSchema;
38
+ /**
39
+ * The verbs on Better Auth's routes (better-auth 1.7.12 to 1.7.14): the arguments its client
40
+ * sends on each, and what Access promises of each answer.
41
+ */
42
+ export declare const BETTER_AUTH_VERBS: {
43
+ readonly signIn: AccessVerbRoute;
44
+ readonly signUp: AccessVerbRoute;
45
+ readonly signOut: AccessVerbRoute;
46
+ readonly resetPassword: AccessVerbRoute;
47
+ };
48
+ /** Access's own route for `me`, under Better Auth's base path. */
49
+ export declare const ME_PATH = "/me";
50
+ /**
51
+ * `me`'s result: the token identity, `sub` and the declared claims, each typed as
52
+ * declared and each optional (a user may hold none of them). Nothing else.
53
+ */
54
+ export declare const meSchema: (claims: AccessClaimsDeclaration) => ObjectSchema;
55
+ /**
56
+ * Why `value` does not fit `schema`, or `undefined` when it does: a key the schema
57
+ * does not name, a required key missing, or a value of another type. The sentence
58
+ * names the key and the type, never the value.
59
+ */
60
+ export declare const schemaDefect: (schema: AccessVerbSchema, value: unknown, at: string) => string | undefined;
61
+ /**
62
+ * Better Auth's answer, cut to what `schema` names: a key it does not name (the
63
+ * session token, a user field) is left out, and so is a value of another type.
64
+ */
65
+ export declare const projected: (schema: AccessVerbSchema, value: unknown) => unknown;
66
+ export {};
@@ -0,0 +1,159 @@
1
+ // The `$access` verbs' routes and shapes: what `access.client` publishes for the
2
+ // framework's clients. Each verb is one of Better Auth's routes (its method and its
3
+ // path under Better Auth's base path), except `me`, which is Access's own. Its
4
+ // arguments are the ones Better Auth's client takes on that route
5
+ // (`authClient.signIn.email({ email, password })`), and its result names what Access
6
+ // promises of the answer: the signed-in user's id (`sub`) and Better Auth's own
7
+ // flags, never a session token or a user field. `me`'s result is the token identity,
8
+ // `{ sub, claims }`, with the claims `policies.claims` declares and nothing else.
9
+ //
10
+ // The schemas are written in the JSON Schema forms the framework's ClientContract
11
+ // takes (`validateClientContractStructure`): every object closed
12
+ // (`additionalProperties: false`), an optional key left out of `required`, and no
13
+ // keyword beyond those forms.
14
+ import { isRecord } from "../policies.compiler.js";
15
+ export const STRING = { type: "string" };
16
+ export const BOOLEAN = { type: "boolean" };
17
+ /** A closed object schema: its properties, the required ones, and no other key. */
18
+ export const closed = (properties, required) => required.length === 0
19
+ ? { type: "object", properties, additionalProperties: false }
20
+ : { type: "object", properties, required, additionalProperties: false };
21
+ /** The signed-in user, as a result names it: its id, the token's `sub`, and nothing of its record. */
22
+ export const USER = closed({ id: STRING }, ["id"]);
23
+ /**
24
+ * The verbs on Better Auth's routes (better-auth 1.7.12 to 1.7.14): the arguments its client
25
+ * sends on each, and what Access promises of each answer.
26
+ */
27
+ export const BETTER_AUTH_VERBS = {
28
+ signIn: {
29
+ method: "POST",
30
+ path: "/sign-in/email",
31
+ args: closed({
32
+ email: STRING,
33
+ password: STRING,
34
+ callbackURL: STRING,
35
+ rememberMe: BOOLEAN,
36
+ }, ["email", "password"]),
37
+ result: closed({ redirect: BOOLEAN, url: STRING, user: USER }, [
38
+ "redirect",
39
+ "user",
40
+ ]),
41
+ endpoint: "signInEmail",
42
+ },
43
+ signUp: {
44
+ method: "POST",
45
+ path: "/sign-up/email",
46
+ args: closed({
47
+ name: STRING,
48
+ email: STRING,
49
+ password: STRING,
50
+ image: STRING,
51
+ callbackURL: STRING,
52
+ rememberMe: BOOLEAN,
53
+ }, ["name", "email", "password"]),
54
+ result: closed({ user: USER }, ["user"]),
55
+ endpoint: "signUpEmail",
56
+ },
57
+ signOut: {
58
+ method: "POST",
59
+ path: "/sign-out",
60
+ result: closed({ success: BOOLEAN }, ["success"]),
61
+ endpoint: "signOut",
62
+ },
63
+ resetPassword: {
64
+ method: "POST",
65
+ path: "/reset-password",
66
+ args: closed({ newPassword: STRING, token: STRING }, [
67
+ "newPassword",
68
+ "token",
69
+ ]),
70
+ result: closed({ status: BOOLEAN }, ["status"]),
71
+ endpoint: "resetPassword",
72
+ },
73
+ };
74
+ /** Access's own route for `me`, under Better Auth's base path. */
75
+ export const ME_PATH = "/me";
76
+ /**
77
+ * `me`'s result: the token identity, `sub` and the declared claims, each typed as
78
+ * declared and each optional (a user may hold none of them). Nothing else.
79
+ */
80
+ export const meSchema = (claims) => {
81
+ const declared = {};
82
+ for (const claim of Object.keys(claims)) {
83
+ declared[claim] = {
84
+ type: claims[claim],
85
+ };
86
+ }
87
+ return closed({ sub: STRING, claims: closed(declared, []) }, [
88
+ "sub",
89
+ "claims",
90
+ ]);
91
+ };
92
+ /** What a refused value is, never the value: arguments carry passwords. */
93
+ const kindOf = (value) => Array.isArray(value)
94
+ ? "an array"
95
+ : value === null
96
+ ? "null"
97
+ : typeof value === "object"
98
+ ? "an object"
99
+ : `a ${typeof value}`;
100
+ /**
101
+ * Why `value` does not fit `schema`, or `undefined` when it does: a key the schema
102
+ * does not name, a required key missing, or a value of another type. The sentence
103
+ * names the key and the type, never the value.
104
+ */
105
+ export const schemaDefect = (schema, value, at) => {
106
+ if (schema.type !== "object") {
107
+ return typeof value === schema.type
108
+ ? undefined
109
+ : `${at} is ${kindOf(value)}, not a ${schema.type}`;
110
+ }
111
+ if (!isRecord(value)) {
112
+ return `${at} is ${kindOf(value)}, not an object`;
113
+ }
114
+ for (const key of Object.keys(value)) {
115
+ if (!Object.hasOwn(schema.properties, key)) {
116
+ return `${at} has \`${key}\`, which it does not take (it takes ${Object.keys(schema.properties).join(", ") || "nothing"})`;
117
+ }
118
+ }
119
+ const required = schema.required;
120
+ for (let index = 0; required !== undefined && index < required.length; index += 1) {
121
+ const key = required[index];
122
+ if (value[key] === undefined) {
123
+ return `${at} lacks \`${key}\``;
124
+ }
125
+ }
126
+ for (const key of Object.keys(schema.properties)) {
127
+ const member = value[key];
128
+ const property = schema.properties[key];
129
+ if (member === undefined || property === undefined) {
130
+ continue;
131
+ }
132
+ const defect = schemaDefect(property, member, `${at}.${key}`);
133
+ if (defect !== undefined) {
134
+ return defect;
135
+ }
136
+ }
137
+ return undefined;
138
+ };
139
+ /**
140
+ * Better Auth's answer, cut to what `schema` names: a key it does not name (the
141
+ * session token, a user field) is left out, and so is a value of another type.
142
+ */
143
+ export const projected = (schema, value) => {
144
+ if (schema.type !== "object") {
145
+ return typeof value === schema.type ? value : undefined;
146
+ }
147
+ if (!isRecord(value)) {
148
+ return undefined;
149
+ }
150
+ const kept = {};
151
+ for (const key of Object.keys(schema.properties)) {
152
+ const property = schema.properties[key];
153
+ const member = property === undefined ? undefined : projected(property, value[key]);
154
+ if (member !== undefined) {
155
+ kept[key] = member;
156
+ }
157
+ }
158
+ return kept;
159
+ };