@rebasepro/server 0.13.1-canary.gf57a27e → 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.
- package/dist/{admin_block-CLeUMG8G.js → admin_block-H86dCPsj.js} +60 -2
- package/dist/admin_block-H86dCPsj.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +74 -6
- package/dist/api/errors.d.ts +12 -0
- package/dist/api/rest/api-generator.d.ts +26 -1
- package/dist/api/rest/idempotency.d.ts +25 -4
- package/dist/api/rest/query-parser.d.ts +15 -1
- package/dist/api/rest/write-validation.d.ts +40 -4
- package/dist/auth/adapter-middleware.d.ts +3 -1
- package/dist/auth/admin-roles.d.ts +24 -0
- package/dist/auth/apple-oauth.d.ts +16 -11
- package/dist/auth/bitbucket-oauth.d.ts +2 -4
- package/dist/auth/builtin-auth-adapter.d.ts +11 -0
- package/dist/auth/discord-oauth.d.ts +5 -6
- package/dist/auth/facebook-oauth.d.ts +2 -4
- package/dist/auth/github-oauth.d.ts +2 -4
- package/dist/auth/gitlab-oauth.d.ts +6 -4
- package/dist/auth/google-oauth.d.ts +1 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/interfaces.d.ts +81 -1
- package/dist/auth/jwt.d.ts +40 -0
- package/dist/auth/linkedin-oauth.d.ts +2 -4
- package/dist/auth/mfa-crypto.d.ts +54 -3
- package/dist/auth/mfa-gate.d.ts +42 -0
- package/dist/auth/mfa-routes.d.ts +26 -1
- package/dist/auth/mfa.d.ts +16 -0
- package/dist/auth/microsoft-oauth.d.ts +19 -4
- package/dist/auth/middleware.d.ts +26 -0
- package/dist/auth/oauth-code-flow.d.ts +66 -0
- package/dist/auth/oauth-signin-policy.d.ts +61 -0
- package/dist/auth/oidc-id-token.d.ts +61 -0
- package/dist/auth/rate-limiter.d.ts +62 -5
- package/dist/auth/registration-policy.d.ts +31 -0
- package/dist/auth/rls-scope.d.ts +25 -0
- package/dist/auth/routes.d.ts +15 -0
- package/dist/auth/slack-oauth.d.ts +2 -4
- package/dist/auth/spotify-oauth.d.ts +2 -4
- package/dist/auth/token-revocation.d.ts +38 -0
- package/dist/auth/twitter-oauth.d.ts +2 -5
- package/dist/{auth-BANhRhN9.js → auth-CYoPVf-E.js} +1814 -354
- package/dist/auth-CYoPVf-E.js.map +1 -0
- package/dist/{backup-BJiY_mM-.js → backup-C6ljYVTp.js} +2 -2
- package/dist/{backup-BJiY_mM-.js.map → backup-C6ljYVTp.js.map} +1 -1
- package/dist/{contract-routes-FSt0Nyfk.js → contract-routes-Bet-eCNJ.js} +3 -3
- package/dist/{contract-routes-FSt0Nyfk.js.map → contract-routes-Bet-eCNJ.js.map} +1 -1
- package/dist/cron/cron-loader.d.ts +26 -1
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +5 -0
- package/dist/cron/define-cron.d.ts +14 -2
- package/dist/cron/index.d.ts +2 -2
- package/dist/{cron-loader-B1S2MCSl.js → cron-loader-YhhQeVBM.js} +43 -8
- package/dist/cron-loader-YhhQeVBM.js.map +1 -0
- package/dist/{cron-routes-D5a9v1HY.js → cron-routes-maM_RlUu.js} +10 -4
- package/dist/cron-routes-maM_RlUu.js.map +1 -0
- package/dist/{cron-scheduler-CsQtYLMq.js → cron-scheduler-DIpYBmZP.js} +13 -7
- package/dist/cron-scheduler-DIpYBmZP.js.map +1 -0
- package/dist/{cron-store-ZbjfuvMb.js → cron-store-Dvr4Y1sZ.js} +5 -5
- package/dist/{cron-store-ZbjfuvMb.js.map → cron-store-Dvr4Y1sZ.js.map} +1 -1
- package/dist/{ddl-bootstrap-Bp3mr6Td.js → ddl-bootstrap-BhXbTnBl.js} +2 -2
- package/dist/{ddl-bootstrap-Bp3mr6Td.js.map → ddl-bootstrap-BhXbTnBl.js.map} +1 -1
- package/dist/email/html.d.ts +54 -0
- package/dist/email/index.d.ts +3 -0
- package/dist/email/link-base.d.ts +39 -0
- package/dist/email/smtp-email-service.d.ts +7 -1
- package/dist/email/templates.d.ts +9 -1
- package/dist/email/types.d.ts +11 -1
- package/dist/env.d.ts +5 -0
- package/dist/{errors-BpudWAVU.js → errors-EBYiaJ2E.js} +14 -2
- package/dist/errors-EBYiaJ2E.js.map +1 -0
- package/dist/function-loader-DDS1v7YX.js +139 -0
- package/dist/function-loader-DDS1v7YX.js.map +1 -0
- package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
- package/dist/function-routes-Btcez1T-.js.map +1 -0
- package/dist/functions/define-function.d.ts +24 -5
- package/dist/functions/function-loader.d.ts +23 -0
- package/dist/functions/function-routes.d.ts +7 -1
- package/dist/functions/request-timeout.d.ts +34 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.es.js +1437 -694
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +10 -1
- package/dist/init/process-safety.d.ts +25 -0
- package/dist/init.d.ts +38 -0
- package/dist/{jwt-CzeviDcB.js → jwt-_IFqfTOg.js} +57 -9
- package/dist/jwt-_IFqfTOg.js.map +1 -0
- package/dist/logger-DfvF_8r-.js +190 -0
- package/dist/logger-DfvF_8r-.js.map +1 -0
- package/dist/{openapi-generator-VB0MUH8h.js → openapi-generator-DPKtUC9X.js} +87 -31
- package/dist/openapi-generator-DPKtUC9X.js.map +1 -0
- package/dist/request-timeout-RivJsME0.js +65 -0
- package/dist/request-timeout-RivJsME0.js.map +1 -0
- package/dist/schema-editor-routes-CRcS3ArS.js +437 -0
- package/dist/schema-editor-routes-CRcS3ArS.js.map +1 -0
- package/dist/serve-spa.d.ts +5 -4
- package/dist/services/outbound-url-guard.d.ts +53 -0
- package/dist/services/routed-realtime-service.d.ts +10 -2
- package/dist/services/webhook-service.d.ts +76 -1
- package/dist/singleton.d.ts +25 -8
- package/dist/{src-DVb5a0bp.js → src-C7rkDGxA.js} +536 -61
- package/dist/src-C7rkDGxA.js.map +1 -0
- package/dist/{src-Ca6NhxKs.js → src-Cz9nMgUR.js} +16 -3
- package/dist/src-Cz9nMgUR.js.map +1 -0
- package/dist/storage/LocalStorageController.d.ts +12 -1
- package/dist/storage/image-transform.d.ts +57 -0
- package/dist/storage/keys.d.ts +34 -1
- package/dist/storage/routes.d.ts +11 -0
- package/dist/storage/tus-handler.d.ts +22 -2
- package/dist/utils/logger.d.ts +18 -0
- package/package.json +6 -5
- package/dist/admin_block-CLeUMG8G.js.map +0 -1
- package/dist/auth-BANhRhN9.js.map +0 -1
- package/dist/cron-loader-B1S2MCSl.js.map +0 -1
- package/dist/cron-routes-D5a9v1HY.js.map +0 -1
- package/dist/cron-scheduler-CsQtYLMq.js.map +0 -1
- package/dist/errors-BpudWAVU.js.map +0 -1
- package/dist/function-loader-B_1fYfUY.js +0 -86
- package/dist/function-loader-B_1fYfUY.js.map +0 -1
- package/dist/function-routes-C0cLIy3N.js.map +0 -1
- package/dist/jwt-CzeviDcB.js.map +0 -1
- package/dist/logger-BYU66ENZ.js +0 -94
- package/dist/logger-BYU66ENZ.js.map +0 -1
- package/dist/openapi-generator-VB0MUH8h.js.map +0 -1
- package/dist/schema-editor-routes-BqjzvvWU.js +0 -248
- package/dist/schema-editor-routes-BqjzvvWU.js.map +0 -1
- package/dist/src-Ca6NhxKs.js.map +0 -1
- package/dist/src-DVb5a0bp.js.map +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refusal to send a request to a destination the guard does not allow.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from a network error on purpose: a blocked destination is
|
|
5
|
+
* *terminal* — retrying it re-runs the same refusal — while an `ENOTFOUND`
|
|
6
|
+
* or a refused connection is worth another attempt.
|
|
7
|
+
*/
|
|
8
|
+
export declare class BlockedUrlError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
export interface OutboundUrlGuardOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Send to destinations that resolve to loopback, link-local, private or
|
|
14
|
+
* otherwise non-public addresses. Off by default, and turning it on
|
|
15
|
+
* re-opens SSRF: any caller who can choose the URL can then reach the
|
|
16
|
+
* pod's own metadata endpoint, the cluster API server and the database.
|
|
17
|
+
* Only for a genuinely local receiver (a sidecar, a dev tunnel).
|
|
18
|
+
*/
|
|
19
|
+
allowPrivateNetworks?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Hostname resolver. Defaults to `dns.lookup(host, { all: true })`.
|
|
22
|
+
* Injected by tests so a unit suite never touches a resolver.
|
|
23
|
+
*/
|
|
24
|
+
lookup?: (hostname: string) => Promise<string[]>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Why this address must not be a webhook destination, or `null` if it is an
|
|
28
|
+
* ordinary public address. Exported for tests and for anyone else adding an
|
|
29
|
+
* outbound call whose destination comes from data.
|
|
30
|
+
*/
|
|
31
|
+
export declare function blockedAddressReason(address: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve and validate a destination before anything connects to it.
|
|
34
|
+
*
|
|
35
|
+
* Three checks, in order, because each is cheaper than the next: the scheme,
|
|
36
|
+
* the hostname, then every address the hostname resolves to. *Every* address —
|
|
37
|
+
* a name that answers with one public and one private record is a rebinding
|
|
38
|
+
* attempt, and taking the first record would let it through.
|
|
39
|
+
*
|
|
40
|
+
* Known limit: this is validate-then-fetch. Between the lookup here and the
|
|
41
|
+
* connection the runtime makes there is a second resolution this code does not
|
|
42
|
+
* see, so a TTL-0 name that flips between answers can still land on a blocked
|
|
43
|
+
* address. Closing that needs the socket pinned to the address checked here,
|
|
44
|
+
* which Node's global `fetch` does not expose without an undici dispatcher.
|
|
45
|
+
* The redirect policy in {@link WebhookDispatcher} closes the much wider
|
|
46
|
+
* version of the same hole — a receiver that simply *points* at the internal
|
|
47
|
+
* address.
|
|
48
|
+
*
|
|
49
|
+
* @throws BlockedUrlError when the destination is not allowed. DNS failures
|
|
50
|
+
* are rethrown as they are, so callers can tell "never send this" from
|
|
51
|
+
* "could not send this right now".
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertAllowedOutboundUrl(rawUrl: string, options?: OutboundUrlGuardOptions): Promise<URL>;
|
|
@@ -15,6 +15,13 @@ interface ClientMessage {
|
|
|
15
15
|
export interface WsRealtimeService extends RealtimeProvider {
|
|
16
16
|
addClient(clientId: string, ws: unknown): void;
|
|
17
17
|
handleClientMessage(clientId: string, message: ClientMessage, authContext?: unknown): Promise<void> | void;
|
|
18
|
+
/**
|
|
19
|
+
* Whether this provider actually implements channels, presence and
|
|
20
|
+
* broadcast. Declared by the provider rather than inferred, because the
|
|
21
|
+
* alternative — assuming the default one does — is how these frames came to
|
|
22
|
+
* be handed to an engine whose message switch drops them without a word.
|
|
23
|
+
*/
|
|
24
|
+
supportsChannels?: boolean;
|
|
18
25
|
}
|
|
19
26
|
export interface RoutedRealtimeOptions {
|
|
20
27
|
/** Per-engine realtime providers, keyed by data-source key. */
|
|
@@ -34,8 +41,9 @@ export interface RoutedRealtimeOptions {
|
|
|
34
41
|
* - `subscribe_collection` / `subscribe_entity` → the provider for the
|
|
35
42
|
* collection's data source (by `payload.path`).
|
|
36
43
|
* - `unsubscribe` → forwarded to all providers (a no-op on non-owners).
|
|
37
|
-
* - channel / presence / broadcast → the
|
|
38
|
-
* pub/sub, not bound to an engine
|
|
44
|
+
* - channel / presence / broadcast → the first provider that declares
|
|
45
|
+
* `supportsChannels` (these are global pub/sub, not bound to an engine — but
|
|
46
|
+
* they are not implemented by every engine either).
|
|
39
47
|
* - `addClient` and lifecycle (`onServerReady`/`destroy`/`stopListening`) →
|
|
40
48
|
* all providers (each registers its own ws close handler for cleanup).
|
|
41
49
|
*/
|
|
@@ -16,14 +16,89 @@ export interface WebhookDeliveryResult {
|
|
|
16
16
|
success: boolean;
|
|
17
17
|
attemptNumber: number;
|
|
18
18
|
}
|
|
19
|
+
export interface WebhookDispatcherOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Deliver to destinations that resolve to loopback, link-local or private
|
|
22
|
+
* addresses. Off by default. Turning it on re-opens SSRF: anyone who can
|
|
23
|
+
* choose a webhook URL can then reach the pod's metadata endpoint, the
|
|
24
|
+
* cluster API server and the database — and read the first 1000 bytes of
|
|
25
|
+
* the answer back out of `responseBody`. Only for a receiver you run
|
|
26
|
+
* yourself on the same host or network.
|
|
27
|
+
*/
|
|
28
|
+
allowPrivateNetworks?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Deadline for one attempt, covering the response body as well as the
|
|
31
|
+
* headers. Defaults to 10s.
|
|
32
|
+
*/
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Called with the final result of every delivery, including the ones
|
|
36
|
+
* started by {@link WebhookDispatcher.enqueueEntityChange} — which return
|
|
37
|
+
* nothing to their caller, so this is the only place their failures
|
|
38
|
+
* surface.
|
|
39
|
+
*/
|
|
40
|
+
onDelivery?: (result: WebhookDeliveryResult) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Hostname resolver used by the destination guard. Defaults to
|
|
43
|
+
* `dns.lookup`; injected by tests so a unit suite never asks a resolver.
|
|
44
|
+
*/
|
|
45
|
+
lookup?: (hostname: string) => Promise<string[]>;
|
|
46
|
+
}
|
|
19
47
|
export declare class WebhookDispatcher {
|
|
20
48
|
private webhooks;
|
|
21
49
|
private maxRetries;
|
|
22
50
|
private retryDelays;
|
|
51
|
+
private readonly options;
|
|
52
|
+
private readonly timeoutMs;
|
|
53
|
+
private queue;
|
|
54
|
+
private draining;
|
|
55
|
+
constructor(options?: WebhookDispatcherOptions);
|
|
23
56
|
/** Register webhooks to watch */
|
|
24
57
|
setWebhooks(webhooks: WebhookConfig[]): void;
|
|
25
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Called when a entity changes — checks if any webhook matches, and awaits
|
|
60
|
+
* every delivery, retries included.
|
|
61
|
+
*
|
|
62
|
+
* **Do not await this inside a collection callback.** `afterSave` and
|
|
63
|
+
* `afterDelete` run inside the write's Postgres transaction, so awaiting a
|
|
64
|
+
* delivery here holds a pooled connection and the row's locks for as long
|
|
65
|
+
* as the receiver takes to answer — up to ~36s across three attempts, per
|
|
66
|
+
* matching webhook. Use {@link enqueueEntityChange} there; this method is
|
|
67
|
+
* for a custom function or a job that wants the results.
|
|
68
|
+
*/
|
|
26
69
|
onEntityChange(table: string, event: "INSERT" | "UPDATE" | "DELETE", id: string, entity: Record<string, unknown> | null, previousEntity?: Record<string, unknown> | null): Promise<WebhookDeliveryResult[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Queue the same deliveries and return immediately.
|
|
72
|
+
*
|
|
73
|
+
* This is the form a collection callback wants. `afterSave` is awaited
|
|
74
|
+
* *inside* the transaction that wrote the row, so anything it awaits is
|
|
75
|
+
* transaction time; queueing hands the HTTP to a drain loop that runs
|
|
76
|
+
* after the callback returns, and the transaction commits without waiting
|
|
77
|
+
* for a third party. It also means a receiver that is down can no longer
|
|
78
|
+
* fail the customer's write.
|
|
79
|
+
*
|
|
80
|
+
* The cost, stated plainly: the queue is in-process and in-memory. A crash
|
|
81
|
+
* or a deploy between the enqueue and the delivery drops the event, and a
|
|
82
|
+
* receiver may see the notification a few milliseconds before the row it
|
|
83
|
+
* describes is committed. Use {@link flush} on shutdown, and
|
|
84
|
+
* `onDelivery` to record failures.
|
|
85
|
+
*/
|
|
86
|
+
enqueueEntityChange(table: string, event: "INSERT" | "UPDATE" | "DELETE", id: string, entity: Record<string, unknown> | null, previousEntity?: Record<string, unknown> | null): void;
|
|
87
|
+
/**
|
|
88
|
+
* Wait for every queued delivery to finish. For graceful shutdown, and for
|
|
89
|
+
* tests that need to observe what {@link enqueueEntityChange} sent.
|
|
90
|
+
*/
|
|
91
|
+
flush(): Promise<void>;
|
|
92
|
+
private startDraining;
|
|
93
|
+
private drainQueue;
|
|
94
|
+
/** The payload each matching webhook gets for this change. */
|
|
95
|
+
private buildDeliveries;
|
|
27
96
|
private deliverWithRetry;
|
|
28
97
|
private deliver;
|
|
98
|
+
/**
|
|
99
|
+
* Read at most {@link MAX_RESPONSE_BYTES} of the response and cancel the
|
|
100
|
+
* rest, so a receiver cannot buffer a 10 GB body into the pod before the
|
|
101
|
+
* caller truncates it to 1000 characters.
|
|
102
|
+
*/
|
|
103
|
+
private readCappedBody;
|
|
29
104
|
}
|
package/dist/singleton.d.ts
CHANGED
|
@@ -22,14 +22,30 @@ export declare function _resetRebaseMock(): void;
|
|
|
22
22
|
* **Admin data plane** (`rebase.dataAsAdmin`):
|
|
23
23
|
* Backed by the native DataDriver — calls go directly to the database without
|
|
24
24
|
* JSON serialization, HTTP dispatch, or middleware overhead. The driver is
|
|
25
|
-
* scoped as `{ uid: "service", roles: ["admin"] }
|
|
26
|
-
*
|
|
25
|
+
* scoped once as `{ uid: "service", roles: ["admin"] }` (`SERVICE_IDENTITY`),
|
|
26
|
+
* which makes it **admin-scoped, not RLS-bypassing**: every read and write runs
|
|
27
|
+
* in a transaction that has done `SET LOCAL ROLE rebase_user` with
|
|
28
|
+
* `app.uid = 'service'`, and policies are evaluated against that. No
|
|
29
|
+
* `REBASE_SERVICE_KEY` is required.
|
|
27
30
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* It clears the default policies through their `rolesOverlap(['admin'])` arm —
|
|
32
|
+
* which is why the difference rarely shows. It shows when you write your own:
|
|
33
|
+
*
|
|
34
|
+
* - `policy.serverContext()` compiles to `auth.uid() IS NULL` and is therefore
|
|
35
|
+
* **false** for this accessor. A collection with `disableDefaultPolicies:
|
|
36
|
+
* true` whose rule is `serverContext()` denies these writes (`42501`) and
|
|
37
|
+
* returns zero rows — HTTP 200, empty — for these reads.
|
|
38
|
+
* - Its reach equals an `admin`-roled application user's reach. It is not a
|
|
39
|
+
* private door.
|
|
40
|
+
*
|
|
41
|
+
* `rebase.sql()` *is* an unconditional bypass — it runs on the owner connection
|
|
42
|
+
* and never goes through `withAuth`. Of the two accessors on this object, the
|
|
43
|
+
* quieter one is the more privileged.
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ `rebase.dataAsAdmin` is for trusted background work (cron jobs,
|
|
46
|
+
* migrations, service tasks) — **not** for serving user-facing data. Inside a
|
|
47
|
+
* request handler, run user-scoped queries through the request-scoped driver
|
|
48
|
+
* (`c.var.driver`), which carries the caller's identity.
|
|
33
49
|
*
|
|
34
50
|
* `rebase.data` is **gone from the type**: `RebaseServerClient` omits it, so the
|
|
35
51
|
* admin-scoped accessor has exactly one name and the privilege is visible at the
|
|
@@ -45,7 +61,8 @@ export declare function _resetRebaseMock(): void;
|
|
|
45
61
|
* ```typescript
|
|
46
62
|
* import { rebase } from "@rebasepro/server";
|
|
47
63
|
*
|
|
48
|
-
* // In a cron job, hook, or trusted service file (admin scope,
|
|
64
|
+
* // In a cron job, hook, or trusted service file (admin scope, RLS evaluated
|
|
65
|
+
* // as `{ uid: "service", roles: ["admin"] }`):
|
|
49
66
|
* await rebase.email.send({ to: "admin@co.com", subject: "Alert", html: "<p>Hi</p>" });
|
|
50
67
|
* const jobs = await rebase.dataAsAdmin.jobs.find({ limit: 10 });
|
|
51
68
|
* ```
|