better-supabase 0.4.0 → 0.5.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/README.md +2 -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-L7Z8mYTx.js} +531 -50
- package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
- package/dist/cli/gen-BJc9vdyu.js +2 -0
- package/dist/cli/{gen-Ccn_p90L.js → gen-C2osRcr5.js} +283 -87
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{init-ClnxRYBF.js → init-Saf5-R9T.js} +2 -2
- package/dist/cli/{introspect-DxqfkVIv.js → introspect--H7oGmcd.js} +2 -2
- package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
- package/dist/cli/read-sets-BfMQcKy8.js +289 -0
- package/dist/cli/{run-CP20LKk_.js → run-BKR2W_79.js} +16 -12
- package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
- package/dist/cli/sql-DzPbfngx.js +348 -0
- package/dist/cli/{version-5mW_8sY1.js → version-BDJuO0UR.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-CbvFmGD8.js → config-DreERlKo.js} +1 -1
- package/dist/{config-C1CYcm6o.d.ts → config-T_ejwyd3.d.ts} +176 -11
- 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-DUxU4oHe.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-VfaGc4Q_.js +41 -0
- package/dist/{index-BBdvo-5d.d.ts → index-BGdgBPdf.d.ts} +49 -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-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-Cf-D2jk1.d.ts → index-Dxn2a5cw.d.ts} +2 -2
- 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-CusDgPaD.d.ts} +5 -1
- 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-BTrQ_ZTp.d.ts} +1 -1
- package/dist/{resolve-BAqQcwMW.d.ts → resolve-C15TJq77.d.ts} +11 -5
- package/dist/{resource-My_y-Syh.js → resource-CRxxwkQq.js} +22 -7
- package/dist/{resource-BJcaIfxi.d.ts → resource-D8iqWMr-.d.ts} +5 -5
- package/dist/respond-D2OQl62-.d.ts +371 -0
- package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
- package/dist/server/index.d.ts +14 -12
- package/dist/server/index.js +11 -17
- package/dist/{server-BA75B0mT.js → server-Csx2sRjg.js} +251 -34
- 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 +247 -10
- package/dist/sql/index.js +8590 -779
- 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-DT-OhSxD.d.ts +88 -0
- package/dist/support-Dk6vdmEw.js +291 -0
- package/dist/support-view-Da5peKFg.d.ts +17 -0
- package/dist/support-view-g4YLBeqX.js +18 -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 +100 -10
- package/dist/testing/index.js +336 -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-DM_Roffb.js} +1 -1
- package/dist/{view-B64o81wp.d.ts → view-BEf0diC5.d.ts} +8 -3
- package/dist/{mfa-byfjruR9.js → view-Bq64ki8-.js} +95 -38
- 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 +16 -8
- package/schemas/config-v1.json +131 -1
- package/schemas/doctor-report-v1.json +10 -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 +3 -0
- package/skills/better-supabase-auth/references/permdock.md +56 -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
package/dist/server/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { n as dbError } from "../errors-
|
|
2
|
-
import { a as problemResponse, i as isProblem, n as PROBLEM_TYPE_BASE, o as toProblem, r as fromProblem, t as PROBLEM_CONTENT_TYPE } from "../problem-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { n as
|
|
6
|
-
import {
|
|
7
|
-
import { t as
|
|
1
|
+
import { n as dbError } from "../errors-DtwXa6b8.js";
|
|
2
|
+
import { a as problemResponse, i as isProblem, n as PROBLEM_TYPE_BASE, o as toProblem, r as fromProblem, t as PROBLEM_CONTENT_TYPE } from "../problem-BW1KhzSG.js";
|
|
3
|
+
import { n as impersonatorOf, r as userContext } from "../impersonation-VfaGc4Q_.js";
|
|
4
|
+
import { t as supportOf } from "../support-view-g4YLBeqX.js";
|
|
5
|
+
import { c as supportCookie, d as guard, f as isResult, l as supportCookieValue, m as settle, n as createServer, o as SUPPORT_COOKIE, p as respond, s as clearSupportCookie, t as TENANT_HEADER, v as PRIMARY_COOKIE } from "../server-Csx2sRjg.js";
|
|
6
|
+
import { _ as writeSession, a as checkAal, d as AUTH_CACHE_HEADERS, g as sessionCookieName, i as amrOf, m as readSession, n as toSession, o as authContext, r as aalOf, s as clientIp, u as resolveAuth, v as refreshSession } from "../view-Bq64ki8-.js";
|
|
7
|
+
import { n as sqlSupportStore, r as supportClaims, t as supportSessions } from "../support-Dk6vdmEw.js";
|
|
8
|
+
import { n as postgresExecutor } from "../executor-XycESQPH.js";
|
|
9
|
+
import { t as defineResource } from "../resource-CRxxwkQq.js";
|
|
8
10
|
import { defineMiddleware } from "@supabase/middleware";
|
|
9
11
|
//#region src/server/middleware.ts
|
|
10
12
|
/** Repository context from a `@supabase/server` context. */
|
|
@@ -12,15 +14,7 @@ function contextFromSupabase(ctx) {
|
|
|
12
14
|
switch (ctx.authMode) {
|
|
13
15
|
case "user":
|
|
14
16
|
if (!ctx.userClaims) break;
|
|
15
|
-
return {
|
|
16
|
-
actor: {
|
|
17
|
-
id: ctx.userClaims.id,
|
|
18
|
-
kind: "user",
|
|
19
|
-
...ctx.userClaims.role === void 0 ? {} : { role: ctx.userClaims.role },
|
|
20
|
-
...ctx.userClaims.email === void 0 ? {} : { email: ctx.userClaims.email }
|
|
21
|
-
},
|
|
22
|
-
claims: ctx.jwtClaims ?? {}
|
|
23
|
-
};
|
|
17
|
+
return userContext(ctx.userClaims, ctx.jwtClaims ?? {});
|
|
24
18
|
case "secret": return {
|
|
25
19
|
actor: {
|
|
26
20
|
id: "service",
|
|
@@ -94,4 +88,4 @@ async function checkSession(sql, auth) {
|
|
|
94
88
|
return row?.active === true ? void 0 : revoked;
|
|
95
89
|
}
|
|
96
90
|
//#endregion
|
|
97
|
-
export { AUTH_CACHE_HEADERS, PRIMARY_COOKIE, PROBLEM_CONTENT_TYPE, PROBLEM_TYPE_BASE, aalOf, amrOf, authContext, checkAal, checkSession, clientIp, contextFromSupabase, createServer, defineResource, fromProblem, guard, impersonatorOf, isProblem, isResult, problemResponse, readSession, refreshSession, resolveAuth, respond, sessionCookieName, settle, toProblem, toSession, withBetterPostgres, withBetterSupabase, writeSession };
|
|
91
|
+
export { AUTH_CACHE_HEADERS, PRIMARY_COOKIE, PROBLEM_CONTENT_TYPE, PROBLEM_TYPE_BASE, SUPPORT_COOKIE, TENANT_HEADER, aalOf, amrOf, authContext, checkAal, checkSession, clearSupportCookie, clientIp, contextFromSupabase, createServer, defineResource, fromProblem, guard, impersonatorOf, isProblem, isResult, problemResponse, readSession, refreshSession, resolveAuth, respond, sessionCookieName, settle, sqlSupportStore, supportClaims, supportCookie, supportCookieValue, supportOf, supportSessions, toProblem, toSession, withBetterPostgres, withBetterSupabase, writeSession };
|
|
@@ -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 { r as userContext, t as actClaim } from "./impersonation-VfaGc4Q_.js";
|
|
5
6
|
import { loadEnv } from "./env/index.js";
|
|
6
|
-
import {
|
|
7
|
+
import { a as checkAal, b as delegationOf, c as prefetchJwks, l as rememberVerified, o as authContext, t as isAnonymousUser, u as resolveAuth, y as actorOf } from "./view-Bq64ki8-.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,6 +162,21 @@ 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
|
/** The scopes a delegated user token lacks; empty for the user's own session. */
|
|
145
182
|
function missingScopes(auth, required) {
|
|
@@ -155,6 +192,7 @@ function guard(auth, allow = ["user"], aal = "aal1", scopes = []) {
|
|
|
155
192
|
if (allow.includes(auth.kind)) {
|
|
156
193
|
const denied = checkAal(auth, aal);
|
|
157
194
|
if (denied || auth.kind !== "user") return denied;
|
|
195
|
+
if (isAnonymousUser(auth.claims) && !allow.includes("anonymous") && !allow.includes("anon")) return dbError("forbidden", "Sign up to continue", { code: "ANONYMOUS_USER" });
|
|
158
196
|
const missing = missingScopes(auth, scopes);
|
|
159
197
|
return missing.length > 0 ? dbError("forbidden", `The token lacks the scope ${missing.join(" ")}`, {
|
|
160
198
|
code: "INSUFFICIENT_SCOPE",
|
|
@@ -202,7 +240,7 @@ async function respond(run, options = {}) {
|
|
|
202
240
|
if (!settled.ok) return problemResponse(settled.error, options);
|
|
203
241
|
if (settled.data instanceof Response) return settled.data;
|
|
204
242
|
if (settled.data === void 0) return new Response(null, { status: 204 });
|
|
205
|
-
return
|
|
243
|
+
return jsonResponse(settled.data, { status: options.status ?? 200 });
|
|
206
244
|
}
|
|
207
245
|
/** Prefetching the JWKS would add a network call to every test that builds a server. */
|
|
208
246
|
function defaultPrefetchJwks() {
|
|
@@ -212,7 +250,56 @@ function defaultExpose() {
|
|
|
212
250
|
return typeof process !== "undefined" && process.env["NODE_ENV"] === "development";
|
|
213
251
|
}
|
|
214
252
|
//#endregion
|
|
253
|
+
//#region src/auth/support-cookie.ts
|
|
254
|
+
/** The cookie that carries the support session id. */
|
|
255
|
+
const SUPPORT_COOKIE = "bs-support";
|
|
256
|
+
/** `Set-Cookie` for a session: HttpOnly, SameSite=Lax, gone when the session expires. */
|
|
257
|
+
function supportCookie(session, options = {}, now = Date.now()) {
|
|
258
|
+
const maxAge = Math.max(0, Math.ceil((session.expiresAt.epochMilliseconds - now) / 1e3));
|
|
259
|
+
return cookieLine(options, encodeURIComponent(session.id), maxAge);
|
|
260
|
+
}
|
|
261
|
+
/** `Set-Cookie` that removes the support cookie. */
|
|
262
|
+
function clearSupportCookie(options = {}) {
|
|
263
|
+
return cookieLine(options, "", 0);
|
|
264
|
+
}
|
|
265
|
+
function cookieLine(options, value, maxAge) {
|
|
266
|
+
return [
|
|
267
|
+
`${options.name ?? "bs-support"}=${value}`,
|
|
268
|
+
`Path=${options.path ?? "/"}`,
|
|
269
|
+
`Max-Age=${String(maxAge)}`,
|
|
270
|
+
"HttpOnly",
|
|
271
|
+
"SameSite=Lax",
|
|
272
|
+
...options.secure === false ? [] : ["Secure"]
|
|
273
|
+
].join("; ");
|
|
274
|
+
}
|
|
275
|
+
/** The support session id in a `Cookie` header, or `undefined`. */
|
|
276
|
+
function supportCookieValue(cookieHeader, name = SUPPORT_COOKIE) {
|
|
277
|
+
if (!cookieHeader) return void 0;
|
|
278
|
+
for (const part of cookieHeader.split(";")) {
|
|
279
|
+
const index = part.indexOf("=");
|
|
280
|
+
if (index === -1 || part.slice(0, index).trim() !== name) continue;
|
|
281
|
+
const value = decodeURIComponent(part.slice(index + 1).trim());
|
|
282
|
+
return value === "" ? void 0 : value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/server/support-off.ts
|
|
287
|
+
const NOT_CONFIGURED = "Support sessions need createServer(betterSupabase, { support: supportSessions({ store }) })";
|
|
288
|
+
const notConfigured = () => AsyncResult.err(dbError("unexpected", NOT_CONFIGURED));
|
|
289
|
+
/** `bs.support` without `ServerOptions.support`: every call fails. */
|
|
290
|
+
const supportOff = {
|
|
291
|
+
start: notConfigured,
|
|
292
|
+
stop: notConfigured,
|
|
293
|
+
revoke: notConfigured,
|
|
294
|
+
current: () => Promise.resolve(void 0),
|
|
295
|
+
list: notConfigured,
|
|
296
|
+
sessionIdOf: () => void 0,
|
|
297
|
+
clearCookie: () => clearSupportCookie()
|
|
298
|
+
};
|
|
299
|
+
//#endregion
|
|
215
300
|
//#region src/server/server.ts
|
|
301
|
+
/** The header `current_tenant_id()` reads over the Data API. */
|
|
302
|
+
const TENANT_HEADER = "x-bs-tenant";
|
|
216
303
|
const STATELESS = {
|
|
217
304
|
persistSession: false,
|
|
218
305
|
autoRefreshToken: false,
|
|
@@ -252,15 +339,24 @@ function createServer(betterSupabase, options = {}) {
|
|
|
252
339
|
if (!key) throw new TypeError("admin() needs SUPABASE_SECRET_KEY; it is not set");
|
|
253
340
|
return key;
|
|
254
341
|
};
|
|
342
|
+
const customFetch = options.fetch ? { fetch: options.fetch } : void 0;
|
|
343
|
+
const sharedGlobal = customFetch ? { global: customFetch } : {};
|
|
255
344
|
const serviceClient = () => {
|
|
256
|
-
adminClient ??= createClient(env().url, secretKey(), {
|
|
345
|
+
adminClient ??= createClient(env().url, secretKey(), {
|
|
346
|
+
auth: STATELESS,
|
|
347
|
+
...sharedGlobal
|
|
348
|
+
});
|
|
257
349
|
return adminClient;
|
|
258
350
|
};
|
|
259
351
|
const readUrl = () => options.readUrl === false ? void 0 : options.readUrl?.replace(/\/+$/, "") ?? env().readUrl;
|
|
260
352
|
/** Clients for the primary share the service and anon clients; others don't. */
|
|
261
353
|
const supabaseAt = (url, auth, headers = {}) => {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
354
|
+
const custom = Object.keys(headers).length > 0;
|
|
355
|
+
const global = custom ? { global: {
|
|
356
|
+
headers,
|
|
357
|
+
...customFetch
|
|
358
|
+
} } : sharedGlobal;
|
|
359
|
+
const shared = !custom && url === env().url;
|
|
264
360
|
switch (auth.kind) {
|
|
265
361
|
case "user": return createClient(url, env().publishableKey, {
|
|
266
362
|
accessToken: () => Promise.resolve(auth.token),
|
|
@@ -276,7 +372,10 @@ function createServer(betterSupabase, options = {}) {
|
|
|
276
372
|
auth: STATELESS,
|
|
277
373
|
...global
|
|
278
374
|
});
|
|
279
|
-
anonClient ??= createClient(url, env().publishableKey, {
|
|
375
|
+
anonClient ??= createClient(url, env().publishableKey, {
|
|
376
|
+
auth: STATELESS,
|
|
377
|
+
...sharedGlobal
|
|
378
|
+
});
|
|
280
379
|
return anonClient;
|
|
281
380
|
default: return auth;
|
|
282
381
|
}
|
|
@@ -290,11 +389,14 @@ function createServer(betterSupabase, options = {}) {
|
|
|
290
389
|
const restAt = (url, auth, headers = {}) => {
|
|
291
390
|
if (auth.kind !== "user") return supabaseAt(url, auth, headers);
|
|
292
391
|
const key = env().publishableKey;
|
|
293
|
-
return new PostgrestClient(new URL("rest/v1", url.endsWith("/") ? url : `${url}/`).href, {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
392
|
+
return new PostgrestClient(new URL("rest/v1", url.endsWith("/") ? url : `${url}/`).href, {
|
|
393
|
+
headers: {
|
|
394
|
+
...headers,
|
|
395
|
+
apikey: key,
|
|
396
|
+
Authorization: `Bearer ${auth.token}`
|
|
397
|
+
},
|
|
398
|
+
...customFetch
|
|
399
|
+
});
|
|
298
400
|
};
|
|
299
401
|
/** `$client` stays the full Supabase client, built when first read. */
|
|
300
402
|
const withLazyClient = (db, client) => Object.defineProperty(db, "$client", {
|
|
@@ -311,14 +413,101 @@ function createServer(betterSupabase, options = {}) {
|
|
|
311
413
|
});
|
|
312
414
|
return withLazyClient(db, () => supabase ??= supabaseFor(auth));
|
|
313
415
|
};
|
|
314
|
-
const sqlFor = (claims, context, stats) => {
|
|
416
|
+
const sqlFor = (claims, context, stats, session) => {
|
|
315
417
|
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 } : {});
|
|
418
|
+
return betterSupabase.connect(session ? options.postgres.executorFor(claims, session) : options.postgres.executorFor(claims), context, stats ? { stats } : {});
|
|
419
|
+
};
|
|
420
|
+
/** `ServerOptions.tenant` outside `context()`, where it can't be awaited. */
|
|
421
|
+
const syncTenant = (request, auth) => {
|
|
422
|
+
const tenant = options.tenant?.(request, auth);
|
|
423
|
+
if (tenant !== void 0 && typeof tenant !== "string") throw new TypeError("ServerOptions.tenant returned a promise: use context(request), or pass { tenant } to contextFromResolution");
|
|
424
|
+
return tenant;
|
|
425
|
+
};
|
|
426
|
+
const pinMs = options.replicas?.pinMs ?? 5e3;
|
|
427
|
+
if (options.support && !options.postgres) throw new TypeError("Support sessions need createServer(betterSupabase, { postgres: createPostgres(), support })");
|
|
428
|
+
const support = options.support ? options.support.create(betterSupabase.events, options.auth?.claims ?? betterSupabase.claimsSchema) : supportOff;
|
|
429
|
+
/**
|
|
430
|
+
* The target's context in a support session. Queries run over direct
|
|
431
|
+
* Postgres with the target's claims plus `act`, read-only unless the
|
|
432
|
+
* session allows writes; nothing reaches Supabase with the admin's token.
|
|
433
|
+
*/
|
|
434
|
+
const supportContextFor = (resolution, active, parent, tenant) => {
|
|
435
|
+
const { session, claims } = active;
|
|
436
|
+
const record = (key) => {
|
|
437
|
+
const value = claims[key];
|
|
438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : void 0;
|
|
439
|
+
};
|
|
440
|
+
const appMetadata = record("app_metadata");
|
|
441
|
+
const userMetadata = record("user_metadata");
|
|
442
|
+
const user = {
|
|
443
|
+
id: session.targetUserId,
|
|
444
|
+
role: typeof claims["role"] === "string" ? claims["role"] : "authenticated",
|
|
445
|
+
...typeof claims["email"] === "string" ? { email: claims["email"] } : {},
|
|
446
|
+
...appMetadata ? { appMetadata } : {},
|
|
447
|
+
...userMetadata ? { userMetadata } : {}
|
|
448
|
+
};
|
|
449
|
+
const auth = {
|
|
450
|
+
kind: "user",
|
|
451
|
+
token: "",
|
|
452
|
+
claims,
|
|
453
|
+
user,
|
|
454
|
+
source: "support",
|
|
455
|
+
expiresAt: Math.floor(session.expiresAt.epochMilliseconds / 1e3)
|
|
456
|
+
};
|
|
457
|
+
const scope = tenant ?? session.tenant;
|
|
458
|
+
const base = userContext(user, claims);
|
|
459
|
+
const context = scope === void 0 ? base : {
|
|
460
|
+
...base,
|
|
461
|
+
tenant: scope
|
|
462
|
+
};
|
|
463
|
+
const sessionOptions = {
|
|
464
|
+
...session.readOnly ? { readOnly: true } : {},
|
|
465
|
+
...scope === void 0 ? {} : { settings: { "better_supabase.tenant": scope } }
|
|
466
|
+
};
|
|
467
|
+
const recorder = new StatsRecorder(parent);
|
|
468
|
+
const sqlClaims = claims;
|
|
469
|
+
const unavailable = () => {
|
|
470
|
+
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");
|
|
471
|
+
};
|
|
472
|
+
let db;
|
|
473
|
+
let sql;
|
|
474
|
+
return {
|
|
475
|
+
auth,
|
|
476
|
+
resolution,
|
|
477
|
+
support: active,
|
|
478
|
+
get supabase() {
|
|
479
|
+
return unavailable();
|
|
480
|
+
},
|
|
481
|
+
get db() {
|
|
482
|
+
db ??= Object.defineProperty(sqlFor(sqlClaims, context, recorder, sessionOptions), "$client", {
|
|
483
|
+
get: unavailable,
|
|
484
|
+
enumerable: true,
|
|
485
|
+
configurable: true
|
|
486
|
+
});
|
|
487
|
+
return db;
|
|
488
|
+
},
|
|
489
|
+
get sql() {
|
|
490
|
+
sql ??= sqlFor(sqlClaims, context, recorder, sessionOptions);
|
|
491
|
+
return sql;
|
|
492
|
+
},
|
|
493
|
+
replica: void 0,
|
|
494
|
+
stats: () => recorder.snapshot(),
|
|
495
|
+
apply: (response) => resolution.apply(response)
|
|
496
|
+
};
|
|
317
497
|
};
|
|
318
498
|
/** Clients and repositories are built on first access: most scopes use one of them. */
|
|
319
|
-
const contextFor = (resolution,
|
|
499
|
+
const contextFor = (resolution, requestHeaders, parent, until = 0, tenant, active) => {
|
|
500
|
+
if (active) return supportContextFor(resolution, active, parent, tenant);
|
|
320
501
|
const { auth } = resolution;
|
|
321
|
-
const context = authContext(auth)
|
|
502
|
+
const context = tenant === void 0 ? authContext(auth) : {
|
|
503
|
+
...authContext(auth),
|
|
504
|
+
tenant
|
|
505
|
+
};
|
|
506
|
+
const headers = tenant === void 0 ? requestHeaders : {
|
|
507
|
+
...requestHeaders,
|
|
508
|
+
[TENANT_HEADER]: tenant
|
|
509
|
+
};
|
|
510
|
+
const session = tenant === void 0 ? void 0 : { settings: { "better_supabase.tenant": tenant } };
|
|
322
511
|
const recorder = new StatsRecorder(parent);
|
|
323
512
|
let supabase;
|
|
324
513
|
let db;
|
|
@@ -354,13 +543,14 @@ function createServer(betterSupabase, options = {}) {
|
|
|
354
543
|
},
|
|
355
544
|
get sql() {
|
|
356
545
|
if (!options.postgres || !sqlClaims) return;
|
|
357
|
-
sql ??= sqlFor(sqlClaims, context, recorder);
|
|
546
|
+
sql ??= sqlFor(sqlClaims, context, recorder, session);
|
|
358
547
|
return sql;
|
|
359
548
|
},
|
|
360
|
-
stats: () => recorder.snapshot()
|
|
549
|
+
stats: () => recorder.snapshot(),
|
|
550
|
+
apply: (response) => withPrimaryPin(resolution.apply(response), replica, pinMs)
|
|
361
551
|
};
|
|
362
552
|
};
|
|
363
|
-
const fromResolution = (resolution, request, contextOptions) => contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? (readUrl() ? pinnedUntil(resolution.requestCookies) : 0));
|
|
553
|
+
const fromResolution = (resolution, request, contextOptions, tenant) => contextFor(resolution, options.headers?.(request) ?? {}, contextOptions.stats, contextOptions.pinnedUntil ?? (readUrl() ? pinnedUntil(resolution.requestCookies) : 0), tenant, contextOptions.support);
|
|
364
554
|
const resolve = async (request, resolveOptions = {}) => {
|
|
365
555
|
const claims = options.auth?.claims ?? betterSupabase.claimsSchema;
|
|
366
556
|
const userMetadata = betterSupabase.userMetadataSchema;
|
|
@@ -371,6 +561,7 @@ function createServer(betterSupabase, options = {}) {
|
|
|
371
561
|
...userMetadata ? { userMetadata } : {},
|
|
372
562
|
env: env(),
|
|
373
563
|
...resolveOptions.refresh === void 0 ? {} : { refresh: resolveOptions.refresh },
|
|
564
|
+
...resolveOptions.cookies === void 0 ? {} : { cookies: resolveOptions.cookies },
|
|
374
565
|
onRefresh: (event) => {
|
|
375
566
|
options.auth?.onRefresh?.(event);
|
|
376
567
|
betterSupabase.events.emit("refresh", event);
|
|
@@ -381,12 +572,16 @@ function createServer(betterSupabase, options = {}) {
|
|
|
381
572
|
betterSupabase.events.emit("auth", {
|
|
382
573
|
source: auth.kind === "user" ? auth.source === "cookie" ? "cookie" : "bearer" : auth.kind === "service" ? "bearer" : "none",
|
|
383
574
|
ok: auth.kind !== "invalid",
|
|
384
|
-
...auth.kind === "user" ? {
|
|
575
|
+
...auth.kind === "user" ? {
|
|
576
|
+
userId: auth.user.id,
|
|
577
|
+
rawSource: auth.source
|
|
578
|
+
} : {},
|
|
579
|
+
...auth.kind === "anon" || auth.kind === "invalid" ? { reason: auth.reason } : {}
|
|
385
580
|
});
|
|
386
581
|
}
|
|
387
582
|
return resolution;
|
|
388
583
|
};
|
|
389
|
-
|
|
584
|
+
const server = {
|
|
390
585
|
get env() {
|
|
391
586
|
return env();
|
|
392
587
|
},
|
|
@@ -399,12 +594,19 @@ function createServer(betterSupabase, options = {}) {
|
|
|
399
594
|
headers: {},
|
|
400
595
|
requestCookies: [],
|
|
401
596
|
apply: (response) => response
|
|
402
|
-
}, {}, contextOptions.stats, contextOptions.pinnedUntil),
|
|
597
|
+
}, {}, contextOptions.stats, contextOptions.pinnedUntil, contextOptions.tenant, contextOptions.support),
|
|
403
598
|
async context(request, contextOptions = {}) {
|
|
404
|
-
const resolution = await resolve(request, {
|
|
405
|
-
|
|
599
|
+
const resolution = await resolve(request, {
|
|
600
|
+
refresh: contextOptions.refresh ?? false,
|
|
601
|
+
...contextOptions.cookies === void 0 ? {} : { cookies: contextOptions.cookies }
|
|
602
|
+
});
|
|
603
|
+
const [tenant, active] = await Promise.all([contextOptions.tenant ?? options.tenant?.(request, resolution.auth), contextOptions.support ?? support.current(request, resolution.auth)]);
|
|
604
|
+
return fromResolution(resolution, request, active ? {
|
|
605
|
+
...contextOptions,
|
|
606
|
+
support: active
|
|
607
|
+
} : contextOptions, tenant);
|
|
406
608
|
},
|
|
407
|
-
contextFromResolution: (resolution, request, contextOptions = {}) => fromResolution(resolution, request, contextOptions),
|
|
609
|
+
contextFromResolution: (resolution, request, contextOptions = {}) => fromResolution(resolution, request, contextOptions, contextOptions.tenant ?? syncTenant(request, resolution.auth)),
|
|
408
610
|
admin: (context = {}) => betterSupabase.connect(serviceClient(), {
|
|
409
611
|
actor: {
|
|
410
612
|
id: "service",
|
|
@@ -414,6 +616,7 @@ function createServer(betterSupabase, options = {}) {
|
|
|
414
616
|
claims: { role: "service_role" },
|
|
415
617
|
...context
|
|
416
618
|
}),
|
|
619
|
+
support,
|
|
417
620
|
deleteAccount: (userId, deleteOptions) => deleteAccount(betterSupabase, serviceClient, userId, deleteOptions),
|
|
418
621
|
actingAs: (userId, claims = {}, impersonation) => {
|
|
419
622
|
const full = {
|
|
@@ -433,6 +636,20 @@ function createServer(betterSupabase, options = {}) {
|
|
|
433
636
|
});
|
|
434
637
|
}
|
|
435
638
|
};
|
|
639
|
+
verifiedSeeders.set(server, (verified) => rememberVerified({
|
|
640
|
+
...options.auth,
|
|
641
|
+
env: env()
|
|
642
|
+
}, verified));
|
|
643
|
+
return server;
|
|
644
|
+
}
|
|
645
|
+
const verifiedSeeders = /* @__PURE__ */ new WeakMap();
|
|
646
|
+
/**
|
|
647
|
+
* Hands `server` a bearer token another layer verified for this request
|
|
648
|
+
* (`@supabase/server`'s `withSupabase`), so `context()` doesn't verify it
|
|
649
|
+
* again. See `rememberVerified` for when it is accepted.
|
|
650
|
+
*/
|
|
651
|
+
function rememberVerifiedFor(server, verified) {
|
|
652
|
+
return verifiedSeeders.get(server)?.(verified) ?? false;
|
|
436
653
|
}
|
|
437
654
|
//#endregion
|
|
438
|
-
export {
|
|
655
|
+
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 };
|