better-supabase 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -16
- package/dist/actor-DaJjqD5R.d.ts +43 -0
- package/dist/channel-FQW-CnGR.d.ts +70 -0
- package/dist/cli/bin.js +1 -1
- package/dist/cli/codemod-DqV7612m.js +388 -0
- package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
- package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
- package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
- package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
- package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
- package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
- package/dist/cli/{doctor-BOym3mbL.js → doctor-C_A-KEAz.js} +533 -50
- package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
- package/dist/cli/gen-CIdSQZad.js +2 -0
- package/dist/cli/{gen-Ccn_p90L.js → gen-CjElD1Vw.js} +321 -88
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{init-ClnxRYBF.js → init-DMa6M7hV.js} +2 -2
- package/dist/cli/{introspect-DxqfkVIv.js → introspect-BKlaIu_M.js} +2 -2
- package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
- package/dist/cli/read-sets-VQcPgxU9.js +436 -0
- package/dist/cli/{run-CP20LKk_.js → run-Bo_NLT5n.js} +16 -12
- package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
- package/dist/cli/sql-CVx-1O8G.js +373 -0
- package/dist/cli/{version-5mW_8sY1.js → version-BCtlgDQx.js} +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +3 -10
- package/dist/clone-BfgmLU90.js +21 -0
- package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -4
- package/dist/config/index.js +4 -4
- package/dist/{config-C1CYcm6o.d.ts → config-Bv7gvfzC.d.ts} +185 -11
- package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
- package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
- package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
- package/dist/edge/index.d.ts +16 -5
- package/dist/edge/index.js +10 -7
- package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-BiJZ4t49.d.ts} +1 -1
- package/dist/env/index.d.ts +2 -2
- package/dist/env/index.js +34 -1
- package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.js +65 -12
- package/dist/events-aIO7vsIk.d.ts +230 -0
- package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
- package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
- package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
- package/dist/hono/index.d.ts +17 -6
- package/dist/hono/index.js +40 -5
- package/dist/impersonation-BgSHAYLf.js +133 -0
- package/dist/{resolve-BAqQcwMW.d.ts → impersonation-BmePsuxz.d.ts} +36 -6
- package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
- package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
- package/dist/{index-Cf-D2jk1.d.ts → index-CWMDdEr4.d.ts} +2 -2
- package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
- package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
- package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
- package/dist/{index-BBdvo-5d.d.ts → index-Dp2VDwog.d.ts} +49 -6
- package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
- package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
- package/dist/index.d.ts +30 -13
- package/dist/index.js +10 -9
- package/dist/jobs/index.d.ts +147 -101
- package/dist/jobs/index.js +744 -237
- package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
- package/dist/kit-events-CTFDfSwb.js +60 -0
- package/dist/kit-transport-B82k1djG.d.ts +33 -0
- package/dist/kit-transport-DJlHQUBU.js +55 -0
- package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
- package/dist/list/index.d.ts +1 -1
- package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
- package/dist/mcp/index.d.ts +25 -8
- package/dist/mcp/index.js +23 -14
- package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-Cu96oJdw.d.ts} +2 -16
- package/dist/next/index.d.ts +31 -10
- package/dist/next/index.js +136 -27
- package/dist/notifications/index.d.ts +162 -0
- package/dist/notifications/index.js +264 -0
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +2 -2
- package/dist/orgs/index.d.ts +132 -0
- package/dist/orgs/index.js +245 -0
- package/dist/orpc/index.d.ts +28 -8
- package/dist/orpc/index.js +16 -3
- package/dist/otel/index.d.ts +21 -2
- package/dist/otel/index.js +50 -9
- package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
- package/dist/permdock-sql-DgOfeixi.js +51 -0
- package/dist/plugin-DwARMuQp.js +59 -0
- package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
- package/dist/plugins/actor/index.d.ts +5 -2
- package/dist/plugins/actor/index.js +22 -8
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +15 -10
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +14 -5
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/tenant/index.js +38 -20
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +4 -3
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +26 -6
- package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
- package/dist/postgres/index.d.ts +5 -5
- package/dist/postgres/index.js +20 -11
- package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
- package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
- package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -3
- package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
- package/dist/queue-BYUCjr59.d.ts +252 -0
- package/dist/react/index.d.ts +8 -7
- package/dist/react/index.js +4 -3
- package/dist/react/server.d.ts +14 -3
- package/dist/react/server.js +10 -2
- package/dist/react/session.d.ts +7 -1
- package/dist/react/session.js +9 -1
- package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
- package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
- package/dist/realtime/index.d.ts +3 -3
- package/dist/realtime/index.js +5 -4
- package/dist/{refresh-BwYkyNOQ.d.ts → refresh-CFPX0Qft.d.ts} +1 -1
- package/dist/{resource-BJcaIfxi.d.ts → resource-DNRFP7KR.d.ts} +5 -5
- package/dist/{resource-My_y-Syh.js → resource-mqfJW4YF.js} +22 -7
- package/dist/respond-BR1fJWOB.d.ts +377 -0
- package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
- package/dist/server/index.d.ts +15 -12
- package/dist/server/index.js +11 -17
- package/dist/{server-BA75B0mT.js → server-B1mZ7CgQ.js} +259 -37
- package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
- package/dist/signers-Cc6r98SW.d.ts +162 -0
- package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
- package/dist/sql/index.d.ts +283 -11
- package/dist/sql/index.js +8705 -775
- package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +1 -2
- package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
- package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
- package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
- package/dist/storage/index.d.ts +4 -4
- package/dist/storage/index.js +155 -26
- package/dist/support-CEPwlw2G.d.ts +89 -0
- package/dist/support-view-B8SlToQX.d.ts +17 -0
- package/dist/support-view-g4YLBeqX.js +18 -0
- package/dist/support-wHnK6zp-.js +293 -0
- package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
- package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
- package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
- package/dist/testing/index.d.ts +132 -10
- package/dist/testing/index.js +476 -8
- package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
- package/dist/user-change-CHFEUro1.js +49 -0
- package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
- package/dist/{version-Bmf7_FzM.js → version-DOYTYwHP.js} +1 -1
- package/dist/{mfa-byfjruR9.js → view-Bn6HNKUe.js} +97 -98
- package/dist/{view-B64o81wp.d.ts → view-CbgObAeb.d.ts} +22 -31
- package/dist/webhooks/index.d.ts +135 -4
- package/dist/webhooks/index.js +588 -2
- package/dist/wire-D2dT86vi.js +35 -0
- package/package.json +27 -19
- package/schemas/config-v1.json +131 -1
- package/schemas/doctor-report-v1.json +11 -0
- package/skills/better-supabase/SKILL.md +9 -6
- package/skills/better-supabase/references/plugins.md +6 -3
- package/skills/better-supabase/references/troubleshooting.md +1 -0
- package/skills/better-supabase-api/SKILL.md +9 -2
- package/skills/better-supabase-api/references/adapters.md +11 -8
- package/skills/better-supabase-auth/SKILL.md +18 -7
- package/skills/better-supabase-auth/references/permdock.md +78 -11
- package/dist/cli/gen-D5cLCULx.js +0 -2
- package/dist/cli/sql-D4VbqmXi.js +0 -199
- package/dist/events-B21UOhvP.d.ts +0 -84
- package/dist/executor-B5IoUxLE.d.ts +0 -195
- package/dist/invalidate-CgxmRamI.js +0 -24
- package/dist/permdock-sql-DImJULY-.d.ts +0 -17
- package/dist/plugin-BqR6wKMB.js +0 -23
- package/dist/respond-DgIbdrt8.d.ts +0 -194
- package/dist/view-BrJI2LKp.js +0 -45
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { n as dbError, o as isDbError, r as dbErrorOf } from "./errors-
|
|
2
|
-
import { i as ok, n as err, o as toDbError, t as AsyncResult } from "./result-
|
|
3
|
-
import { a as postgrestExecutor, n as StatsRecorder } from "./stats-
|
|
4
|
-
import { a as problemResponse } from "./problem-
|
|
1
|
+
import { n as dbError, o as isDbError, r as dbErrorOf } from "./errors-DtwXa6b8.js";
|
|
2
|
+
import { i as ok, n as err, o as toDbError, t as AsyncResult } from "./result-BKiliPY7.js";
|
|
3
|
+
import { a as postgrestExecutor, n as StatsRecorder } from "./stats-Bqob3WqT.js";
|
|
4
|
+
import { a as problemResponse } from "./problem-BW1KhzSG.js";
|
|
5
|
+
import { a as delegationOf, i as actorOf, r as userContext, t as actClaim } from "./impersonation-BgSHAYLf.js";
|
|
5
6
|
import { loadEnv } from "./env/index.js";
|
|
6
|
-
import {
|
|
7
|
+
import { a as checkAal, c as prefetchJwks, l as rememberVerified, o as authContext, t as isAnonymousUser, u as resolveAuth } from "./view-Bn6HNKUe.js";
|
|
7
8
|
import { parseCookieHeader, serializeCookieHeader } from "@supabase/ssr";
|
|
8
9
|
import { createClient } from "@supabase/supabase-js";
|
|
9
10
|
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
@@ -34,14 +35,35 @@ function pinnedUntil(cookies) {
|
|
|
34
35
|
const until = Number(value);
|
|
35
36
|
return value !== void 0 && Number.isFinite(until) ? until : 0;
|
|
36
37
|
}
|
|
37
|
-
/**
|
|
38
|
-
function
|
|
39
|
-
return
|
|
38
|
+
/** Options of the `bs-primary-until` cookie, for frameworks that set cookies themselves. */
|
|
39
|
+
function primaryCookieOptions(pinMs) {
|
|
40
|
+
return {
|
|
40
41
|
path: "/",
|
|
41
42
|
maxAge: Math.max(1, Math.ceil(pinMs / 1e3)),
|
|
42
43
|
httpOnly: true,
|
|
43
44
|
sameSite: "lax"
|
|
44
|
-
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** A `Set-Cookie` value keeping the next requests on the primary for `pinMs`. */
|
|
48
|
+
function primaryCookie(pinMs, now = Date.now()) {
|
|
49
|
+
return serializeCookieHeader(PRIMARY_COOKIE, String(now + pinMs), primaryCookieOptions(pinMs));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `response` with the `bs-primary-until` cookie when the request wrote, so the
|
|
53
|
+
* caller's next requests read their own writes. A response with immutable
|
|
54
|
+
* headers is copied.
|
|
55
|
+
*/
|
|
56
|
+
function withPrimaryPin(response, replica, pinMs) {
|
|
57
|
+
if (!replica?.wrote) return response;
|
|
58
|
+
const cookie = primaryCookie(pinMs);
|
|
59
|
+
try {
|
|
60
|
+
response.headers.append("set-cookie", cookie);
|
|
61
|
+
return response;
|
|
62
|
+
} catch {
|
|
63
|
+
const copy = new Response(response.body, response);
|
|
64
|
+
copy.headers.append("set-cookie", cookie);
|
|
65
|
+
return copy;
|
|
66
|
+
}
|
|
45
67
|
}
|
|
46
68
|
/**
|
|
47
69
|
* Reads go to `replica` until the context is pinned; writes, and every read
|
|
@@ -101,7 +123,7 @@ function deleteAccount(betterSupabase, service, userId, options = {}) {
|
|
|
101
123
|
const param = bucket.owner;
|
|
102
124
|
if (!param) continue;
|
|
103
125
|
options.signal?.throwIfAborted();
|
|
104
|
-
const storage = bucket.connect(client);
|
|
126
|
+
const storage = bucket.connect(client, { allTenants: true });
|
|
105
127
|
const listed = await storage.list({ [param]: userId }, options.signal ? { signal: options.signal } : {});
|
|
106
128
|
if (!listed.ok) return listed;
|
|
107
129
|
const paths = listed.data.map((object) => object.path).filter((path) => {
|
|
@@ -140,19 +162,39 @@ function deleteAccount(betterSupabase, service, userId, options = {}) {
|
|
|
140
162
|
});
|
|
141
163
|
}
|
|
142
164
|
//#endregion
|
|
165
|
+
//#region src/core/json.ts
|
|
166
|
+
/** A `JSON.stringify` replacer: `bigint` values become decimal strings, as the wire codec writes them. */
|
|
167
|
+
function jsonReplacer(_key, value) {
|
|
168
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
169
|
+
}
|
|
170
|
+
/** `Response.json` that writes `bigint` values as decimal strings instead of throwing. */
|
|
171
|
+
function jsonResponse(data, init = {}) {
|
|
172
|
+
const headers = new Headers(init.headers);
|
|
173
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
174
|
+
return new Response(JSON.stringify(data, jsonReplacer), {
|
|
175
|
+
...init,
|
|
176
|
+
headers
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
143
180
|
//#region src/server/respond.ts
|
|
144
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* The scopes a delegated user token lacks. Empty for the user's own session,
|
|
183
|
+
* which a support or impersonated session counts as: only an `oauth-client`
|
|
184
|
+
* actor is limited to what the user delegated.
|
|
185
|
+
*/
|
|
145
186
|
function missingScopes(auth, required) {
|
|
146
187
|
if (required.length === 0) return [];
|
|
147
188
|
const outcome = actorOf(auth.claims);
|
|
148
|
-
if (!outcome.ok ||
|
|
189
|
+
if (!outcome.ok || outcome.actor?.kind !== "oauth-client") return [];
|
|
149
190
|
const granted = new Set(delegationOf(auth.claims)?.scopes);
|
|
150
191
|
return required.filter((scope) => !granted.has(scope));
|
|
151
192
|
}
|
|
152
193
|
/** `undefined` when `auth` may pass; otherwise the 401/403 error to send. */
|
|
153
194
|
function guard(auth, allow = ["user"], aal = "aal1", scopes = []) {
|
|
154
195
|
if (auth.kind === "invalid") return auth.error;
|
|
155
|
-
|
|
196
|
+
const anonymous = auth.kind === "user" && isAnonymousUser(auth.claims);
|
|
197
|
+
if (anonymous ? allow.includes("anonymous") : allow.includes(auth.kind)) {
|
|
156
198
|
const denied = checkAal(auth, aal);
|
|
157
199
|
if (denied || auth.kind !== "user") return denied;
|
|
158
200
|
const missing = missingScopes(auth, scopes);
|
|
@@ -161,6 +203,7 @@ function guard(auth, allow = ["user"], aal = "aal1", scopes = []) {
|
|
|
161
203
|
scopes
|
|
162
204
|
}) : void 0;
|
|
163
205
|
}
|
|
206
|
+
if (anonymous) return dbError("forbidden", "Sign up to continue", { code: "ANONYMOUS_USER" });
|
|
164
207
|
return auth.kind === "anon" ? dbError("unauthorized", "Sign in to continue", { code: "MISSING_CREDENTIALS" }) : dbError("forbidden", `A ${auth.kind} caller is not allowed here`);
|
|
165
208
|
}
|
|
166
209
|
function isResult(value) {
|
|
@@ -202,7 +245,7 @@ async function respond(run, options = {}) {
|
|
|
202
245
|
if (!settled.ok) return problemResponse(settled.error, options);
|
|
203
246
|
if (settled.data instanceof Response) return settled.data;
|
|
204
247
|
if (settled.data === void 0) return new Response(null, { status: 204 });
|
|
205
|
-
return
|
|
248
|
+
return jsonResponse(settled.data, { status: options.status ?? 200 });
|
|
206
249
|
}
|
|
207
250
|
/** Prefetching the JWKS would add a network call to every test that builds a server. */
|
|
208
251
|
function defaultPrefetchJwks() {
|
|
@@ -212,7 +255,56 @@ function defaultExpose() {
|
|
|
212
255
|
return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
|
|
213
256
|
}
|
|
214
257
|
//#endregion
|
|
258
|
+
//#region src/auth/support-cookie.ts
|
|
259
|
+
/** The cookie that carries the support session id. */
|
|
260
|
+
const SUPPORT_COOKIE = "bs-support";
|
|
261
|
+
/** `Set-Cookie` for a session: HttpOnly, SameSite=Lax, gone when the session expires. */
|
|
262
|
+
function supportCookie(session, options = {}, now = Date.now()) {
|
|
263
|
+
const maxAge = Math.max(0, Math.ceil((session.expiresAt.epochMilliseconds - now) / 1e3));
|
|
264
|
+
return cookieLine(options, encodeURIComponent(session.id), maxAge);
|
|
265
|
+
}
|
|
266
|
+
/** `Set-Cookie` that removes the support cookie. */
|
|
267
|
+
function clearSupportCookie(options = {}) {
|
|
268
|
+
return cookieLine(options, "", 0);
|
|
269
|
+
}
|
|
270
|
+
function cookieLine(options, value, maxAge) {
|
|
271
|
+
return [
|
|
272
|
+
`${options.name ?? "bs-support"}=${value}`,
|
|
273
|
+
`Path=${options.path ?? "/"}`,
|
|
274
|
+
`Max-Age=${String(maxAge)}`,
|
|
275
|
+
"HttpOnly",
|
|
276
|
+
"SameSite=Lax",
|
|
277
|
+
...options.secure === false ? [] : ["Secure"]
|
|
278
|
+
].join("; ");
|
|
279
|
+
}
|
|
280
|
+
/** The support session id in a `Cookie` header, or `undefined`. */
|
|
281
|
+
function supportCookieValue(cookieHeader, name = SUPPORT_COOKIE) {
|
|
282
|
+
if (!cookieHeader) return void 0;
|
|
283
|
+
for (const part of cookieHeader.split(";")) {
|
|
284
|
+
const index = part.indexOf("=");
|
|
285
|
+
if (index === -1 || part.slice(0, index).trim() !== name) continue;
|
|
286
|
+
const value = decodeURIComponent(part.slice(index + 1).trim());
|
|
287
|
+
return value === "" ? void 0 : value;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/server/support-off.ts
|
|
292
|
+
const NOT_CONFIGURED = "Support sessions need createServer(betterSupabase, { support: supportSessions({ store }) })";
|
|
293
|
+
const notConfigured = () => AsyncResult.err(dbError("unexpected", NOT_CONFIGURED));
|
|
294
|
+
/** `bs.support` without `ServerOptions.support`: every call fails. */
|
|
295
|
+
const supportOff = {
|
|
296
|
+
start: notConfigured,
|
|
297
|
+
stop: notConfigured,
|
|
298
|
+
revoke: notConfigured,
|
|
299
|
+
current: () => Promise.resolve(void 0),
|
|
300
|
+
list: notConfigured,
|
|
301
|
+
sessionIdOf: () => void 0,
|
|
302
|
+
clearCookie: () => clearSupportCookie()
|
|
303
|
+
};
|
|
304
|
+
//#endregion
|
|
215
305
|
//#region src/server/server.ts
|
|
306
|
+
/** The header `current_tenant_id()` reads over the Data API. */
|
|
307
|
+
const TENANT_HEADER = "x-bs-tenant";
|
|
216
308
|
const STATELESS = {
|
|
217
309
|
persistSession: false,
|
|
218
310
|
autoRefreshToken: false,
|
|
@@ -252,15 +344,24 @@ function createServer(betterSupabase, options = {}) {
|
|
|
252
344
|
if (!key) throw new TypeError("admin() needs SUPABASE_SECRET_KEY; it is not set");
|
|
253
345
|
return key;
|
|
254
346
|
};
|
|
347
|
+
const customFetch = options.fetch ? { fetch: options.fetch } : void 0;
|
|
348
|
+
const sharedGlobal = customFetch ? { global: customFetch } : {};
|
|
255
349
|
const serviceClient = () => {
|
|
256
|
-
adminClient ??= createClient(env().url, secretKey(), {
|
|
350
|
+
adminClient ??= createClient(env().url, secretKey(), {
|
|
351
|
+
auth: STATELESS,
|
|
352
|
+
...sharedGlobal
|
|
353
|
+
});
|
|
257
354
|
return adminClient;
|
|
258
355
|
};
|
|
259
356
|
const readUrl = () => options.readUrl === false ? void 0 : options.readUrl?.replace(/\/+$/, "") ?? env().readUrl;
|
|
260
357
|
/** Clients for the primary share the service and anon clients; others don't. */
|
|
261
358
|
const supabaseAt = (url, auth, headers = {}) => {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
359
|
+
const custom = Object.keys(headers).length > 0;
|
|
360
|
+
const global = custom ? { global: {
|
|
361
|
+
headers,
|
|
362
|
+
...customFetch
|
|
363
|
+
} } : sharedGlobal;
|
|
364
|
+
const shared = !custom && url === env().url;
|
|
264
365
|
switch (auth.kind) {
|
|
265
366
|
case "user": return createClient(url, env().publishableKey, {
|
|
266
367
|
accessToken: () => Promise.resolve(auth.token),
|
|
@@ -276,7 +377,10 @@ function createServer(betterSupabase, options = {}) {
|
|
|
276
377
|
auth: STATELESS,
|
|
277
378
|
...global
|
|
278
379
|
});
|
|
279
|
-
anonClient ??= createClient(url, env().publishableKey, {
|
|
380
|
+
anonClient ??= createClient(url, env().publishableKey, {
|
|
381
|
+
auth: STATELESS,
|
|
382
|
+
...sharedGlobal
|
|
383
|
+
});
|
|
280
384
|
return anonClient;
|
|
281
385
|
default: return auth;
|
|
282
386
|
}
|
|
@@ -290,11 +394,14 @@ function createServer(betterSupabase, options = {}) {
|
|
|
290
394
|
const restAt = (url, auth, headers = {}) => {
|
|
291
395
|
if (auth.kind !== "user") return supabaseAt(url, auth, headers);
|
|
292
396
|
const key = env().publishableKey;
|
|
293
|
-
return new PostgrestClient(new URL("rest/v1", url.endsWith("/") ? url : `${url}/`).href, {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
397
|
+
return new PostgrestClient(new URL("rest/v1", url.endsWith("/") ? url : `${url}/`).href, {
|
|
398
|
+
headers: {
|
|
399
|
+
...headers,
|
|
400
|
+
apikey: key,
|
|
401
|
+
Authorization: `Bearer ${auth.token}`
|
|
402
|
+
},
|
|
403
|
+
...customFetch
|
|
404
|
+
});
|
|
298
405
|
};
|
|
299
406
|
/** `$client` stays the full Supabase client, built when first read. */
|
|
300
407
|
const withLazyClient = (db, client) => Object.defineProperty(db, "$client", {
|
|
@@ -311,14 +418,101 @@ function createServer(betterSupabase, options = {}) {
|
|
|
311
418
|
});
|
|
312
419
|
return withLazyClient(db, () => supabase ??= supabaseFor(auth));
|
|
313
420
|
};
|
|
314
|
-
const sqlFor = (claims, context, stats) => {
|
|
421
|
+
const sqlFor = (claims, context, stats, session) => {
|
|
315
422
|
if (!options.postgres) throw new TypeError("Direct Postgres access needs createServer(betterSupabase, { postgres: createPostgres() })");
|
|
316
|
-
return betterSupabase.connect(options.postgres.executorFor(claims), context, stats ? { stats } : {});
|
|
423
|
+
return betterSupabase.connect(session ? options.postgres.executorFor(claims, session) : options.postgres.executorFor(claims), context, stats ? { stats } : {});
|
|
424
|
+
};
|
|
425
|
+
/** `ServerOptions.tenant` outside `context()`, where it can't be awaited. */
|
|
426
|
+
const syncTenant = (request, auth) => {
|
|
427
|
+
const tenant = options.tenant?.(request, auth);
|
|
428
|
+
if (tenant !== void 0 && typeof tenant !== "string") throw new TypeError("ServerOptions.tenant returned a promise: use context(request), or pass { tenant } to contextFromResolution");
|
|
429
|
+
return tenant;
|
|
430
|
+
};
|
|
431
|
+
const pinMs = options.replicas?.pinMs ?? 5e3;
|
|
432
|
+
if (options.support && !options.postgres) throw new TypeError("Support sessions need createServer(betterSupabase, { postgres: createPostgres(), support })");
|
|
433
|
+
const support = options.support ? options.support.create(betterSupabase.events, options.auth?.claims ?? betterSupabase.claimsSchema) : supportOff;
|
|
434
|
+
/**
|
|
435
|
+
* The target's context in a support session. Queries run over direct
|
|
436
|
+
* Postgres with the target's claims plus `act`, read-only unless the
|
|
437
|
+
* session allows writes; nothing reaches Supabase with the admin's token.
|
|
438
|
+
*/
|
|
439
|
+
const supportContextFor = (resolution, active, parent, tenant) => {
|
|
440
|
+
const { session, claims } = active;
|
|
441
|
+
const record = (key) => {
|
|
442
|
+
const value = claims[key];
|
|
443
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : void 0;
|
|
444
|
+
};
|
|
445
|
+
const appMetadata = record("app_metadata");
|
|
446
|
+
const userMetadata = record("user_metadata");
|
|
447
|
+
const user = {
|
|
448
|
+
id: session.targetUserId,
|
|
449
|
+
role: typeof claims["role"] === "string" ? claims["role"] : "authenticated",
|
|
450
|
+
...typeof claims["email"] === "string" ? { email: claims["email"] } : {},
|
|
451
|
+
...appMetadata ? { appMetadata } : {},
|
|
452
|
+
...userMetadata ? { userMetadata } : {}
|
|
453
|
+
};
|
|
454
|
+
const auth = {
|
|
455
|
+
kind: "user",
|
|
456
|
+
token: "",
|
|
457
|
+
claims,
|
|
458
|
+
user,
|
|
459
|
+
source: "support",
|
|
460
|
+
expiresAt: Math.floor(session.expiresAt.epochMilliseconds / 1e3)
|
|
461
|
+
};
|
|
462
|
+
const scope = tenant ?? session.tenant;
|
|
463
|
+
const base = userContext(user, claims);
|
|
464
|
+
const context = scope === void 0 ? base : {
|
|
465
|
+
...base,
|
|
466
|
+
tenant: scope
|
|
467
|
+
};
|
|
468
|
+
const sessionOptions = {
|
|
469
|
+
...session.readOnly ? { readOnly: true } : {},
|
|
470
|
+
...scope === void 0 ? {} : { settings: { "better_supabase.tenant": scope } }
|
|
471
|
+
};
|
|
472
|
+
const recorder = new StatsRecorder(parent);
|
|
473
|
+
const sqlClaims = claims;
|
|
474
|
+
const unavailable = () => {
|
|
475
|
+
throw new TypeError("ctx.supabase and ctx.db.$client are not available in a support session; ctx.db and ctx.sql run as the target");
|
|
476
|
+
};
|
|
477
|
+
let db;
|
|
478
|
+
let sql;
|
|
479
|
+
return {
|
|
480
|
+
auth,
|
|
481
|
+
resolution,
|
|
482
|
+
support: active,
|
|
483
|
+
get supabase() {
|
|
484
|
+
return unavailable();
|
|
485
|
+
},
|
|
486
|
+
get db() {
|
|
487
|
+
db ??= Object.defineProperty(sqlFor(sqlClaims, context, recorder, sessionOptions), "$client", {
|
|
488
|
+
get: unavailable,
|
|
489
|
+
enumerable: true,
|
|
490
|
+
configurable: true
|
|
491
|
+
});
|
|
492
|
+
return db;
|
|
493
|
+
},
|
|
494
|
+
get sql() {
|
|
495
|
+
sql ??= sqlFor(sqlClaims, context, recorder, sessionOptions);
|
|
496
|
+
return sql;
|
|
497
|
+
},
|
|
498
|
+
replica: void 0,
|
|
499
|
+
stats: () => recorder.snapshot(),
|
|
500
|
+
apply: (response) => resolution.apply(response)
|
|
501
|
+
};
|
|
317
502
|
};
|
|
318
503
|
/** Clients and repositories are built on first access: most scopes use one of them. */
|
|
319
|
-
const contextFor = (resolution,
|
|
504
|
+
const contextFor = (resolution, requestHeaders, parent, until = 0, tenant, active) => {
|
|
505
|
+
if (active) return supportContextFor(resolution, active, parent, tenant);
|
|
320
506
|
const { auth } = resolution;
|
|
321
|
-
const context = authContext(auth)
|
|
507
|
+
const context = tenant === void 0 ? authContext(auth) : {
|
|
508
|
+
...authContext(auth),
|
|
509
|
+
tenant
|
|
510
|
+
};
|
|
511
|
+
const headers = tenant === void 0 ? requestHeaders : {
|
|
512
|
+
...requestHeaders,
|
|
513
|
+
[TENANT_HEADER]: tenant
|
|
514
|
+
};
|
|
515
|
+
const session = tenant === void 0 ? void 0 : { settings: { "better_supabase.tenant": tenant } };
|
|
322
516
|
const recorder = new StatsRecorder(parent);
|
|
323
517
|
let supabase;
|
|
324
518
|
let db;
|
|
@@ -354,13 +548,14 @@ function createServer(betterSupabase, options = {}) {
|
|
|
354
548
|
},
|
|
355
549
|
get sql() {
|
|
356
550
|
if (!options.postgres || !sqlClaims) return;
|
|
357
|
-
sql ??= sqlFor(sqlClaims, context, recorder);
|
|
551
|
+
sql ??= sqlFor(sqlClaims, context, recorder, session);
|
|
358
552
|
return sql;
|
|
359
553
|
},
|
|
360
|
-
stats: () => recorder.snapshot()
|
|
554
|
+
stats: () => recorder.snapshot(),
|
|
555
|
+
apply: (response) => withPrimaryPin(resolution.apply(response), replica, pinMs)
|
|
361
556
|
};
|
|
362
557
|
};
|
|
363
|
-
const fromResolution = (resolution, request, contextOptions) => contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? (readUrl() ? pinnedUntil(resolution.requestCookies) : 0));
|
|
558
|
+
const fromResolution = (resolution, request, contextOptions, tenant) => contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? (readUrl() ? pinnedUntil(resolution.requestCookies) : 0), tenant, contextOptions.support);
|
|
364
559
|
const resolve = async (request, resolveOptions = {}) => {
|
|
365
560
|
const claims = options.auth?.claims ?? betterSupabase.claimsSchema;
|
|
366
561
|
const userMetadata = betterSupabase.userMetadataSchema;
|
|
@@ -371,6 +566,7 @@ function createServer(betterSupabase, options = {}) {
|
|
|
371
566
|
...userMetadata ? { userMetadata } : {},
|
|
372
567
|
env: env(),
|
|
373
568
|
...resolveOptions.refresh === void 0 ? {} : { refresh: resolveOptions.refresh },
|
|
569
|
+
...resolveOptions.cookies === void 0 ? {} : { cookies: resolveOptions.cookies },
|
|
374
570
|
onRefresh: (event) => {
|
|
375
571
|
options.auth?.onRefresh?.(event);
|
|
376
572
|
betterSupabase.events.emit("refresh", event);
|
|
@@ -381,12 +577,16 @@ function createServer(betterSupabase, options = {}) {
|
|
|
381
577
|
betterSupabase.events.emit("auth", {
|
|
382
578
|
source: auth.kind === "user" ? auth.source === "cookie" ? "cookie" : "bearer" : auth.kind === "service" ? "bearer" : "none",
|
|
383
579
|
ok: auth.kind !== "invalid",
|
|
384
|
-
...auth.kind === "user" ? {
|
|
580
|
+
...auth.kind === "user" ? {
|
|
581
|
+
userId: auth.user.id,
|
|
582
|
+
rawSource: auth.source
|
|
583
|
+
} : {},
|
|
584
|
+
...auth.kind === "anon" || auth.kind === "invalid" ? { reason: auth.reason } : {}
|
|
385
585
|
});
|
|
386
586
|
}
|
|
387
587
|
return resolution;
|
|
388
588
|
};
|
|
389
|
-
|
|
589
|
+
const server = {
|
|
390
590
|
get env() {
|
|
391
591
|
return env();
|
|
392
592
|
},
|
|
@@ -399,12 +599,19 @@ function createServer(betterSupabase, options = {}) {
|
|
|
399
599
|
headers: {},
|
|
400
600
|
requestCookies: [],
|
|
401
601
|
apply: (response) => response
|
|
402
|
-
}, {}, contextOptions.stats, contextOptions.pinnedUntil),
|
|
602
|
+
}, {}, contextOptions.stats, contextOptions.pinnedUntil, contextOptions.tenant, contextOptions.support),
|
|
403
603
|
async context(request, contextOptions = {}) {
|
|
404
|
-
const resolution = await resolve(request, {
|
|
405
|
-
|
|
604
|
+
const resolution = await resolve(request, {
|
|
605
|
+
refresh: contextOptions.refresh ?? false,
|
|
606
|
+
...contextOptions.cookies === void 0 ? {} : { cookies: contextOptions.cookies }
|
|
607
|
+
});
|
|
608
|
+
const [tenant, active] = await Promise.all([contextOptions.tenant ?? options.tenant?.(request, resolution.auth), contextOptions.support ?? support.current(request, resolution.auth)]);
|
|
609
|
+
return fromResolution(resolution, request, active ? {
|
|
610
|
+
...contextOptions,
|
|
611
|
+
support: active
|
|
612
|
+
} : contextOptions, tenant);
|
|
406
613
|
},
|
|
407
|
-
contextFromResolution: (resolution, request, contextOptions = {}) => fromResolution(resolution, request, contextOptions),
|
|
614
|
+
contextFromResolution: (resolution, request, contextOptions = {}) => fromResolution(resolution, request, contextOptions, contextOptions.tenant ?? syncTenant(request, resolution.auth)),
|
|
408
615
|
admin: (context = {}) => betterSupabase.connect(serviceClient(), {
|
|
409
616
|
actor: {
|
|
410
617
|
id: "service",
|
|
@@ -414,6 +621,7 @@ function createServer(betterSupabase, options = {}) {
|
|
|
414
621
|
claims: { role: "service_role" },
|
|
415
622
|
...context
|
|
416
623
|
}),
|
|
624
|
+
support,
|
|
417
625
|
deleteAccount: (userId, deleteOptions) => deleteAccount(betterSupabase, serviceClient, userId, deleteOptions),
|
|
418
626
|
actingAs: (userId, claims = {}, impersonation) => {
|
|
419
627
|
const full = {
|
|
@@ -433,6 +641,20 @@ function createServer(betterSupabase, options = {}) {
|
|
|
433
641
|
});
|
|
434
642
|
}
|
|
435
643
|
};
|
|
644
|
+
verifiedSeeders.set(server, (verified) => rememberVerified({
|
|
645
|
+
...options.auth,
|
|
646
|
+
env: env()
|
|
647
|
+
}, verified));
|
|
648
|
+
return server;
|
|
649
|
+
}
|
|
650
|
+
const verifiedSeeders = /* @__PURE__ */ new WeakMap();
|
|
651
|
+
/**
|
|
652
|
+
* Hands `server` a bearer token another layer verified for this request
|
|
653
|
+
* (`@supabase/server`'s `withSupabase`), so `context()` doesn't verify it
|
|
654
|
+
* again. See `rememberVerified` for when it is accepted.
|
|
655
|
+
*/
|
|
656
|
+
function rememberVerifiedFor(server, verified) {
|
|
657
|
+
return verifiedSeeders.get(server)?.(verified) ?? false;
|
|
436
658
|
}
|
|
437
659
|
//#endregion
|
|
438
|
-
export {
|
|
660
|
+
export { DEFAULT_PIN_MS as _, withExtra as a, withPrimaryPin as b, supportCookie as c, guard as d, isResult as f, jsonResponse as g, jsonReplacer as h, rememberVerifiedFor as i, supportCookieValue as l, settle as m, createServer as n, SUPPORT_COOKIE as o, respond as p, extendServer as r, clearSupportCookie as s, TENANT_HEADER as t, defaultExpose as u, PRIMARY_COOKIE as v, primaryCookieOptions as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as dbError, t as DbException } from "./errors-
|
|
1
|
+
import { n as dbError, t as DbException } from "./errors-DtwXa6b8.js";
|
|
2
2
|
import { t as lookupOf } from "./lookup-BaP5bI27.js";
|
|
3
3
|
//#region src/plugins/shared.ts
|
|
4
4
|
/** Database column name for an app column, or `undefined` if it is unknown. */
|
|
@@ -35,6 +35,20 @@ function withDefault(row, column, value) {
|
|
|
35
35
|
[column]: value
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Refuses caller-supplied values for columns a plugin fills (timestamps,
|
|
40
|
+
* actor, soft delete) unless the call passes `{ override: true }`. Returns
|
|
41
|
+
* whether the caller may keep its own values.
|
|
42
|
+
*/
|
|
43
|
+
function guardManaged(op, columns, plugin, options) {
|
|
44
|
+
if (options["override"] === true) return true;
|
|
45
|
+
const rows = op.kind === "insert" ? op.rows : op.kind === "update" ? [op.set] : [];
|
|
46
|
+
for (const column of columns) {
|
|
47
|
+
if (column === void 0) continue;
|
|
48
|
+
if (rows.some((row) => column in row)) throw new DbException(dbError("invalid_request", `"${column}" on ${op.table.key} is filled by ${plugin}(); pass { override: true } to set it yourself`, { table: op.table.key }));
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
38
52
|
/** Upserts that may update existing rows must not overwrite "created" columns. */
|
|
39
53
|
function insertsOnly(op) {
|
|
40
54
|
return !op.onConflict || op.onConflict.action === "ignore";
|
|
@@ -55,4 +69,4 @@ function toDb(table, row) {
|
|
|
55
69
|
return out;
|
|
56
70
|
}
|
|
57
71
|
//#endregion
|
|
58
|
-
export {
|
|
72
|
+
export { insertsOnly as a, toApp as c, guardManaged as i, toDb as l, equals as n, isNotNull as o, forbidden as r, isNull as s, dbName as t, withDefault as u };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
import { t as KitTransport } from "./kit-transport-B82k1djG.js";
|
|
3
|
+
//#region src/webhooks/url-policy.d.ts
|
|
4
|
+
/** Decides whether a webhook may be sent to `url`. Rejecting counts as `false`. */
|
|
5
|
+
type AllowUrl = (url: URL) => boolean | Promise<boolean>;
|
|
6
|
+
/** The addresses a host name resolves to. */
|
|
7
|
+
type ResolveHost = (hostname: string) => Promise<readonly string[]>;
|
|
8
|
+
interface PublicUrlOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Resolves host names so each address can be checked. Defaults to
|
|
11
|
+
* `node:dns` where the runtime has it (Node, Bun, Deno); pass one on other
|
|
12
|
+
* runtimes, or `false` to check only the URL.
|
|
13
|
+
*/
|
|
14
|
+
readonly resolve?: ResolveHost | false;
|
|
15
|
+
/** Allow `http:` URLs, e.g. for local development. */
|
|
16
|
+
readonly allowHttp?: boolean;
|
|
17
|
+
/** Host names that skip every check, e.g. an internal receiver you trust. */
|
|
18
|
+
readonly allowHosts?: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether an IP address is reachable on the public internet: not loopback,
|
|
22
|
+
* private, link-local (cloud metadata), carrier-grade NAT, multicast or
|
|
23
|
+
* reserved. Returns `false` for anything that isn't an IP address.
|
|
24
|
+
*/
|
|
25
|
+
declare function isPublicAddress(address: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The default `allowUrl`: HTTPS only, no credentials in the URL, no local
|
|
28
|
+
* host names, and every address the host resolves to must be public. The
|
|
29
|
+
* delivery transport calls it again for each redirect.
|
|
30
|
+
*
|
|
31
|
+
* DNS can change between this check and the connection. Route webhooks
|
|
32
|
+
* through an egress proxy when that matters.
|
|
33
|
+
*/
|
|
34
|
+
declare function publicUrl(options?: PublicUrlOptions): AllowUrl;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/webhooks/http.d.ts
|
|
37
|
+
interface WebhookRequest {
|
|
38
|
+
readonly url: string;
|
|
39
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
40
|
+
readonly body: string;
|
|
41
|
+
readonly signal?: AbortSignal;
|
|
42
|
+
}
|
|
43
|
+
interface WebhookResponse {
|
|
44
|
+
readonly status: number;
|
|
45
|
+
readonly body: string;
|
|
46
|
+
/** Seconds from the `Retry-After` header, when the receiver sent one. */
|
|
47
|
+
readonly retryAfter?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Sends one signed webhook request. Throw `WebhookPolicyError` when the
|
|
51
|
+
* request must never be retried (a blocked URL); any other error retries.
|
|
52
|
+
*/
|
|
53
|
+
interface WebhookTransport {
|
|
54
|
+
readonly apiVersion: 1;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
send(request: WebhookRequest): Promise<WebhookResponse>;
|
|
57
|
+
}
|
|
58
|
+
/** A delivery that must not be retried, e.g. a URL `allowUrl` rejected. */
|
|
59
|
+
declare class WebhookPolicyError extends Error {
|
|
60
|
+
override readonly name = "WebhookPolicyError";
|
|
61
|
+
}
|
|
62
|
+
interface FetchTransportOptions {
|
|
63
|
+
readonly fetch?: typeof fetch;
|
|
64
|
+
/** Checked before the first request and before each redirect. */
|
|
65
|
+
readonly allowUrl?: AllowUrl;
|
|
66
|
+
/**
|
|
67
|
+
* Defaults to 0: a redirect fails the delivery without a retry. Each hop
|
|
68
|
+
* you allow is checked with `allowUrl` first.
|
|
69
|
+
*/
|
|
70
|
+
readonly maxRedirects?: number;
|
|
71
|
+
/** Per request. Defaults to 10 seconds. */
|
|
72
|
+
readonly timeoutMs?: number;
|
|
73
|
+
/** The response body bytes kept; the rest is not read. Defaults to 64 KiB. */
|
|
74
|
+
readonly maxResponseBytes?: number;
|
|
75
|
+
}
|
|
76
|
+
/** Sends webhooks with `fetch`, following redirects only to allowed URLs. */
|
|
77
|
+
declare function fetchTransport(options?: FetchTransportOptions): WebhookTransport;
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/webhooks/secrets.d.ts
|
|
80
|
+
interface RotateSecretOptions {
|
|
81
|
+
/** How long older secrets keep signing, e.g. `'24 hours'` (the default). */
|
|
82
|
+
readonly overlap?: string;
|
|
83
|
+
/** Use this secret instead of a generated one, e.g. when migrating. */
|
|
84
|
+
readonly secret?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Where endpoint signing secrets live. `sqlSecretStore` (Vault or a
|
|
88
|
+
* column, set in `kits.webhooks-out.options.secretStorage`) is the default;
|
|
89
|
+
* implement it for a KMS or a secrets manager.
|
|
90
|
+
*/
|
|
91
|
+
interface WebhookSecretStore {
|
|
92
|
+
readonly apiVersion: 1;
|
|
93
|
+
/** The live secrets of an endpoint, newest first. */
|
|
94
|
+
secrets(endpointId: string): Promise<readonly string[]>;
|
|
95
|
+
/** Creates a new secret and returns it once. */
|
|
96
|
+
rotate?(endpointId: string, options?: RotateSecretOptions): Promise<string>;
|
|
97
|
+
}
|
|
98
|
+
/** The kit's `webhook_secrets` and `rotate_webhook_secret` functions. */
|
|
99
|
+
declare function sqlSecretStore(transport: KitTransport, options?: {
|
|
100
|
+
readonly schema?: string;
|
|
101
|
+
}): WebhookSecretStore;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/webhooks/signers.d.ts
|
|
104
|
+
interface WebhookSignInput {
|
|
105
|
+
/** The delivery id, the same on every retry so receivers can deduplicate. */
|
|
106
|
+
readonly id: string;
|
|
107
|
+
readonly body: string;
|
|
108
|
+
readonly timestamp: Temporal.Instant;
|
|
109
|
+
/** The endpoint's live secrets, newest first. Sign with each while rotating. */
|
|
110
|
+
readonly secrets: readonly string[];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Adds the signature headers to an outgoing webhook. `standardWebhooks()`
|
|
114
|
+
* is the default; `hmacSigner()` covers custom header and payload formats.
|
|
115
|
+
*/
|
|
116
|
+
interface WebhookSigner {
|
|
117
|
+
readonly apiVersion: 1;
|
|
118
|
+
readonly name: string;
|
|
119
|
+
sign(input: WebhookSignInput): Readonly<Record<string, string>> | Promise<Readonly<Record<string, string>>>;
|
|
120
|
+
}
|
|
121
|
+
interface StandardWebhooksOptions {
|
|
122
|
+
/** Header names, for receivers that expect a prefix such as `svix-`. */
|
|
123
|
+
readonly headers?: {
|
|
124
|
+
readonly id?: string;
|
|
125
|
+
readonly timestamp?: string;
|
|
126
|
+
readonly signature?: string;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Signs with the [Standard Webhooks](https://www.standardwebhooks.com/)
|
|
131
|
+
* scheme: `webhook-id`, `webhook-timestamp` and one `v1,` signature per
|
|
132
|
+
* secret in `webhook-signature`. `verifyWebhook` checks it.
|
|
133
|
+
*/
|
|
134
|
+
declare function standardWebhooks(options?: StandardWebhooksOptions): WebhookSigner;
|
|
135
|
+
interface HmacSignerOptions {
|
|
136
|
+
readonly name?: string;
|
|
137
|
+
/** e.g. `x-acme-signature`. */
|
|
138
|
+
readonly signatureHeader: string;
|
|
139
|
+
/** Sends the epoch-seconds timestamp in this header. */
|
|
140
|
+
readonly timestampHeader?: string;
|
|
141
|
+
/** Sends the delivery id in this header. */
|
|
142
|
+
readonly idHeader?: string;
|
|
143
|
+
/** The signed text. Defaults to `${timestamp}.${body}`. */
|
|
144
|
+
readonly content?: (input: {
|
|
145
|
+
readonly id: string;
|
|
146
|
+
readonly timestamp: string;
|
|
147
|
+
readonly body: string;
|
|
148
|
+
}) => string;
|
|
149
|
+
/** Defaults to `hex`. */
|
|
150
|
+
readonly encoding?: "hex" | "base64";
|
|
151
|
+
/** Written before each signature. Defaults to `v1=`. */
|
|
152
|
+
readonly prefix?: string;
|
|
153
|
+
/** Joins the signatures of several secrets. Defaults to `,`. */
|
|
154
|
+
readonly separator?: string;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* HMAC-SHA256 with the secret's UTF-8 bytes as the key, for receivers that
|
|
158
|
+
* verify an existing format, e.g. `v1=<hex>` over `timestamp.body`.
|
|
159
|
+
*/
|
|
160
|
+
declare function hmacSigner(options: HmacSignerOptions): WebhookSigner;
|
|
161
|
+
//#endregion
|
|
162
|
+
export { PublicUrlOptions as _, hmacSigner as a, publicUrl as b, WebhookSecretStore as c, WebhookPolicyError as d, WebhookRequest as f, AllowUrl as g, fetchTransport as h, WebhookSigner as i, sqlSecretStore as l, WebhookTransport as m, StandardWebhooksOptions as n, standardWebhooks as o, WebhookResponse as p, WebhookSignInput as r, RotateSecretOptions as s, HmacSignerOptions as t, FetchTransportOptions as u, ResolveHost as v, isPublicAddress as y };
|