@rebasepro/server 0.25.1 → 0.26.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 (157) hide show
  1. package/dist/{GCSStorageController-CRd9bFrc.js → GCSStorageController-40FyFeSx.js} +6 -2
  2. package/dist/GCSStorageController-40FyFeSx.js.map +1 -0
  3. package/dist/{S3StorageController-D00EFa5P.js → S3StorageController-Bl5shp94.js} +6 -2
  4. package/dist/S3StorageController-Bl5shp94.js.map +1 -0
  5. package/dist/access-BFAUvsSs.js +1897 -0
  6. package/dist/access-BFAUvsSs.js.map +1 -0
  7. package/dist/api/branch-routes.d.ts +25 -0
  8. package/dist/api/live-schema-routes.d.ts +10 -0
  9. package/dist/api/rest/api-generator.d.ts +9 -1
  10. package/dist/api/rest/idempotency.d.ts +15 -1
  11. package/dist/api/root-error-handler.d.ts +6 -5
  12. package/dist/api/types.d.ts +12 -0
  13. package/dist/{ast-schema-editor-mCeSC03x.js → ast-schema-editor-madTa9Hh.js} +2 -2
  14. package/dist/{ast-schema-editor-mCeSC03x.js.map → ast-schema-editor-madTa9Hh.js.map} +1 -1
  15. package/dist/auth/access.d.ts +10 -0
  16. package/dist/auth/adapter-middleware.d.ts +2 -1
  17. package/dist/auth/adapter-service-key.d.ts +24 -0
  18. package/dist/auth/api-keys/api-key-middleware.d.ts +21 -1
  19. package/dist/auth/api-keys/api-key-store.d.ts +8 -1
  20. package/dist/auth/api-keys/key-grant.d.ts +18 -1
  21. package/dist/auth/expired-token-sweep.d.ts +5 -0
  22. package/dist/auth/impersonation.d.ts +3 -1
  23. package/dist/auth/jwt.d.ts +8 -2
  24. package/dist/auth/oauth-signin-policy.d.ts +21 -35
  25. package/dist/auth/rls-scope.d.ts +17 -7
  26. package/dist/auth/rls-simulator-routes.d.ts +51 -0
  27. package/dist/auth/simulation-read-only.d.ts +28 -0
  28. package/dist/auth/simulation.d.ts +141 -0
  29. package/dist/auth/sql-rate-limit-store.d.ts +15 -1
  30. package/dist/{auth-Dkvo2tTJ.js → auth-izI7AEzC.js} +370 -1880
  31. package/dist/auth-izI7AEzC.js.map +1 -0
  32. package/dist/auth_adapter-B7RUMk6e.js +89 -0
  33. package/dist/auth_adapter-B7RUMk6e.js.map +1 -0
  34. package/dist/backend/create-backend.d.ts +6 -0
  35. package/dist/backend/options.d.ts +9 -4
  36. package/dist/{backend-DTAOsLQc.js → backend-CaG_Jyso.js} +9 -2
  37. package/dist/backend-CaG_Jyso.js.map +1 -0
  38. package/dist/{backup-B9m3cb3X.js → backup-BVbHoKo8.js} +6 -6
  39. package/dist/{backup-B9m3cb3X.js.map → backup-BVbHoKo8.js.map} +1 -1
  40. package/dist/boot/boot.d.ts +2 -15
  41. package/dist/boot/ddl-bootstrap.d.ts +34 -0
  42. package/dist/boot/env.d.ts +6 -0
  43. package/dist/boot/fetch-bundle.d.ts +5 -1
  44. package/dist/boot/provision-only.d.ts +23 -0
  45. package/dist/boot/provision.d.ts +53 -2
  46. package/dist/boot/role.d.ts +2 -0
  47. package/dist/boot/schema-change-recorder.d.ts +45 -0
  48. package/dist/boot/security-headers.d.ts +22 -0
  49. package/dist/branch-routes-B2vvEx53.js +84 -0
  50. package/dist/branch-routes-B2vvEx53.js.map +1 -0
  51. package/dist/{contract-routes-CsFXW7j4.js → contract-routes-CU2A3JnF.js} +2 -2
  52. package/dist/{contract-routes-CsFXW7j4.js.map → contract-routes-CU2A3JnF.js.map} +1 -1
  53. package/dist/cron/cron-store.d.ts +8 -1
  54. package/dist/{cron-loader-Dj6lA4HQ.js → cron-loader-mMhqbVsl.js} +2 -2
  55. package/dist/{cron-loader-Dj6lA4HQ.js.map → cron-loader-mMhqbVsl.js.map} +1 -1
  56. package/dist/{cron-routes-CwbLZguO.js → cron-routes-Cs33j_Kr.js} +3 -3
  57. package/dist/{cron-routes-CwbLZguO.js.map → cron-routes-Cs33j_Kr.js.map} +1 -1
  58. package/dist/{cron-scheduler-B04ACoUE.js → cron-scheduler-Be7CNghL.js} +2 -2
  59. package/dist/{cron-scheduler-B04ACoUE.js.map → cron-scheduler-Be7CNghL.js.map} +1 -1
  60. package/dist/{cron-store-DkyfuLOH.js → cron-store-DLn11M8H.js} +10 -9
  61. package/dist/cron-store-DLn11M8H.js.map +1 -0
  62. package/dist/{strings.es-BeZWAodn.js → data_driver-D0mg1Y_x.js} +2 -90
  63. package/dist/data_driver-D0mg1Y_x.js.map +1 -0
  64. package/dist/{ddl-bootstrap-Ck20GSIX.js → ddl-bootstrap-CjRrn12l.js} +91 -2
  65. package/dist/ddl-bootstrap-CjRrn12l.js.map +1 -0
  66. package/dist/deploy/pod-contract.d.ts +1 -1
  67. package/dist/email/dev-sink.d.ts +6 -0
  68. package/dist/email/index.d.ts +1 -1
  69. package/dist/{soft-delete-params-BmlVwecC.js → field-access-DYsvjI_P.js} +27 -57
  70. package/dist/field-access-DYsvjI_P.js.map +1 -0
  71. package/dist/{filter-dialect-O1HK6Nvr.js → filter-dialect-TrKFWLqT.js} +3 -3
  72. package/dist/{filter-dialect-O1HK6Nvr.js.map → filter-dialect-TrKFWLqT.js.map} +1 -1
  73. package/dist/{function-loader-KKDqTcIr.js → function-loader-B8YzXEsQ.js} +21 -14
  74. package/dist/function-loader-B8YzXEsQ.js.map +1 -0
  75. package/dist/functions/hono-like.d.ts +12 -0
  76. package/dist/functions/index.js +19 -1
  77. package/dist/functions/index.js.map +1 -1
  78. package/dist/functions/request-timeout.d.ts +3 -3
  79. package/dist/history/history-routes.d.ts +6 -0
  80. package/dist/history/retention-sweep.d.ts +5 -0
  81. package/dist/{history-recorder-B2rkuhuy.js → history-recorder-Bsau7h4X.js} +6 -6
  82. package/dist/history-recorder-Bsau7h4X.js.map +1 -0
  83. package/dist/{history-store-DPofGVDp.js → history-store-BQz5YCkW.js} +26 -2
  84. package/dist/history-store-BQz5YCkW.js.map +1 -0
  85. package/dist/index.d.ts +3 -1
  86. package/dist/index.es.js +2449 -543
  87. package/dist/index.es.js.map +1 -1
  88. package/dist/init/process-safety.d.ts +4 -3
  89. package/dist/init/shutdown.d.ts +8 -0
  90. package/dist/init.d.ts +6 -1
  91. package/dist/jobs/job-store.d.ts +8 -1
  92. package/dist/{jobs-h1WRq3cp.js → jobs-Co6Ni5HP.js} +6 -6
  93. package/dist/jobs-Co6Ni5HP.js.map +1 -0
  94. package/dist/{jwt-D3oFauZI.js → jwt-rDS0884_.js} +4 -3
  95. package/dist/{jwt-D3oFauZI.js.map → jwt-rDS0884_.js.map} +1 -1
  96. package/dist/{keys-COzQ2aE8.js → keys-BXcdRNhJ.js} +2 -2
  97. package/dist/{keys-COzQ2aE8.js.map → keys-BXcdRNhJ.js.map} +1 -1
  98. package/dist/mcp/consent-page.d.ts +38 -1
  99. package/dist/mcp/mcp-routes.d.ts +9 -0
  100. package/dist/mcp/mcp-tools.d.ts +7 -1
  101. package/dist/mcp/oauth-store.d.ts +12 -5
  102. package/dist/metrics/history-recorder.d.ts +4 -1
  103. package/dist/metrics/history-store.d.ts +8 -0
  104. package/dist/{offline-BrqCRgih-BtDB1ecy.js → offline-AvuEcg7u-DFHL98OT.js} +127 -40
  105. package/dist/offline-AvuEcg7u-DFHL98OT.js.map +1 -0
  106. package/dist/{offline-store-BhYXViha-BW5j1We_.js → offline-store-BhYXViha-YTmjBpo2.js} +5 -5
  107. package/dist/{offline-store-BhYXViha-BW5j1We_.js.map → offline-store-BhYXViha-YTmjBpo2.js.map} +1 -1
  108. package/dist/{openapi-generator-BjHr6upl.js → openapi-generator-CyxEtYEX.js} +61 -22
  109. package/dist/openapi-generator-CyxEtYEX.js.map +1 -0
  110. package/dist/{proxy-CH8Jsovg.js → proxy-BYir8tWK.js} +4 -6
  111. package/dist/proxy-BYir8tWK.js.map +1 -0
  112. package/dist/{query-parser-otFZqJsC.js → query-parser-CDXWf2hH.js} +6 -5
  113. package/dist/{query-parser-otFZqJsC.js.map → query-parser-CDXWf2hH.js.map} +1 -1
  114. package/dist/{request-timeout-59crS-9u.js → request-timeout-DVjo-c_8.js} +4 -4
  115. package/dist/request-timeout-DVjo-c_8.js.map +1 -0
  116. package/dist/rls-simulator-routes-bEY7-5-u.js +190 -0
  117. package/dist/rls-simulator-routes-bEY7-5-u.js.map +1 -0
  118. package/dist/schema-edit/deployed-schema-guard.d.ts +47 -0
  119. package/dist/{schema-editor-routes-Bsut0bCC.js → schema-editor-routes-Dc3DU_jm.js} +3 -3
  120. package/dist/{schema-editor-routes-Bsut0bCC.js.map → schema-editor-routes-Dc3DU_jm.js.map} +1 -1
  121. package/dist/soft-delete-params-BRONCOA2.js +59 -0
  122. package/dist/soft-delete-params-BRONCOA2.js.map +1 -0
  123. package/dist/{sql-export-routes-DmB_uZ4l.js → sql-export-routes-QycX8W8S.js} +38 -4
  124. package/dist/sql-export-routes-QycX8W8S.js.map +1 -0
  125. package/dist/{src-D5Y3N-S4.js → src-Bh9LRPb9.js} +21 -2
  126. package/dist/{src-D5Y3N-S4.js.map → src-Bh9LRPb9.js.map} +1 -1
  127. package/dist/storage/GCSStorageController.d.ts +2 -0
  128. package/dist/storage/LocalStorageController.d.ts +8 -3
  129. package/dist/storage/S3StorageController.d.ts +2 -0
  130. package/dist/storage/maintenance-keys.d.ts +17 -0
  131. package/dist/storage/maintenance-routes.d.ts +28 -5
  132. package/dist/storage/maintenance.d.ts +136 -4
  133. package/dist/storage/orphan-scans.d.ts +120 -0
  134. package/dist/storage/types.d.ts +11 -0
  135. package/dist/strings.es-B7QLnHh2.js +96 -0
  136. package/dist/strings.es-B7QLnHh2.js.map +1 -0
  137. package/dist/upload-validation-Bgo626uY.js.map +1 -1
  138. package/package.json +5 -5
  139. package/dist/GCSStorageController-CRd9bFrc.js.map +0 -1
  140. package/dist/S3StorageController-D00EFa5P.js.map +0 -1
  141. package/dist/auth-Dkvo2tTJ.js.map +0 -1
  142. package/dist/auth_adapter-CIItCTc8.js +0 -21
  143. package/dist/auth_adapter-CIItCTc8.js.map +0 -1
  144. package/dist/backend-DTAOsLQc.js.map +0 -1
  145. package/dist/cron-store-DkyfuLOH.js.map +0 -1
  146. package/dist/ddl-bootstrap-Ck20GSIX.js.map +0 -1
  147. package/dist/function-loader-KKDqTcIr.js.map +0 -1
  148. package/dist/history-recorder-B2rkuhuy.js.map +0 -1
  149. package/dist/history-store-DPofGVDp.js.map +0 -1
  150. package/dist/jobs-h1WRq3cp.js.map +0 -1
  151. package/dist/offline-BrqCRgih-BtDB1ecy.js.map +0 -1
  152. package/dist/openapi-generator-BjHr6upl.js.map +0 -1
  153. package/dist/proxy-CH8Jsovg.js.map +0 -1
  154. package/dist/request-timeout-59crS-9u.js.map +0 -1
  155. package/dist/soft-delete-params-BmlVwecC.js.map +0 -1
  156. package/dist/sql-export-routes-DmB_uZ4l.js.map +0 -1
  157. package/dist/strings.es-BeZWAodn.js.map +0 -1
@@ -6,25 +6,17 @@
6
6
  /** Why an incoming OAuth identity may not be auto-attached to an existing account. */
7
7
  export type AutoLinkRefusal =
8
8
  /** The provider did not report that it verified the address. */
9
- "provider-email-unverified"
10
- /**
11
- * The local account holds a password nobody ever proved they own: it was
12
- * created through `POST /auth/register`, which does not verify the
13
- * address. Attaching a provider identity to it would hand the session to
14
- * whoever registered the address first — the classic pre-hijack.
15
- */
16
- | "local-account-unverified"
17
- /**
18
- * The local account has no password, and its address was never proven
19
- * either: it was made by a sign-in through a provider that did not vouch
20
- * for the address, or by someone who never had to. Its sign-in methods are
21
- * whatever that someone attached, so this is the same pre-hijack as a
22
- * password would be. Reported apart from `local-account-unverified` only
23
- * so a login screen does not tell its owner to use a password.
24
- */
25
- | "local-account-unverified-passwordless";
9
+ "provider-email-unverified";
26
10
  export type AutoLinkDecision = {
27
11
  allowed: true;
12
+ /**
13
+ * The local account's address was never proven, and this sign-in
14
+ * proves it. The route settles the account the way an email code does
15
+ * (`confirmAddressOwnership`) before it links the identity: the
16
+ * password and every identity whose provider did not vouch for the
17
+ * address go, and every session ends.
18
+ */
19
+ provesAddress: boolean;
28
20
  } | {
29
21
  allowed: false;
30
22
  reason: AutoLinkRefusal;
@@ -37,25 +29,19 @@ export interface AutoLinkExistingUser {
37
29
  /**
38
30
  * May an OAuth identity be attached to a pre-existing account found *by email*?
39
31
  *
40
- * Both sides have to be trustworthy:
41
- *
42
- * - the **provider** must have verified the address, or the caller has not
43
- * shown they control it;
44
- * - the **local account**'s address must have been verified too. Whoever
45
- * made an unverified account never proved the address, and every way in
46
- * they left on it — a password, an identity from a provider that did not
47
- * vouch for the address — would go on opening the account the owner is
48
- * about to be signed into.
49
- *
50
- * The second rule used to exempt an account with no password, on the theory
51
- * that one made by an OAuth sign-in holds no credential an attacker could have
52
- * planted. It holds exactly one: the identity it was made with, which is an
53
- * attacker's whenever the provider did not vouch for the address.
32
+ * Only when the **provider** verified the address. Otherwise the caller has
33
+ * not shown they control it, and linking would hand them the account.
54
34
  *
55
- * A refusal is not a dead end. `POST /auth/link/<provider>` attaches the
56
- * identity once the caller proves ownership by holding a session, and proving
57
- * the address (a magic link, an email code, a password reset) verifies the
58
- * account — removing what nobody proved — after which this answers yes.
35
+ * When it did, the caller has proven the address exactly as reading a code out
36
+ * of the inbox does: only the inbox's owner can hold a provider account that
37
+ * vouches for it. If the **local account**'s address was never verified,
38
+ * whoever made it never proved the address, and every way in they left on it —
39
+ * a password, an identity from a provider that did not vouch for the address,
40
+ * a live session — would go on opening the account the owner is about to be
41
+ * signed into. So that case answers `provesAddress: true`, and the route
42
+ * removes those ways in before it links (the pre-hijack defence). It used to
43
+ * refuse instead, which left the owner of the address locked out of their own
44
+ * address until they found a magic link or a reset.
59
45
  */
60
46
  export declare function decideOAuthAutoLink(args: {
61
47
  providerEmailVerified: boolean | undefined;
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * @module
13
13
  */
14
- import type { DataDriver } from "@rebasepro/types";
14
+ import type { CallerCredential, DataDriver } from "@rebasepro/types";
15
15
  /**
16
16
  * The identity the trusted server plane runs as: `rebase.dataAsAdmin`, and any
17
17
  * caller presenting the service key.
@@ -37,6 +37,21 @@ export declare const SERVICE_IDENTITY: {
37
37
  uid: string;
38
38
  roles: string[];
39
39
  };
40
+ /**
41
+ * Who a request runs as, as a driver is scoped to it.
42
+ *
43
+ * `credential` is the API key the request authenticated with, when it was one.
44
+ * It changes nothing the database sees; the driver hands it to collection
45
+ * callbacks as `context.credential`, so a personal key's write can be told
46
+ * from its owner's own.
47
+ */
48
+ export interface ScopeIdentity {
49
+ uid: string;
50
+ roles?: string[];
51
+ isAnonymous?: boolean;
52
+ claims?: Record<string, unknown>;
53
+ credential?: CallerCredential;
54
+ }
40
55
  /**
41
56
  * Can this driver run a request as a specific user?
42
57
  *
@@ -71,9 +86,4 @@ export declare function scopeDataDriver(driver: DataDriver,
71
86
  * without this the database sees the same principal either way. See
72
87
  * `rebase.is_anonymous()`.
73
88
  */
74
- user: {
75
- uid: string;
76
- roles?: string[];
77
- isAnonymous?: boolean;
78
- claims?: Record<string, unknown>;
79
- }): Promise<DataDriver>;
89
+ user: ScopeIdentity): Promise<DataDriver>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The RLS simulator's admin surface, under `/api/admin/rls-simulator`.
3
+ *
4
+ * The data API answers one collection at a time for a simulated identity
5
+ * (`auth/simulation.ts`). This answers the questions the admin panel's
6
+ * "Simulate access" bar asks about all of them at once:
7
+ *
8
+ * - `GET /options` — what can be simulated: the roles the project names (the
9
+ * access model's, the ones its row and field rules mention, and `anon`) and
10
+ * the API keys that are live.
11
+ * - `POST /preview` — for `{ role?, userId?, apiKeyId? }`, each collection's
12
+ * rows as that identity sees them against the caller's own view, whether a
13
+ * key's scopes shut it out, and which fields its roles cannot read.
14
+ *
15
+ * The same decision as the data API: only an administrator's own session, a
16
+ * 403 for anyone else.
17
+ *
18
+ * @module
19
+ */
20
+ import { Hono } from "hono";
21
+ import { type CollectionConfig, type DataDriver } from "@rebasepro/types";
22
+ import type { HonoEnv } from "../api/types.js";
23
+ import type { ApiKeyStore } from "./api-keys/api-key-store.js";
24
+ import type { ImpersonationUserResolver } from "./impersonation.js";
25
+ export interface RlsSimulatorRouteConfig {
26
+ /** The collections this backend serves, as they are now. */
27
+ collections(): readonly CollectionConfig[];
28
+ /** The unscoped driver a collection's rows live in. */
29
+ driverFor(slug: string): DataDriver;
30
+ resolveUser?: ImpersonationUserResolver;
31
+ apiKeyStore?: ApiKeyStore;
32
+ }
33
+ /** One collection, as the simulated identity reaches it. */
34
+ export interface SimulatedCollectionView {
35
+ slug: string;
36
+ name: string;
37
+ /**
38
+ * Why the identity sees nothing here: its key's scopes, row policies
39
+ * hiding every row, or a read that fails for it (`error` says why).
40
+ */
41
+ hiddenBy?: "scope" | "rls" | "error";
42
+ /** Rows the simulated identity reads. Absent when not counted. */
43
+ visibleRows?: number;
44
+ /** Rows the administrator asking reads. */
45
+ totalRows?: number;
46
+ /** Fields the identity's roles cannot read. */
47
+ maskedColumns: string[];
48
+ /** Why the rows could not be counted. */
49
+ error?: string;
50
+ }
51
+ export declare function createRlsSimulatorRoutes(config: RlsSimulatorRouteConfig): Hono<HonoEnv>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * "Simulate access" is read-only.
3
+ *
4
+ * The simulator answers "what does this role, user or key see". A write made
5
+ * while simulating ran as the simulated identity for real: it committed as
6
+ * `rebase_user` with `app.uid` set to the simulated uid, `entity_history`
7
+ * recorded that uid as the author, callbacks saw `context.user` holding a role
8
+ * the account may not even have, and a role-only simulation stamped owner
9
+ * columns with the nobody uid. Only the request log named the administrator.
10
+ * So every door that honours the simulation headers refuses anything but a
11
+ * read, naming the simulation, rather than performing it as someone else.
12
+ *
13
+ * Its own module so the REST generator, history routes and the functions
14
+ * guard can share it without importing the simulator itself.
15
+ *
16
+ * @module
17
+ */
18
+ import type { DataOperation } from "./api-keys/http-operation.js";
19
+ /**
20
+ * Refuse a write or delete on a request that simulates access.
21
+ *
22
+ * `c` is anything with the request context's `get` — the REST generator calls
23
+ * this with a per-operation stand-in for each operation of a `_batch`.
24
+ * `operation` is what the request does, not its verb: `POST …/query` is a read.
25
+ */
26
+ export declare function refuseSimulatedWrite(c: {
27
+ get: (key: string) => unknown;
28
+ }, operation: DataOperation): void;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The RLS simulator: an administrator's data-plane request run as a role, a
3
+ * user or a service key — `x-rebase-simulate-role`, `x-rebase-simulate-user-id`
4
+ * and `x-rebase-simulate-api-key-id`.
5
+ *
6
+ * Impersonation ({@link ./impersonation}) answers "what does this person see";
7
+ * this answers the question an author asks while writing policies, before any
8
+ * such person exists: "what does a `customer` see, what does `u_1` holding
9
+ * `customer` see, what does the support agent's key see". So a role need not be
10
+ * held by anyone and a uid sent with a role need not name an account. Nothing
11
+ * here widens what the caller could do: the caller is an administrator, whom
12
+ * the default policies already let read and write every row, and every
13
+ * simulated identity is narrower.
14
+ *
15
+ * The identity reaches the database exactly as a real caller's does —
16
+ * `scopeDataDriver` → `withAuth` → `applyAuthContext`, which sets the
17
+ * transaction-local identity (`app.uid`, `app.roles`, `app.jwt`) and switches to
18
+ * `rebase_user`, so every policy is evaluated for it. A simulated key also
19
+ * carries its scopes, so the data API refuses the collections the key cannot
20
+ * reach, as it would refuse the key. Fields the identity's roles cannot read
21
+ * are stripped by the driver as for any caller; {@link reportSimulatedContext}
22
+ * names them on the response.
23
+ *
24
+ * Who may: a signed-in administrator on their own session, judged on their
25
+ * roles as the database has them now. Never an API key, never the service
26
+ * key, never anyone else — a 403, never an answer as themselves.
27
+ *
28
+ * Where: the data API and custom functions, like impersonation. Every other
29
+ * route refuses the headers (`refuseUnhonouredImpersonation`).
30
+ *
31
+ * Read-only: a write, delete, batch, revert or non-GET function call that
32
+ * carries the headers is refused (`SIMULATION_READ_ONLY`, see
33
+ * {@link ./simulation-read-only}) rather than run as the simulated identity.
34
+ *
35
+ * Not simulated: the data API's `requireAuth` gate. A simulated anonymous
36
+ * caller is let past it, so the answer is what the rows' policies give an
37
+ * anonymous caller — a 401 there would send the admin panel to refresh a
38
+ * session that is fine. Everywhere else it is no session, as a real anonymous
39
+ * caller is: it carries no `user`, so a function's `requireAuth` refuses it.
40
+ *
41
+ * @module
42
+ */
43
+ import type { Context, MiddlewareHandler } from "hono";
44
+ import { type AccessSimulation, type ApiKeyMasked, type CollectionConfig, type DataDriver } from "@rebasepro/types";
45
+ import type { HonoEnv } from "../api/types.js";
46
+ import { ApiError } from "../api/errors.js";
47
+ import type { ImpersonationCredential, ImpersonationUserResolver } from "./impersonation.js";
48
+ import type { ApiKeyStore } from "./api-keys/api-key-store.js";
49
+ import { type AccessControl } from "./access.js";
50
+ /**
51
+ * The uid a role simulated on its own runs as: a valid UUID that no row
52
+ * names, so an ownership policy matches nothing and a policy comparing a
53
+ * `uuid` column to `auth.uid()` still casts cleanly.
54
+ */
55
+ export declare const SIMULATED_NOBODY_UID = "00000000-0000-0000-0000-000000000000";
56
+ /** Who a granted simulation runs as. */
57
+ export interface SimulatedIdentity {
58
+ uid: string;
59
+ roles: string[];
60
+ isAnonymous: boolean;
61
+ claims?: Record<string, unknown>;
62
+ /** A key's scopes. Absent for a person, whose data plane is RLS's to decide. */
63
+ scopes?: string[];
64
+ apiKey?: ApiKeyMasked;
65
+ /** What the response header names: the role (or roles) and the uid. */
66
+ role: string;
67
+ }
68
+ export interface SimulationRequest {
69
+ simulation: AccessSimulation;
70
+ /** What the caller presented. Only a `session` may simulate. */
71
+ credential: ImpersonationCredential;
72
+ caller?: {
73
+ uid: string;
74
+ roles: readonly string[];
75
+ };
76
+ /** Re-reads the caller's roles, and finds a user named without a role. */
77
+ resolveUser?: ImpersonationUserResolver;
78
+ /** Finds a key named by id. Absent: a key simulation is refused. */
79
+ apiKeyStore?: ApiKeyStore;
80
+ /** Whose access model a personal key's owner is judged against. */
81
+ access?: AccessControl;
82
+ /** Where the request came in, for the audit line; absent writes none. */
83
+ audit?: Record<string, unknown>;
84
+ }
85
+ export type SimulationDecision = {
86
+ granted: SimulatedIdentity;
87
+ simulator: string;
88
+ } | {
89
+ refused: ApiError;
90
+ };
91
+ /** Read the three simulation headers, or `undefined` when none was sent. */
92
+ export declare function simulationOf(header: (name: string) => string | undefined): AccessSimulation | undefined;
93
+ /** Whether a request carries any simulation header. */
94
+ export declare function carriesSimulation(header: (name: string) => string | undefined): boolean;
95
+ /**
96
+ * Decide whether this caller may simulate what they asked for, and who the
97
+ * request then runs as.
98
+ *
99
+ * The caller is judged before anything they named is looked up, so a refusal
100
+ * says nothing about whether a user or key exists. Throws when a lookup fails.
101
+ */
102
+ export declare function decideSimulation(request: SimulationRequest): Promise<SimulationDecision>;
103
+ export interface SimulationOptions {
104
+ credential: ImpersonationCredential;
105
+ /** The unscoped delegate the request's driver was scoped from. */
106
+ driver: DataDriver;
107
+ resolveUser?: ImpersonationUserResolver;
108
+ apiKeyStore?: ApiKeyStore;
109
+ }
110
+ /** The caller the auth middleware put on the context, narrowed. */
111
+ export declare function callerOf(c: Context<HonoEnv>): {
112
+ uid: string;
113
+ roles: string[];
114
+ } | undefined;
115
+ /**
116
+ * Honour or refuse this request's simulation headers.
117
+ *
118
+ * Called by the data-plane auth middlewares once the caller is on the context
119
+ * and before impersonation, which it excludes. On a grant the context's
120
+ * `user`, `driver`, `scopes` and `apiKey` become the simulated identity's,
121
+ * `impersonator` names the administrator for the request log, and
122
+ * `simulation` records what to report on the response.
123
+ *
124
+ * @returns The refusal to send, or `undefined` to carry on.
125
+ */
126
+ export declare function applySimulation(c: Context<HonoEnv>, { credential, driver, resolveUser, apiKeyStore }: SimulationOptions): Promise<Response | undefined>;
127
+ /**
128
+ * The fields of `collection` the roles cannot read: what the driver strips
129
+ * from every row it hands them. Fields hidden from everyone (`excludeFromApi`)
130
+ * are left out — no role sees those, so they say nothing about this one.
131
+ */
132
+ export declare function maskedFieldsFor(collection: CollectionConfig, roles: readonly string[]): string[];
133
+ /**
134
+ * Name what a simulated request ran as on its response
135
+ * ({@link SIMULATED_CONTEXT_HEADER}), refusals included.
136
+ *
137
+ * Mounted ahead of the data-plane groups; it reads, after the request is
138
+ * answered, what {@link applySimulation} and the REST generator recorded on
139
+ * the context — the identity, and which collection the request was about.
140
+ */
141
+ export declare function reportSimulatedContext(): MiddlewareHandler<HonoEnv>;
@@ -46,6 +46,12 @@ import type { RateLimitStore } from "./rate-limit-store.js";
46
46
  export interface SqlRateLimitStoreOptions {
47
47
  /** How often to delete expired buckets. Defaults to 5 minutes. */
48
48
  sweepMs?: number;
49
+ /**
50
+ * Default `true`. `false` is a backend that changes nothing in its
51
+ * database: the table is used if it is there, never created, and counts
52
+ * stay per-process without it.
53
+ */
54
+ provision?: boolean;
49
55
  /**
50
56
  * Clock, for tests. Driving the window directly beats sleeping through half
51
57
  * of one — a real sleep overruns on a loaded machine and takes the hits it
@@ -60,4 +66,12 @@ export interface SqlRateLimitStoreOptions {
60
66
  * deployment on a driver with no SQL admin should degrade to per-process counts
61
67
  * with a warning, not refuse to serve.
62
68
  */
63
- export declare function createSqlRateLimitStore(driver: DataDriver, options?: SqlRateLimitStoreOptions): RateLimitStore | undefined;
69
+ /** The shared store, plus the one step a provisioning run takes ahead of time. */
70
+ export interface SqlRateLimitStore extends RateLimitStore {
71
+ /**
72
+ * Create the table now rather than on the first counted request — what a
73
+ * provisioning run calls. Resolves to whether it is usable.
74
+ */
75
+ ensureTable(): Promise<boolean>;
76
+ }
77
+ export declare function createSqlRateLimitStore(driver: DataDriver, options?: SqlRateLimitStoreOptions): SqlRateLimitStore | undefined;