@rebasepro/server 0.13.1-canary.gef9608c → 0.14.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 (132) hide show
  1. package/dist/{admin_block-EmIHae6X.js → admin_block-H86dCPsj.js} +61 -2
  2. package/dist/admin_block-H86dCPsj.js.map +1 -0
  3. package/dist/api/ast-schema-editor.d.ts +74 -6
  4. package/dist/api/errors.d.ts +12 -0
  5. package/dist/api/rest/api-generator.d.ts +26 -1
  6. package/dist/api/rest/idempotency.d.ts +25 -4
  7. package/dist/api/rest/query-parser.d.ts +15 -1
  8. package/dist/api/rest/write-validation.d.ts +40 -4
  9. package/dist/auth/adapter-middleware.d.ts +3 -1
  10. package/dist/auth/admin-roles.d.ts +24 -0
  11. package/dist/auth/apple-oauth.d.ts +16 -11
  12. package/dist/auth/bitbucket-oauth.d.ts +2 -4
  13. package/dist/auth/builtin-auth-adapter.d.ts +11 -0
  14. package/dist/auth/discord-oauth.d.ts +5 -6
  15. package/dist/auth/facebook-oauth.d.ts +2 -4
  16. package/dist/auth/github-oauth.d.ts +2 -4
  17. package/dist/auth/gitlab-oauth.d.ts +6 -4
  18. package/dist/auth/google-oauth.d.ts +1 -0
  19. package/dist/auth/index.d.ts +7 -0
  20. package/dist/auth/interfaces.d.ts +81 -1
  21. package/dist/auth/jwt.d.ts +40 -0
  22. package/dist/auth/linkedin-oauth.d.ts +2 -4
  23. package/dist/auth/mfa-crypto.d.ts +54 -3
  24. package/dist/auth/mfa-gate.d.ts +42 -0
  25. package/dist/auth/mfa-routes.d.ts +26 -1
  26. package/dist/auth/mfa.d.ts +16 -0
  27. package/dist/auth/microsoft-oauth.d.ts +19 -4
  28. package/dist/auth/middleware.d.ts +26 -0
  29. package/dist/auth/oauth-code-flow.d.ts +66 -0
  30. package/dist/auth/oauth-signin-policy.d.ts +61 -0
  31. package/dist/auth/oidc-id-token.d.ts +61 -0
  32. package/dist/auth/rate-limiter.d.ts +62 -5
  33. package/dist/auth/registration-policy.d.ts +31 -0
  34. package/dist/auth/rls-scope.d.ts +25 -0
  35. package/dist/auth/routes.d.ts +15 -0
  36. package/dist/auth/slack-oauth.d.ts +2 -4
  37. package/dist/auth/spotify-oauth.d.ts +2 -4
  38. package/dist/auth/token-revocation.d.ts +38 -0
  39. package/dist/auth/twitter-oauth.d.ts +2 -5
  40. package/dist/{auth-DU4ShERo.js → auth-CYoPVf-E.js} +1985 -395
  41. package/dist/auth-CYoPVf-E.js.map +1 -0
  42. package/dist/{backup-BJiY_mM-.js → backup-C6ljYVTp.js} +2 -2
  43. package/dist/{backup-BJiY_mM-.js.map → backup-C6ljYVTp.js.map} +1 -1
  44. package/dist/boot/ddl-bootstrap.d.ts +70 -0
  45. package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-Bet-eCNJ.js} +3 -3
  46. package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-Bet-eCNJ.js.map} +1 -1
  47. package/dist/cron/cron-loader.d.ts +26 -1
  48. package/dist/cron/cron-routes.d.ts +1 -1
  49. package/dist/cron/cron-scheduler.d.ts +13 -3
  50. package/dist/cron/define-cron.d.ts +17 -3
  51. package/dist/cron/index.d.ts +2 -2
  52. package/dist/{cron-loader-B1S2MCSl.js → cron-loader-YhhQeVBM.js} +43 -8
  53. package/dist/cron-loader-YhhQeVBM.js.map +1 -0
  54. package/dist/{cron-routes-D5a9v1HY.js → cron-routes-maM_RlUu.js} +10 -4
  55. package/dist/cron-routes-maM_RlUu.js.map +1 -0
  56. package/dist/{cron-scheduler-BhFZWR0T.js → cron-scheduler-DIpYBmZP.js} +20 -8
  57. package/dist/cron-scheduler-DIpYBmZP.js.map +1 -0
  58. package/dist/{cron-store-Cu4sOseb.js → cron-store-Dvr4Y1sZ.js} +60 -51
  59. package/dist/cron-store-Dvr4Y1sZ.js.map +1 -0
  60. package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
  61. package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
  62. package/dist/email/html.d.ts +54 -0
  63. package/dist/email/index.d.ts +3 -0
  64. package/dist/email/link-base.d.ts +39 -0
  65. package/dist/email/smtp-email-service.d.ts +7 -1
  66. package/dist/email/templates.d.ts +9 -1
  67. package/dist/email/types.d.ts +11 -1
  68. package/dist/env.d.ts +5 -0
  69. package/dist/{errors-BpudWAVU.js → errors-EBYiaJ2E.js} +14 -2
  70. package/dist/errors-EBYiaJ2E.js.map +1 -0
  71. package/dist/function-loader-DDS1v7YX.js +139 -0
  72. package/dist/function-loader-DDS1v7YX.js.map +1 -0
  73. package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
  74. package/dist/function-routes-Btcez1T-.js.map +1 -0
  75. package/dist/functions/define-function.d.ts +25 -6
  76. package/dist/functions/function-loader.d.ts +23 -0
  77. package/dist/functions/function-routes.d.ts +7 -1
  78. package/dist/functions/request-timeout.d.ts +34 -0
  79. package/dist/history/history-routes.d.ts +6 -0
  80. package/dist/index.d.ts +3 -2
  81. package/dist/index.es.js +1893 -714
  82. package/dist/index.es.js.map +1 -1
  83. package/dist/init/docs.d.ts +10 -1
  84. package/dist/init/process-safety.d.ts +25 -0
  85. package/dist/init.d.ts +38 -0
  86. package/dist/{jwt-CzeviDcB.js → jwt-_IFqfTOg.js} +57 -9
  87. package/dist/jwt-_IFqfTOg.js.map +1 -0
  88. package/dist/logger-DfvF_8r-.js +190 -0
  89. package/dist/logger-DfvF_8r-.js.map +1 -0
  90. package/dist/{openapi-generator-D5xsfVJY.js → openapi-generator-DPKtUC9X.js} +326 -55
  91. package/dist/openapi-generator-DPKtUC9X.js.map +1 -0
  92. package/dist/request-timeout-RivJsME0.js +65 -0
  93. package/dist/request-timeout-RivJsME0.js.map +1 -0
  94. package/dist/schema-editor-routes-CRcS3ArS.js +437 -0
  95. package/dist/schema-editor-routes-CRcS3ArS.js.map +1 -0
  96. package/dist/serve-spa.d.ts +5 -4
  97. package/dist/services/outbound-url-guard.d.ts +53 -0
  98. package/dist/services/routed-realtime-service.d.ts +10 -2
  99. package/dist/services/webhook-service.d.ts +76 -1
  100. package/dist/singleton.d.ts +25 -8
  101. package/dist/{src-CKOZBpeJ.js → src-C7rkDGxA.js} +619 -70
  102. package/dist/src-C7rkDGxA.js.map +1 -0
  103. package/dist/{src-_qQ3RNCK.js → src-Cz9nMgUR.js} +93 -3
  104. package/dist/src-Cz9nMgUR.js.map +1 -0
  105. package/dist/storage/LocalStorageController.d.ts +12 -1
  106. package/dist/storage/image-transform.d.ts +57 -0
  107. package/dist/storage/keys.d.ts +98 -0
  108. package/dist/storage/routes.d.ts +11 -0
  109. package/dist/storage/tus-handler.d.ts +22 -2
  110. package/dist/utils/logger.d.ts +18 -0
  111. package/dist/utils/sql.d.ts +11 -6
  112. package/package.json +6 -5
  113. package/dist/admin_block-EmIHae6X.js.map +0 -1
  114. package/dist/auth-DU4ShERo.js.map +0 -1
  115. package/dist/backend-CIxN4FVm.js +0 -15
  116. package/dist/backend-CIxN4FVm.js.map +0 -1
  117. package/dist/cron-loader-B1S2MCSl.js.map +0 -1
  118. package/dist/cron-routes-D5a9v1HY.js.map +0 -1
  119. package/dist/cron-scheduler-BhFZWR0T.js.map +0 -1
  120. package/dist/cron-store-Cu4sOseb.js.map +0 -1
  121. package/dist/errors-BpudWAVU.js.map +0 -1
  122. package/dist/function-loader-B_1fYfUY.js +0 -86
  123. package/dist/function-loader-B_1fYfUY.js.map +0 -1
  124. package/dist/function-routes-C0cLIy3N.js.map +0 -1
  125. package/dist/jwt-CzeviDcB.js.map +0 -1
  126. package/dist/logger-BYU66ENZ.js +0 -94
  127. package/dist/logger-BYU66ENZ.js.map +0 -1
  128. package/dist/openapi-generator-D5xsfVJY.js.map +0 -1
  129. package/dist/schema-editor-routes-C1DxDnDR.js +0 -248
  130. package/dist/schema-editor-routes-C1DxDnDR.js.map +0 -1
  131. package/dist/src-CKOZBpeJ.js.map +0 -1
  132. package/dist/src-_qQ3RNCK.js.map +0 -1
@@ -32,9 +32,23 @@ interface RateLimiterOptions {
32
32
  * appends the address it saw to `X-Forwarded-For`, so the real client IP is
33
33
  * the Nth entry from the right. Anything further left is client-supplied and
34
34
  * ignored — which is what stops a caller spoofing `X-Forwarded-For` to
35
- * rotate rate-limit keys. Defaults to `TRUSTED_PROXY_HOPS` (env) or `1`.
36
- * Set to `0` when the server is exposed directly (no proxy): `X-Forwarded-For`
37
- * is then ignored entirely in favour of `X-Real-IP`.
35
+ * rotate rate-limit keys.
36
+ *
37
+ * **Defaults to `0`: no proxy is trusted, and `X-Forwarded-For` and
38
+ * `X-Real-IP` are both ignored in favour of the socket address.** A server
39
+ * cannot tell from a request whether a proxy put that header there or the
40
+ * caller did, so the number of hops is a deployment fact that has to be
41
+ * declared. It previously defaulted to `1`, and nothing in the repo ever
42
+ * set it — so on any directly-exposed server (`rebase dev`, a self-hosted
43
+ * `rebase-server`, a passthrough load balancer) one header gave every
44
+ * IP-keyed limiter an effective limit of one request per attacker-chosen
45
+ * value.
46
+ *
47
+ * Set it — via this option or `TRUSTED_PROXY_HOPS` — to the number of
48
+ * proxies you actually run. Behind one ingress or load balancer that is
49
+ * `1`. Leaving it unset behind a proxy makes every client share the
50
+ * proxy's address as one bucket, which the boot-time warning below tells
51
+ * you about the first time it happens.
38
52
  */
39
53
  trustedProxyHops?: number;
40
54
  }
@@ -54,6 +68,26 @@ export declare const defaultAuthLimiter: MiddlewareHandler<HonoEnv>;
54
68
  * 50 requests per 15 minutes per IP.
55
69
  */
56
70
  export declare const strictAuthLimiter: MiddlewareHandler<HonoEnv>;
71
+ /**
72
+ * Limiter for `POST /auth/send-verification`, keyed by the authenticated user.
73
+ *
74
+ * That route had no limiter at all while every one of its email-sending
75
+ * siblings had one, and being authenticated is not the protection it looks
76
+ * like: registration does not verify the address it is given, so an attacker
77
+ * registers a victim's address, signs in to the account they just made, and
78
+ * loops the route. Each call mints a token and mails the victim.
79
+ *
80
+ * An IP limiter cannot express what is wanted here — the recipient is the
81
+ * quantity being protected, not the caller — so this one keys on the uid, which
82
+ * on this route is one-to-one with the recipient address (emails are unique per
83
+ * account). `strictAuthLimiter` still runs in front of it to bound the caller by
84
+ * IP; this bounds what any single address can be sent.
85
+ *
86
+ * Five per 15 minutes is generous for "I didn't get the email, resend it" and
87
+ * useless as a mail bomb. Unauthenticated requests fall back to the IP bucket so
88
+ * the limiter is never a no-op if it is ever mounted before the auth middleware.
89
+ */
90
+ export declare const verificationEmailLimiter: MiddlewareHandler<HonoEnv>;
57
91
  /**
58
92
  * Key generator for API-key-based rate limiting.
59
93
  *
@@ -66,6 +100,14 @@ export declare function apiKeyKeyGenerator(c: Parameters<MiddlewareHandler<HonoE
66
100
  export interface DataRateLimitConfig {
67
101
  /** Turn the whole thing off — for a deployment whose proxy already does it. */
68
102
  enabled?: boolean;
103
+ /**
104
+ * Trusted reverse-proxy hops, for the IP bucket. Same meaning and same
105
+ * fail-safe default of 0 as {@link RateLimiterOptions.trustedProxyHops} —
106
+ * threaded explicitly because this limiter builds its own key generator,
107
+ * and before this existed the option was accepted by the surrounding
108
+ * limiter and then ignored for the one bucket that keys on an address.
109
+ */
110
+ trustedProxyHops?: number;
69
111
  windowMs?: number;
70
112
  /** Fallback for an API key with no `rate_limit` of its own. Default 1000. */
71
113
  apiKey?: number;
@@ -73,13 +115,28 @@ export interface DataRateLimitConfig {
73
115
  user?: number;
74
116
  /**
75
117
  * Per IP, for requests with no principal at all. Default 300.
76
- * `null` disables the anonymous bucket entirely — used for routers whose
77
- * anonymous traffic must not be throttled (public webhook functions).
118
+ * `null` disables the anonymous bucket entirely.
78
119
  */
79
120
  anonymous?: number | null;
121
+ /**
122
+ * Per IP, for anonymous requests to `/api/functions/*`. Default 3000 —
123
+ * deliberately far looser than {@link anonymous}, because the functions
124
+ * router is public by default for webhook receivers (Stripe, GitHub) whose
125
+ * bursts arrive from a handful of provider IPs and would trip the data
126
+ * API's ceiling.
127
+ *
128
+ * The looseness bounds the *value*, not the existence of the limit: this
129
+ * router is the one that invites anonymous callers, so it is the last one
130
+ * that should have no ceiling at all. `null` disables it — an explicit
131
+ * choice, which is what the hardcoded `anonymous: null` here used to be
132
+ * without any way to say otherwise.
133
+ */
134
+ anonymousFunctions?: number | null;
80
135
  /** Share counts across replicas. Defaults to this process's memory. */
81
136
  store?: RateLimitStore;
82
137
  }
138
+ /** @see DataRateLimitConfig.anonymousFunctions */
139
+ export declare const DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT = 3000;
83
140
  /**
84
141
  * Rate limiting for the data API.
85
142
  *
@@ -55,3 +55,34 @@ export declare function isRegistrationOpen(policy: RegistrationPolicy): boolean;
55
55
  * on whether the table is empty, so now go and look".
56
56
  */
57
57
  export declare function isSteadyStateRegistrationOpen(policy: Omit<RegistrationPolicy, "needsSetup">): boolean;
58
+ /**
59
+ * Whether `POST /auth/anonymous` may mint a user.
60
+ *
61
+ * Anonymous sign-in *is* registration: it inserts a `users` row and assigns
62
+ * `defaultRole` exactly as `POST /auth/register` does. It simply never asked.
63
+ * Both anonymous routes were mounted unconditionally and consulted none of the
64
+ * gates above, so a backend with `allowRegistration: false` **and**
65
+ * `disableSelfRegistration: true` — the documented hard kill switch — still
66
+ * handed a permanent account to any caller in two unauthenticated requests:
67
+ * `POST /auth/anonymous` for the row and the session, then
68
+ * `POST /auth/anonymous/link` to put an email and password on it.
69
+ *
70
+ * Two terms, because the two questions are genuinely different:
71
+ *
72
+ * - `allowAnonymous` is the feature switch, and it is **opt-in**. There was no
73
+ * key at all before, so a deployment could not turn anonymous auth off even
74
+ * after finding it on; defaulting it to `true` here would keep that hole
75
+ * open for everyone who never learns the key exists.
76
+ * - `disableSelfRegistration` still wins over it. Its docstring calls it a
77
+ * hard kill switch that blocks "self-registration outright", and an account
78
+ * created without credentials is still an account created by the public.
79
+ * An operator who sets both gets the kill switch they asked for.
80
+ *
81
+ * `allowRegistration` deliberately does **not** gate this. A public read-mostly
82
+ * app that wants anonymous sessions but no sign-up forms is a real deployment,
83
+ * and `allowAnonymous: true` says so precisely.
84
+ */
85
+ export declare function isAnonymousAuthOpen(policy: {
86
+ allowAnonymous?: boolean;
87
+ disableSelfRegistration?: boolean;
88
+ }): boolean;
@@ -12,6 +12,31 @@
12
12
  * @module
13
13
  */
14
14
  import type { DataDriver } from "@rebasepro/types";
15
+ /**
16
+ * The identity the trusted server plane runs as: `rebase.dataAsAdmin`, and any
17
+ * caller presenting the service key.
18
+ *
19
+ * Naming it makes the privilege legible, because it is narrower than it reads.
20
+ * This is **not** an RLS bypass — a driver scoped with it is an
21
+ * `AuthenticatedPostgresBackendDriver`, so every statement runs in a
22
+ * transaction that has done `SET LOCAL ROLE rebase_user` with
23
+ * `app.uid = 'service'`, and policies are evaluated. Two consequences worth
24
+ * knowing before you write a policy:
25
+ *
26
+ * - It passes the default policies through their `rolesOverlap(['admin'])`
27
+ * arm — the same arm an application user holding the `admin` role passes.
28
+ * - `policy.serverContext()` compiles to `auth.uid() IS NULL` and is therefore
29
+ * **false** for it. A collection with `disableDefaultPolicies: true` whose
30
+ * only rule is `serverContext()` denies these writes (42501) and returns
31
+ * zero rows for these reads.
32
+ *
33
+ * The true bypass is `rebase.sql()`, which runs on the owner connection and
34
+ * never goes near `withAuth`.
35
+ */
36
+ export declare const SERVICE_IDENTITY: {
37
+ uid: string;
38
+ roles: string[];
39
+ };
15
40
  /**
16
41
  * Scope a DataDriver via `withAuth()` for RLS.
17
42
  *
@@ -18,8 +18,23 @@ export interface AuthModuleConfig {
18
18
  defaultRole?: string;
19
19
  /** Optional array of OAuth providers */
20
20
  oauthProviders?: OAuthProvider<unknown>[];
21
+ /**
22
+ * Redirect URIs the OAuth routes will accept, for every provider.
23
+ *
24
+ * Left unset, the only check is the provider's own registered-URI match,
25
+ * which authorises every URI registered on that OAuth client. Compared on
26
+ * origin plus path; query and fragment are ignored, as is a trailing slash.
27
+ */
28
+ allowedRedirectUris?: string[];
21
29
  /** When true, blocks all self-registration regardless of `allowRegistration`. */
22
30
  disableSelfRegistration?: boolean;
31
+ /**
32
+ * Opt-in: allow `POST /auth/anonymous` to mint a user without credentials.
33
+ *
34
+ * Off by default. See {@link isAnonymousAuthOpen} for why this is opt-in
35
+ * rather than opt-out, and why `disableSelfRegistration` overrides it.
36
+ */
37
+ allowAnonymous?: boolean;
23
38
  /**
24
39
  * Auth hooks for customizing password hashing, credential
25
40
  * verification, lifecycle hooks, etc.
@@ -1,4 +1,5 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a Slack OAuth Provider integration (OAuth 2.0 / "Sign in with Slack").
4
5
  * Uses the OpenID Connect flow with the "openid,email,profile" scopes.
@@ -6,7 +7,4 @@ import type { OAuthProvider } from "./interfaces";
6
7
  export declare function createSlackProvider(config: {
7
8
  clientId: string;
8
9
  clientSecret: string;
9
- }): OAuthProvider<{
10
- code: string;
11
- redirectUri: string;
12
- }>;
10
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -1,4 +1,5 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a Spotify OAuth Provider integration.
4
5
  * Uses the authorization code flow with the "user-read-email" scope.
@@ -6,7 +7,4 @@ import type { OAuthProvider } from "./interfaces";
6
7
  export declare function createSpotifyProvider(config: {
7
8
  clientId: string;
8
9
  clientSecret: string;
9
- }): OAuthProvider<{
10
- code: string;
11
- redirectUri: string;
12
- }>;
10
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -0,0 +1,38 @@
1
+ import type { AuthRepository } from "./interfaces";
2
+ import type { AccessTokenPayload } from "./jwt";
3
+ /**
4
+ * Has this access token been revoked?
5
+ *
6
+ * `logout`, `change-password`, `reset-password` and `DELETE /auth/sessions` all
7
+ * stamp a `tokensValidAfter` watermark on the user. Every one of them also
8
+ * deletes refresh-token rows, which is what made the gap easy to miss: the
9
+ * session really is gone, and the *refresh* path really does check the
10
+ * watermark — so signing out looked like it worked.
11
+ *
12
+ * The access token was untouched. It is a bearer credential that nothing
13
+ * consulted a database about, so it stayed valid for its full lifetime after
14
+ * every one of those actions. "Sign out everywhere" left the stolen token
15
+ * working; so did changing your password because you thought it had leaked.
16
+ *
17
+ * The watermark had exactly one read in the repository, on refresh. This adds
18
+ * the other one.
19
+ *
20
+ * ## Cost
21
+ *
22
+ * One indexed lookup per request, on the paths that already make one. The
23
+ * adapter re-reads roles for every authenticated request; the admin routes now
24
+ * do too. Both call this with the row they were already fetching in mind — a
25
+ * repository that wants to serve both from one query is free to cache
26
+ * internally, but nothing here assumes it.
27
+ *
28
+ * ## Failure
29
+ *
30
+ * Fails **open**, deliberately, and this is the one place in the auth stack
31
+ * where that is right. The watermark is an extra revocation signal layered over
32
+ * a token that has already been verified — signature, expiry and purpose all
33
+ * checked. Refusing every request when the database is unreachable would turn a
34
+ * transient outage into a total sign-out of an entire deployment, and the
35
+ * attacker this protects against needs to have already stolen a live token. The
36
+ * failure is logged at warn so it is visible rather than silent.
37
+ */
38
+ export declare function isAccessTokenRevoked(authRepo: Pick<AuthRepository, "getTokensValidAfter">, payload: Pick<AccessTokenPayload, "uid" | "iat">): Promise<boolean>;
@@ -1,4 +1,5 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a Twitter/X OAuth 2.0 Provider integration.
4
5
  *
@@ -11,8 +12,4 @@ import type { OAuthProvider } from "./interfaces";
11
12
  export declare function createTwitterProvider(config: {
12
13
  clientId: string;
13
14
  clientSecret: string;
14
- }): OAuthProvider<{
15
- code: string;
16
- redirectUri: string;
17
- codeVerifier: string;
18
- }>;
15
+ }): OAuthProvider<OAuthCodeFlowPayload>;