@rebasepro/server 0.23.0 → 0.24.0

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 (181) hide show
  1. package/README.md +1 -1
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/{GCSStorageController-CjrA4PMo.js → GCSStorageController-BSiP1c-f.js} +22 -8
  4. package/dist/GCSStorageController-BSiP1c-f.js.map +1 -0
  5. package/dist/{S3StorageController-B6pKDNVj.js → S3StorageController-CAwFRgjV.js} +19 -7
  6. package/dist/S3StorageController-CAwFRgjV.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +9 -0
  9. package/dist/api/live-schema-routes.d.ts +38 -8
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/openapi-generator.d.ts +17 -0
  12. package/dist/api/rest/api-generator.d.ts +44 -10
  13. package/dist/api/rest/write-validation.d.ts +2 -2
  14. package/dist/api/types.d.ts +17 -1
  15. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-CWqS_sLJ.js} +309 -11
  16. package/dist/ast-schema-editor-CWqS_sLJ.js.map +1 -0
  17. package/dist/auth/access.d.ts +105 -0
  18. package/dist/auth/adapter-middleware.d.ts +2 -1
  19. package/dist/auth/address-ownership.d.ts +16 -1
  20. package/dist/auth/admin-roles-route.d.ts +4 -2
  21. package/dist/auth/admin-roles.d.ts +17 -20
  22. package/dist/auth/admin-users-route.d.ts +1 -0
  23. package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
  24. package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
  25. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  26. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  27. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  28. package/dist/auth/api-keys/index.d.ts +11 -11
  29. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  30. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  31. package/dist/auth/auth-hooks.d.ts +46 -7
  32. package/dist/auth/builtin-auth-adapter.d.ts +8 -0
  33. package/dist/auth/cookie-utils.d.ts +7 -0
  34. package/dist/auth/deliverable-address.d.ts +6 -0
  35. package/dist/auth/email-change-routes.d.ts +41 -0
  36. package/dist/auth/expired-token-sweep.d.ts +67 -0
  37. package/dist/auth/impersonation.d.ts +110 -0
  38. package/dist/auth/index.d.ts +4 -2
  39. package/dist/auth/interfaces.d.ts +110 -59
  40. package/dist/auth/jwt.d.ts +49 -3
  41. package/dist/auth/magic-link-routes.d.ts +2 -6
  42. package/dist/auth/mfa-routes.d.ts +2 -9
  43. package/dist/auth/middleware.d.ts +17 -5
  44. package/dist/auth/otp-routes.d.ts +2 -6
  45. package/dist/auth/passwordless-signup.d.ts +27 -0
  46. package/dist/auth/platform-token.d.ts +122 -0
  47. package/dist/auth/rate-limiter.d.ts +41 -0
  48. package/dist/auth/routes.d.ts +45 -0
  49. package/dist/auth/scope-routes.d.ts +22 -0
  50. package/dist/auth/session-routes.d.ts +11 -6
  51. package/dist/auth/token-revocation.d.ts +50 -1
  52. package/dist/auth/verify-credential.d.ts +28 -0
  53. package/dist/{auth-B-GIMpDG.js → auth-DMLngxn_.js} +2159 -569
  54. package/dist/auth-DMLngxn_.js.map +1 -0
  55. package/dist/backend-DTAOsLQc.js.map +1 -1
  56. package/dist/backup/backup-common.d.ts +10 -0
  57. package/dist/backup/backup-routes.d.ts +24 -4
  58. package/dist/backup/backup-schedule.d.ts +33 -0
  59. package/dist/backup/backup-storage.d.ts +14 -0
  60. package/dist/backup/index.d.ts +2 -0
  61. package/dist/backup-CN0s50D2.js +444 -0
  62. package/dist/backup-CN0s50D2.js.map +1 -0
  63. package/dist/boot/bundle.d.ts +19 -0
  64. package/dist/boot/env.d.ts +49 -4
  65. package/dist/boot/security-headers.d.ts +26 -0
  66. package/dist/boot/static-routing.d.ts +56 -0
  67. package/dist/collection_patch-BRu-BvDv.js +472 -0
  68. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  69. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-fz8i4pxs.js} +17 -4
  70. package/dist/contract-routes-fz8i4pxs.js.map +1 -0
  71. package/dist/cron/cron-scheduler.d.ts +25 -20
  72. package/dist/cron/cron-store.d.ts +6 -2
  73. package/dist/{cron-loader-DfTj2Hbi.js → cron-loader-CwaANlOG.js} +4 -4
  74. package/dist/cron-loader-CwaANlOG.js.map +1 -0
  75. package/dist/{cron-routes-eE8nif_b.js → cron-routes-Bc-SB0Se.js} +10 -7
  76. package/dist/cron-routes-Bc-SB0Se.js.map +1 -0
  77. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-CYQgco86.js} +52 -34
  78. package/dist/cron-scheduler-CYQgco86.js.map +1 -0
  79. package/dist/{cron-store-TcoGz-xS.js → cron-store-D2Q9-Aco.js} +10 -15
  80. package/dist/cron-store-D2Q9-Aco.js.map +1 -0
  81. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BaqMSa4Y.js} +2 -2
  82. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BaqMSa4Y.js.map} +1 -1
  83. package/dist/email/index.d.ts +2 -2
  84. package/dist/email/templates.d.ts +22 -0
  85. package/dist/email/types.d.ts +26 -0
  86. package/dist/env.d.ts +1 -2
  87. package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
  88. package/dist/errors-D6_y86c5.js.map +1 -0
  89. package/dist/{function-loader-xnbDAPfa.js → function-loader-D7o5Epjj.js} +2 -2
  90. package/dist/{function-loader-xnbDAPfa.js.map → function-loader-D7o5Epjj.js.map} +1 -1
  91. package/dist/{function-routes-Chet4-lB.js → function-routes-CaNG4waN.js} +24 -12
  92. package/dist/function-routes-CaNG4waN.js.map +1 -0
  93. package/dist/functions/context.d.ts +17 -6
  94. package/dist/functions/guards.d.ts +22 -5
  95. package/dist/functions/index.d.ts +2 -2
  96. package/dist/functions/index.js +90 -36
  97. package/dist/functions/index.js.map +1 -1
  98. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-Nr8zLvoU.js} +4 -4
  99. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-Nr8zLvoU.js.map} +1 -1
  100. package/dist/{history-store-BhxWOuz9.js → history-store-rcAm_xFR.js} +2 -2
  101. package/dist/{history-store-BhxWOuz9.js.map → history-store-rcAm_xFR.js.map} +1 -1
  102. package/dist/index.d.ts +8 -2
  103. package/dist/index.es.js +3084 -753
  104. package/dist/index.es.js.map +1 -1
  105. package/dist/init/health.d.ts +17 -2
  106. package/dist/init/shutdown.d.ts +10 -0
  107. package/dist/init.d.ts +54 -0
  108. package/dist/{jobs-CazMYhyy.js → jobs-DqYNfquG.js} +5 -5
  109. package/dist/{jobs-CazMYhyy.js.map → jobs-DqYNfquG.js.map} +1 -1
  110. package/dist/{jwt-DnQHNFCl.js → jwt-R6bSPMjk.js} +39 -15
  111. package/dist/{jwt-DnQHNFCl.js.map → jwt-R6bSPMjk.js.map} +1 -1
  112. package/dist/{keys-CogCQpxG.js → keys-GAVZqbqx.js} +3 -17
  113. package/dist/{keys-CogCQpxG.js.map → keys-GAVZqbqx.js.map} +1 -1
  114. package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
  115. package/dist/logger-D-S-hO5e.js.map +1 -0
  116. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DAdv37GI.js} +48 -8
  117. package/dist/logs-routes-DAdv37GI.js.map +1 -0
  118. package/dist/mcp/consent-page.d.ts +1 -1
  119. package/dist/mcp/mcp-routes.d.ts +7 -0
  120. package/dist/mcp/mcp-tools.d.ts +15 -9
  121. package/dist/mcp/oauth-metadata.d.ts +21 -16
  122. package/dist/mcp/oauth-routes.d.ts +7 -1
  123. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-DAq_XVDu.js} +104 -13
  124. package/dist/openapi-generator-DAq_XVDu.js.map +1 -0
  125. package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
  126. package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
  127. package/dist/{query-parser-DGRVFNM3.js → query-parser-BgiKJKvc.js} +6 -56
  128. package/dist/query-parser-BgiKJKvc.js.map +1 -0
  129. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-DgH7j8qO.js} +3 -3
  130. package/dist/{request-timeout-C_4C2BeR.js.map → request-timeout-DgH7j8qO.js.map} +1 -1
  131. package/dist/schema-edit/apply-schema-change.d.ts +63 -3
  132. package/dist/schema-edit/project-root.d.ts +3 -2
  133. package/dist/schema-edit/remote-source.d.ts +9 -4
  134. package/dist/{schema-editor-routes-C5-lh_jO.js → schema-editor-routes-oIyuWl3L.js} +12 -7
  135. package/dist/schema-editor-routes-oIyuWl3L.js.map +1 -0
  136. package/dist/serve-spa.d.ts +58 -0
  137. package/dist/services/routed-realtime-service.d.ts +11 -0
  138. package/dist/soft-delete-params-BWPilMPF.js +59 -0
  139. package/dist/soft-delete-params-BWPilMPF.js.map +1 -0
  140. package/dist/{src-vkcwKXbT.js → src-CatHFUym.js} +439 -20
  141. package/dist/src-CatHFUym.js.map +1 -0
  142. package/dist/{src-pmvW7BFx.js → src-I3aG1PcY.js} +252 -70
  143. package/dist/src-I3aG1PcY.js.map +1 -0
  144. package/dist/storage/GCSStorageController.d.ts +2 -0
  145. package/dist/storage/LocalStorageController.d.ts +2 -0
  146. package/dist/storage/S3StorageController.d.ts +2 -0
  147. package/dist/storage/index.d.ts +2 -2
  148. package/dist/storage/property-limits.d.ts +41 -6
  149. package/dist/storage/request-keys.d.ts +15 -0
  150. package/dist/storage/requested-object.d.ts +74 -0
  151. package/dist/storage/routes.d.ts +36 -18
  152. package/dist/storage/tus-handler.d.ts +30 -5
  153. package/dist/storage/types.d.ts +19 -0
  154. package/dist/types-BfKcm9do.js.map +1 -1
  155. package/dist/utils/logger.d.ts +12 -0
  156. package/package.json +5 -5
  157. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  158. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  159. package/dist/admin-roles-vYdp_Pil.js +0 -36
  160. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  161. package/dist/admin_block-DxKLmdiv.js +0 -206
  162. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  163. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  164. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  165. package/dist/auth-B-GIMpDG.js.map +0 -1
  166. package/dist/backup-D7YR94N3.js +0 -253
  167. package/dist/backup-D7YR94N3.js.map +0 -1
  168. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  169. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  170. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  171. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  172. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  173. package/dist/errors-DWsX4yTd.js.map +0 -1
  174. package/dist/function-routes-Chet4-lB.js.map +0 -1
  175. package/dist/logger-DO2PZc4i.js.map +0 -1
  176. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  177. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  178. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  179. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  180. package/dist/src-pmvW7BFx.js.map +0 -1
  181. package/dist/src-vkcwKXbT.js.map +0 -1
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Platform tokens: a short-lived credential the platform hosting this server
3
+ * mints for one person, for one project, for a few read-only scopes.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * On Rebase Cloud the owner of a project is signed in to the *control plane*,
8
+ * not to their own app. Their app's admin surfaces — the cron job list and its
9
+ * run history first — accept an admin user of the app, an `rk_` key, or the
10
+ * service key, and an owner may hold none of them: a fresh deploy has no admin
11
+ * account, and the service key is admin on the whole API, which is the wrong
12
+ * thing to hand someone who wants to read why last night's job failed.
13
+ *
14
+ * So the control plane signs a token instead. It checks the caller's project
15
+ * membership, then signs `{ aud: <project>, sub: <who>, scope: "cron:read" }`
16
+ * with a key only it holds, valid for minutes. This server verifies it against
17
+ * the public half — `REBASE_PLATFORM_TOKEN_KEY` — which the platform sets at
18
+ * deploy. Nothing secret lives in the tenant: the env holds a public key, and
19
+ * leaking it lets nobody mint anything.
20
+ *
21
+ * ## What a platform token can never do
22
+ *
23
+ * - **Exceed {@link PLATFORM_TOKEN_SCOPES}.** The token names scopes; this
24
+ * server grants only the ones on that list, whatever the platform signed. The
25
+ * ceiling lives here, in the tenant's code, so a compromised or buggy control
26
+ * plane cannot widen it.
27
+ * - **Outlive {@link PLATFORM_TOKEN_MAX_LIFETIME_SECONDS}.** A token whose
28
+ * `exp - iat` is longer is refused, not clamped: a long-lived platform token
29
+ * is a static secret by another name.
30
+ * - **Reach another project.** One platform key signs for every tenant, so the
31
+ * audience is what binds a token to this one: `aud` must equal
32
+ * `REBASE_PLATFORM_TOKEN_AUDIENCE`.
33
+ * - **Act as a person of this app.** The caller is `platform:<sub>` with no
34
+ * roles: row-level security sees nobody it knows, and every surface that
35
+ * checks a scope other than the granted ones refuses it.
36
+ *
37
+ * ## Wire format
38
+ *
39
+ * `rpt_` followed by a compact ES256 JWS. The prefix keeps the credential
40
+ * classes apart the way `rk_` does: a platform token is never tried as a user
41
+ * session, and a user session is never tried as a platform token.
42
+ *
43
+ * @module
44
+ */
45
+ import type { MiddlewareHandler } from "hono";
46
+ import type { AdminScope } from "@rebasepro/types";
47
+ import type { HonoEnv } from "../api/types.js";
48
+ /** Marks a bearer token as a platform token. */
49
+ export declare const PLATFORM_TOKEN_PREFIX = "rpt_";
50
+ /** The `iss` every platform token carries. */
51
+ export declare const PLATFORM_TOKEN_ISSUER = "rebase-cloud";
52
+ /**
53
+ * Everything a platform token can be granted on this server.
54
+ *
55
+ * Read-only on purpose. Adding a scope here is a decision about what the
56
+ * platform may do inside a customer's app on a person's behalf, and belongs in
57
+ * its own change.
58
+ */
59
+ export declare const PLATFORM_TOKEN_SCOPES: readonly AdminScope[];
60
+ /** The longest `exp - iat` a platform token may declare. */
61
+ export declare const PLATFORM_TOKEN_MAX_LIFETIME_SECONDS = 600;
62
+ /** The environment variables a platform sets to turn platform tokens on. */
63
+ export declare const PLATFORM_TOKEN_KEY_ENV = "REBASE_PLATFORM_TOKEN_KEY";
64
+ export declare const PLATFORM_TOKEN_AUDIENCE_ENV = "REBASE_PLATFORM_TOKEN_AUDIENCE";
65
+ /** What this server verifies platform tokens against. */
66
+ export interface PlatformTokenConfig {
67
+ /**
68
+ * PEM-encoded SPKI public keys, EC P-256. More than one during a key
69
+ * rotation: a token verifies against any of them.
70
+ */
71
+ publicKeys: string[];
72
+ /** This project, as the platform names it in `aud`. */
73
+ audience: string;
74
+ }
75
+ /** A verified platform token, as the request will act. */
76
+ export interface PlatformCaller {
77
+ /** Who the platform minted it for — a control-plane account. */
78
+ subject: string;
79
+ /** The granted scopes: what the token asked for, within the ceiling. */
80
+ scopes: string[];
81
+ /** `jti`, when the platform set one — what an audit line quotes. */
82
+ tokenId?: string;
83
+ }
84
+ /** Why a presented platform token does not authenticate. */
85
+ export interface PlatformTokenRefusal {
86
+ refusal: string;
87
+ }
88
+ export declare function isPlatformToken(token: string): boolean;
89
+ /**
90
+ * The PEM blocks in an env value: real newlines, `\n`-escaped ones (what a
91
+ * one-line `.env` holds), or the whole thing base64-encoded.
92
+ */
93
+ export declare function parsePublicKeys(value: string): string[];
94
+ /**
95
+ * Read the platform-token configuration from the environment.
96
+ *
97
+ * `undefined` when platform tokens are off — neither variable set, which is
98
+ * every self-hosted server — or misconfigured. A misconfiguration is logged and
99
+ * leaves them off rather than failing the boot: the platform set these values,
100
+ * the app's owner cannot fix them, and the app serving traffic matters more
101
+ * than its cron history being readable from the CLI.
102
+ */
103
+ export declare function platformTokensFromEnv(env: Record<string, string | undefined>): Promise<PlatformTokenConfig | undefined>;
104
+ /**
105
+ * Verify a presented platform token: signature, issuer, audience, lifetime, and
106
+ * the scopes it may be granted here.
107
+ */
108
+ export declare function verifyPlatformToken(token: string, config: PlatformTokenConfig, nowSeconds?: number): Promise<PlatformCaller | PlatformTokenRefusal>;
109
+ /**
110
+ * Authenticate an `rpt_` bearer token ahead of an admin gate.
111
+ *
112
+ * Mounted where the `rk_` pre-auth is, and shaped like it: a request it does not
113
+ * recognise passes through untouched, a recognised one either becomes a caller
114
+ * with narrowed `scopes` or is refused here. The gate's scope check then decides
115
+ * — so a token holding `cron:read` reads cron, and is a 403 on every other
116
+ * surface.
117
+ *
118
+ * With `config` undefined, platform tokens are off on this server; an `rpt_`
119
+ * token is still recognised, so its holder learns that rather than the generic
120
+ * "invalid token" a JWT parser would answer.
121
+ */
122
+ export declare function createPlatformTokenPreAuth(config: PlatformTokenConfig | undefined): MiddlewareHandler<HonoEnv>;
@@ -83,6 +83,41 @@ export declare function setSharedRateLimitStore(store: RateLimitStore | undefine
83
83
  * Uses a sliding window: only hits within the last `windowMs` are counted.
84
84
  */
85
85
  export declare function createRateLimiter(options?: RateLimiterOptions): MiddlewareHandler<HonoEnv>;
86
+ /**
87
+ * Default key generator: the client's address, from the most trustworthy source
88
+ * this deployment has.
89
+ *
90
+ * `X-Forwarded-For` is a client-writable header; only the entries appended by
91
+ * trusted reverse proxies can be believed. With `trustedProxyHops` proxies in
92
+ * front, each appends the address it saw, so the real client IP is the
93
+ * `trustedProxyHops`-th entry from the right — everything further left is
94
+ * client-supplied and must be ignored. This is what prevents a caller from
95
+ * spoofing `X-Forwarded-For` to spread its requests across many rate-limit keys.
96
+ *
97
+ * `X-Real-IP` is the *same* kind of header and needs the same rule, which it
98
+ * did not have: it was read unconditionally, including under
99
+ * `trustedProxyHops === 0` — the mode whose entire meaning is "no proxy is in
100
+ * front of me". With no proxy there, nothing writes `X-Real-IP` except the
101
+ * caller, so the key was theirs to choose: one header per request bought an
102
+ * unlimited number of buckets, and the limiters on login, registration and
103
+ * password reset counted to one. The reasoning had been done carefully for one
104
+ * spelling of a proxy header and not carried to its twin.
105
+ *
106
+ * So `X-Real-IP` is now believed only where a proxy is declared to exist. With
107
+ * none, the connection's own address is used — unforgeable, and available
108
+ * because the server runs on `@hono/node-server`. `"unknown"` is the last
109
+ * resort only, and it is a single shared bucket by design: better that
110
+ * anonymous callers throttle each other than that any of them throttles nobody.
111
+ */
112
+ /**
113
+ * Where a request comes from, as the rate limiters judge it: the socket's own
114
+ * address, or — only behind as many proxies as `TRUSTED_PROXY_HOPS` declares —
115
+ * the address they report. What a session row records as its IP, so the
116
+ * sessions list shows the same address the limiter counted, rather than the
117
+ * raw `X-Forwarded-For` (whose leftmost entry the caller chooses) or
118
+ * `"unknown"` for every direct connection.
119
+ */
120
+ export declare function requestClientAddress(c: Parameters<MiddlewareHandler<HonoEnv>>[0]): string;
86
121
  /**
87
122
  * Pre-configured rate limiter for general auth endpoints (login, register).
88
123
  * 200 requests per 15 minutes per IP.
@@ -209,6 +244,12 @@ export interface DataRateLimitConfig {
209
244
  anonymousFunctions?: number | null;
210
245
  /** Share counts across replicas. Defaults to this process's memory. */
211
246
  store?: RateLimitStore;
247
+ /**
248
+ * The deployment's service key, recognised as a Bearer before any auth
249
+ * middleware has run — the storage router's limiter runs ahead of its
250
+ * routes' auth — and never limited. See {@link dataRateLimitBuckets}.
251
+ */
252
+ serviceKey?: string;
212
253
  }
213
254
  /** @see DataRateLimitConfig.anonymousFunctions */
214
255
  export declare const DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT = 3000;
@@ -4,6 +4,8 @@ import type { AuthHooks } from "./auth-hooks.js";
4
4
  import { EmailService, EmailConfig } from "../email/index.js";
5
5
  import { HonoEnv } from "../api/types.js";
6
6
  import { type CaptchaConfig } from "./captcha.js";
7
+ import { isDeliverableAddress } from "./deliverable-address.js";
8
+ export { isDeliverableAddress };
7
9
  /**
8
10
  * Shared configuration for auth and admin route factories.
9
11
  */
@@ -86,7 +88,30 @@ export interface AuthModuleConfig {
86
88
  * how long a captured token stays useful to someone who copied it.
87
89
  */
88
90
  refreshTokenReuseIntervalSeconds?: number;
91
+ /**
92
+ * Let a magic-link or email-code request for an address with no account
93
+ * create one (no password, unverified until the link or code is used),
94
+ * while registration is open. Off by default. See `passwordless-signup.ts`.
95
+ */
96
+ magicLinkCreatesUsers?: boolean;
97
+ /**
98
+ * What a refresh token presented after its reuse window does to its
99
+ * session. See `RebaseAuthConfig.refreshTokenReuse`. Default `"reject"`.
100
+ */
101
+ refreshTokenReuse?: RefreshTokenReusePolicy;
102
+ /**
103
+ * Refuse password sign-in until the account's address is verified, and
104
+ * register confirm-first: `POST /auth/register` answers the same "check
105
+ * your inbox" whether or not the address already has an account, and
106
+ * signs nobody in. Off by default. Needs email; the boot refuses it
107
+ * without. See `RebaseAuthConfig.requireEmailVerification`.
108
+ */
109
+ requireEmailVerification?: boolean;
89
110
  }
111
+ /** What a refresh token replayed after its reuse window does to its session. */
112
+ export type RefreshTokenReusePolicy = "reject" | "revoke-session";
113
+ /** How long an email-verification link stays usable. */
114
+ export declare const EMAIL_VERIFICATION_TTL_MS: number;
90
115
  /**
91
116
  * Configuration for httpOnly refresh-token cookies.
92
117
  */
@@ -109,4 +134,24 @@ export interface CookieAuthConfig {
109
134
  */
110
135
  secure?: boolean;
111
136
  }
137
+ /**
138
+ * What {@link CreateSessionAndTokens} is told about the sign-in it opens.
139
+ *
140
+ * `method` is what `providerId` says for the session from now on — stored on
141
+ * its refresh token and carried across every rotation — so each door names
142
+ * its own rather than leaving it to a default.
143
+ */
144
+ export interface SessionOptions {
145
+ /** `"password"`, `"anonymous"`, `"magic-link"`, `"otp"`, `"mfa"` or a provider id. */
146
+ method: string;
147
+ /** Only for the route that has just seen the second factor. */
148
+ skipMfaGate?: boolean;
149
+ aal?: "aal1" | "aal2";
150
+ }
151
+ /** Mint a session, as every sign-in door does. See `createSessionAndTokens`. */
152
+ export type CreateSessionAndTokens = (uid: string, userAgent: string, ipAddress: string, options: SessionOptions) => Promise<{
153
+ roleIds: string[];
154
+ accessToken: string;
155
+ refreshToken: string;
156
+ }>;
112
157
  export declare function createAuthRoutes(config: AuthModuleConfig): Hono<HonoEnv>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `GET /auth/scopes` — every scope this backend knows, described, and the
3
+ * ones the caller holds.
4
+ *
5
+ * What a screen needs to offer scopes for a key or a role: the built-in ones,
6
+ * the app's own `auth.scopes`, their wording and what their targets name. Any
7
+ * authenticated caller may read it; it describes the backend's vocabulary,
8
+ * not anybody's data.
9
+ *
10
+ * @module
11
+ */
12
+ import { Hono, type MiddlewareHandler } from "hono";
13
+ import type { AccessJudgeRepository } from "./token-revocation.js";
14
+ import type { HonoEnv } from "../api/types.js";
15
+ export interface ScopeRouteOptions {
16
+ serviceKey?: string;
17
+ resolveRoles?: (uid: string) => Promise<string[]>;
18
+ revocationRepo?: AccessJudgeRepository;
19
+ /** Authenticates `rk_` keys first, so a key can read what it holds. */
20
+ apiKeyPreAuth?: MiddlewareHandler<HonoEnv>;
21
+ }
22
+ export declare function createScopeRoutes(options: ScopeRouteOptions): Hono<HonoEnv>;
@@ -2,7 +2,7 @@ import { Hono } from "hono";
2
2
  import { z } from "zod";
3
3
  import { HonoEnv } from "../api/types.js";
4
4
  import type { MiddlewareHandler } from "hono";
5
- import type { AuthModuleConfig } from "./routes.js";
5
+ import type { AuthModuleConfig, CreateSessionAndTokens } from "./routes.js";
6
6
  import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
7
7
  import type { resolveAuthHooks } from "./auth-hooks.js";
8
8
  interface SessionRoutesConfig {
@@ -28,11 +28,7 @@ interface SessionRoutesConfig {
28
28
  isAnonymous?: boolean;
29
29
  metadata?: Record<string, unknown> | null;
30
30
  }, roleIds: string[], accessToken: string, refreshToken: string, providerId: string) => unknown;
31
- createSessionAndTokens: (uid: string, userAgent: string, ipAddress: string) => Promise<{
32
- roleIds: string[];
33
- accessToken: string;
34
- refreshToken: string;
35
- }>;
31
+ createSessionAndTokens: CreateSessionAndTokens;
36
32
  applyTransformHook: (response: AuthResponsePayload, method: TransformAuthResponseContext["method"], request: Request, uid: string) => Promise<AuthResponsePayload>;
37
33
  /**
38
34
  * The `register` captcha, built by the caller so a misconfiguration fails
@@ -41,6 +37,15 @@ interface SessionRoutesConfig {
41
37
  * `/register`. Absent when captcha is off or `register` is not protected.
42
38
  */
43
39
  registerCaptcha?: MiddlewareHandler<HonoEnv>;
40
+ /**
41
+ * Mail the account a verification link, in the background. Upgrading a
42
+ * guest is registration, and registration starts the address proof.
43
+ */
44
+ sendVerificationMail?: (user: {
45
+ id: string;
46
+ email: string;
47
+ displayName?: string | null;
48
+ }) => void;
44
49
  }
45
50
  export declare function mountSessionRoutes(opts: SessionRoutesConfig): void;
46
51
  export {};
@@ -37,6 +37,55 @@ import type { AccessTokenPayload } from "./jwt.js";
37
37
  * failure is logged at warn so it is visible rather than silent.
38
38
  */
39
39
  export declare function isAccessTokenRevoked(authRepo: Pick<AuthRepository, "getTokensValidAfter">, payload: Pick<AccessTokenPayload, "uid" | "iat">): Promise<boolean>;
40
+ /**
41
+ * The repository reads {@link judgeAccessToken} may make. All optional: a
42
+ * repository answers what it can, and the judge says what it could not ask.
43
+ */
44
+ export type AccessJudgeRepository = Partial<Pick<AuthRepository, "getAccountAccessState" | "getUserWithRoles" | "getTokensValidAfter">>;
45
+ /**
46
+ * Why an access token that verifies is not honoured.
47
+ *
48
+ * - `revoked`: issued before the account's revocation watermark — a sign-out
49
+ * everywhere, a password change or reset.
50
+ * - `session-revoked`: its own sign-in was ended — `POST /auth/logout`, or
51
+ * `DELETE /auth/sessions/:id` from another device. Needs the token's `sid`.
52
+ * - `account-deleted`: the account it names no longer exists.
53
+ * - `account-disabled`: an administrator switched the account off.
54
+ */
55
+ export type AccessTokenRefusal = "revoked" | "session-revoked" | "account-deleted" | "account-disabled";
56
+ export type AccessTokenVerdict = {
57
+ live: true;
58
+ /**
59
+ * The account's roles now, or `undefined` when the repository could
60
+ * not say (it reads neither the account nor its roles).
61
+ */
62
+ roles?: string[];
63
+ } | {
64
+ live: false;
65
+ refusal: AccessTokenRefusal;
66
+ };
67
+ /**
68
+ * Is the account behind this verified access token still the one that may use
69
+ * it — and with which roles?
70
+ *
71
+ * A verified signature says who the token was minted for, an hour ago at
72
+ * most. This asks the database what has happened to that account since. Every
73
+ * door that honours an access token — the data plane, the admin gates, the
74
+ * realtime socket — asks it here, so they cannot disagree about it.
75
+ *
76
+ * They did. A deleted account read as "not revoked": the watermark lives on
77
+ * the user row, so once the row was gone there was no watermark, and the
78
+ * roles lookup answered `[]` rather than "nobody". A token its owner had
79
+ * revoked came back to life when an administrator deleted the account, as an
80
+ * authenticated principal with that uid, for the rest of its lifetime. MCP
81
+ * refresh and personal API keys already treated a missing account as revoked;
82
+ * the main door did not.
83
+ *
84
+ * Throws when the repository does. Unlike the watermark read on its own, this
85
+ * one decides the roles a request runs with, so a failure is a refusal (the
86
+ * callers answer 503) rather than a guess.
87
+ */
88
+ export declare function judgeAccessToken(authRepo: AccessJudgeRepository, payload: Pick<AccessTokenPayload, "uid" | "iat" | "sid">): Promise<AccessTokenVerdict>;
40
89
  /**
41
90
  * End every session this user holds, on every device.
42
91
  *
@@ -72,4 +121,4 @@ export declare function revokeAllSessions(authRepo: Pick<AuthRepository, "delete
72
121
  * `null` removes the password instead, for the same reason and with the same
73
122
  * revocation: see `confirmAddressOwnership`.
74
123
  */
75
- export declare function replaceUserPassword(authRepo: Pick<AuthRepository, "updatePassword" | "deleteAllRefreshTokensForUser" | "setTokensValidAfter">, uid: string, passwordHash: string | null): Promise<void>;
124
+ export declare function replaceUserPassword(authRepo: Pick<AuthRepository, "updatePassword" | "deleteAllRefreshTokensForUser" | "setTokensValidAfter"> & Partial<Pick<AuthRepository, "deleteAllPasswordResetTokensForUser">>, uid: string, passwordHash: string | null): Promise<void>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Verify a bearer credential outside the HTTP middlewares — for a custom
3
+ * socket, a tunnel, anything an app authenticates from a frame or a header it
4
+ * read itself.
5
+ *
6
+ * One answer for the two kinds of credential a person can hold: a session
7
+ * token, and an API key (`rk_`). Both come back as who the caller acts as and
8
+ * what it may do, so the endpoint checks a scope the same way either way.
9
+ *
10
+ * @module
11
+ */
12
+ import type { ApiKeyStore } from "./api-keys/api-key-store.js";
13
+ /** Who a verified credential acts as, and what it may do. */
14
+ export interface VerifiedCredential {
15
+ uid: string;
16
+ roles: string[];
17
+ scopes: string[];
18
+ /** `"api-key"` when an `rk_` key was presented, otherwise `"session"`. */
19
+ kind: "session" | "api-key";
20
+ }
21
+ /** Install the key store `verifyCredential` checks `rk_` keys against. Called once at boot. */
22
+ export declare function configureCredentialStore(store: ApiKeyStore | undefined): void;
23
+ /**
24
+ * The identity and scopes behind a bearer credential, or null when it does
25
+ * not verify — expired, revoked, unknown, or a key on a backend with no key
26
+ * store.
27
+ */
28
+ export declare function verifyCredential(token: string): Promise<VerifiedCredential | null>;