better-supabase 0.15.1 → 0.16.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/{ai-schedules-D_Hz-ik5.d.ts → ai-schedules-BmkARmQ9.d.ts} +1 -1
- package/dist/{api-DxAPqMrE.d.ts → api-hHwieEfz.d.ts} +1 -1
- package/dist/{app-tables-CbCFSXLG.d.ts → app-tables-yMQ4ubyY.d.ts} +1 -1
- package/dist/astro/index.d.ts +4 -4
- package/dist/{auth-module-BEFESDC4.js → auth-module-BbmX-N4L.js} +12 -4
- package/dist/better-result/index.d.ts +2 -2
- package/dist/{bind-h5jre2K0.d.ts → bind-JGCPGUJh.d.ts} +4 -4
- package/dist/{block-env-BrfRvPrj.d.ts → block-env-y65GF_px.d.ts} +4 -4
- package/dist/blocks/access-requests/index.d.ts +1 -1
- package/dist/blocks/activities/index.d.ts +1 -1
- package/dist/blocks/agents/index.d.ts +1 -1
- package/dist/blocks/ai-cache/index.d.ts +1 -1
- package/dist/blocks/ai-chat/index.d.ts +4 -4
- package/dist/blocks/ai-files/index.d.ts +1 -1
- package/dist/blocks/ai-keys/index.d.ts +1 -1
- package/dist/blocks/ai-schedules/index.d.ts +2 -2
- package/dist/blocks/announcements/index.d.ts +4 -4
- package/dist/blocks/api-keys/index.d.ts +2 -2
- package/dist/blocks/approvals/index.d.ts +1 -1
- package/dist/blocks/assets/index.d.ts +1 -1
- package/dist/blocks/assignment/index.d.ts +1 -1
- package/dist/blocks/attachments/index.d.ts +2 -2
- package/dist/blocks/audit/index.d.ts +5 -5
- package/dist/blocks/automations/index.d.ts +3 -3
- package/dist/blocks/billable-time/index.d.ts +1 -1
- package/dist/blocks/billing/index.d.ts +3 -3
- package/dist/blocks/bookings/index.d.ts +2 -2
- package/dist/blocks/bulk/index.d.ts +1 -1
- package/dist/blocks/calendar/index.d.ts +2 -2
- package/dist/blocks/catalog/index.d.ts +1 -1
- package/dist/blocks/channels/index.d.ts +1 -1
- package/dist/blocks/collections/index.d.ts +1 -1
- package/dist/blocks/comments/index.d.ts +4 -4
- package/dist/blocks/compliance/index.d.ts +1 -1
- package/dist/blocks/connectors/index.d.ts +1 -1
- package/dist/blocks/contacts/index.d.ts +1 -1
- package/dist/blocks/content/index.d.ts +1 -1
- package/dist/blocks/contracts/index.d.ts +1 -1
- package/dist/blocks/courses/index.d.ts +1 -1
- package/dist/blocks/created-by/index.d.ts +1 -1
- package/dist/blocks/custom-fields/index.d.ts +1 -1
- package/dist/blocks/data-lifecycle/index.d.ts +1 -1
- package/dist/blocks/deals/index.d.ts +1 -1
- package/dist/blocks/dispatch/index.d.ts +1 -1
- package/dist/blocks/documents/index.d.ts +1 -1
- package/dist/blocks/domains/index.d.ts +1 -1
- package/dist/blocks/donations/index.d.ts +1 -1
- package/dist/blocks/drive/index.d.ts +1 -1
- package/dist/blocks/employees/index.d.ts +2 -2
- package/dist/blocks/enrichment/index.d.ts +1 -1
- package/dist/blocks/enrollments/index.d.ts +1 -1
- package/dist/blocks/entitlements/index.d.ts +3 -3
- package/dist/blocks/expenses/index.d.ts +1 -1
- package/dist/blocks/field-history/index.d.ts +1 -1
- package/dist/blocks/flags/index.d.ts +1 -1
- package/dist/blocks/forecasts/index.d.ts +1 -1
- package/dist/blocks/forms/index.d.ts +2 -2
- package/dist/blocks/goals/index.d.ts +1 -1
- package/dist/blocks/guests/index.d.ts +1 -1
- package/dist/blocks/hiring/index.d.ts +1 -1
- package/dist/blocks/identities/index.d.ts +1 -1
- package/dist/blocks/imports/index.d.ts +1 -1
- package/dist/blocks/inbox/index.d.ts +5 -5
- package/dist/blocks/incidents/index.d.ts +1 -1
- package/dist/blocks/index.d.ts +2 -2
- package/dist/blocks/inventory/index.d.ts +1 -1
- package/dist/blocks/invoices/index.d.ts +1 -1
- package/dist/blocks/knowledge/index.d.ts +1 -1
- package/dist/blocks/lead-scoring/index.d.ts +1 -1
- package/dist/blocks/leases/index.d.ts +1 -1
- package/dist/blocks/leave/index.d.ts +1 -1
- package/dist/blocks/ledger/index.d.ts +1 -1
- package/dist/blocks/mail-sync/index.d.ts +1 -1
- package/dist/blocks/memberships/index.d.ts +1 -1
- package/dist/blocks/memory/index.d.ts +1 -1
- package/dist/blocks/messaging/index.d.ts +4 -4
- package/dist/blocks/metrics/index.d.ts +1 -1
- package/dist/blocks/mfa/index.d.ts +2 -2
- package/dist/blocks/moderation/index.d.ts +1 -1
- package/dist/blocks/notifications/index.d.ts +4 -4
- package/dist/blocks/numbering/index.d.ts +1 -1
- package/dist/blocks/oauth-server/index.d.ts +1 -1
- package/dist/blocks/onboarding/index.d.ts +2 -2
- package/dist/blocks/orders/index.d.ts +2 -2
- package/dist/blocks/organizations/index.d.ts +2 -2
- package/dist/blocks/outbox/index.d.ts +1 -1
- package/dist/blocks/passes/index.d.ts +1 -1
- package/dist/blocks/permissions/index.d.ts +3 -3
- package/dist/blocks/pipelines/index.d.ts +1 -1
- package/dist/blocks/posts/index.d.ts +1 -1
- package/dist/blocks/profiles/index.d.ts +1 -1
- package/dist/blocks/promotions/index.d.ts +1 -1
- package/dist/blocks/push/index.d.ts +2 -2
- package/dist/blocks/queue/index.d.ts +29 -2
- package/dist/blocks/queue/index.js +1 -1
- package/dist/blocks/quotes/index.d.ts +1 -1
- package/dist/blocks/rank/index.d.ts +1 -1
- package/dist/blocks/reactions/index.d.ts +4 -4
- package/dist/blocks/relations/index.d.ts +1 -1
- package/dist/blocks/reminders/index.d.ts +1 -1
- package/dist/blocks/reviews/index.d.ts +1 -1
- package/dist/blocks/saved-views/index.d.ts +3 -3
- package/dist/blocks/scores/index.d.ts +1 -1
- package/dist/blocks/search/index.d.ts +3 -3
- package/dist/blocks/security-events/index.d.ts +1 -1
- package/dist/blocks/segments/index.d.ts +1 -1
- package/dist/blocks/sequences/index.d.ts +1 -1
- package/dist/blocks/service-accounts/index.d.ts +1 -1
- package/dist/blocks/sessions/index.d.ts +2 -2
- package/dist/blocks/settings/index.d.ts +2 -2
- package/dist/blocks/share-links/index.d.ts +1 -1
- package/dist/blocks/shipments/index.d.ts +1 -1
- package/dist/blocks/signatures/index.d.ts +1 -1
- package/dist/blocks/signing-keys/index.d.ts +1 -1
- package/dist/blocks/sla/index.d.ts +1 -1
- package/dist/blocks/sso/index.d.ts +1 -1
- package/dist/blocks/state-machine/index.d.ts +1 -1
- package/dist/blocks/subjects/index.d.ts +2 -2
- package/dist/blocks/tags/index.d.ts +2 -2
- package/dist/blocks/tasks/index.d.ts +2 -2
- package/dist/blocks/teams/index.d.ts +1 -1
- package/dist/blocks/templates/index.d.ts +1 -1
- package/dist/blocks/time-tracking/index.d.ts +1 -1
- package/dist/blocks/timeline/index.d.ts +3 -3
- package/dist/blocks/trash/index.d.ts +1 -1
- package/dist/blocks/undo/index.d.ts +2 -2
- package/dist/blocks/updated-at/index.d.ts +1 -1
- package/dist/blocks/usage/index.d.ts +2 -2
- package/dist/blocks/vercel-marketplace/index.d.ts +1 -1
- package/dist/blocks/versions/index.d.ts +1 -1
- package/dist/blocks/waitlist/index.d.ts +1 -1
- package/dist/blocks/webhooks/index.d.ts +2 -2
- package/dist/blocks/webhooks/index.js +1 -1
- package/dist/blocks/workflow-editor/index.d.ts +4 -4
- package/dist/blocks/workflows/index.d.ts +4 -4
- package/dist/{bucket-CiNi8sIw.d.ts → bucket-vc-QRrva.d.ts} +40 -1
- package/dist/cli/add-Bfb_ISrO.js +1 -0
- package/dist/cli/{add-DNOL9PDu.js → add-D7TkPdco.js} +9 -9
- package/dist/cli/{add-C04vkBk9.js → add-I8OW-qJH.js} +2 -2
- package/dist/cli/{backfill-VFzf-TJP.js → backfill-DIz5DnlQ.js} +1 -1
- package/dist/cli/bin.js +1 -1
- package/dist/cli/{blocks-Bn7WUca5.js → blocks-Bp5RPoft.js} +1 -1
- package/dist/cli/{blocks-Cb7mILFU.js → blocks-xk-SE8gS.js} +1 -1
- package/dist/cli/{cache-DkplpIGe.js → cache-AznIssjI.js} +1 -1
- package/dist/cli/{codemod-BbaE5dL_.js → codemod-Aewxza6i.js} +1 -1
- package/dist/cli/{content-mapper-Cdx6arxu.js → content-mapper-DCrz6OIv.js} +2 -2
- package/dist/cli/{doctor-C-VO-QQ2.js → doctor-DkOSLPEQ.js} +13 -13
- package/dist/cli/{gen-yRHOHW1b.js → gen-CFkYUnWq.js} +10 -10
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/cli/{init-BOw0SX65.js → init-CHj9ad4S.js} +5 -5
- package/dist/cli/{live-Lmg-hYyO.js → live-DGdLR41j.js} +2 -2
- package/dist/cli/{mcp-DQ1zR2eA.js → mcp-BZno503V.js} +2 -2
- package/dist/cli/{metadata-CZaxkv21.js → metadata-BfgHMe4q.js} +3 -3
- package/dist/cli/{migration-safety-BbhLu2fo.js → migration-safety-BnnXPAro.js} +1 -1
- package/dist/cli/{module-migration-DOn6EmNz.js → module-migration-O_IxewFs.js} +1 -1
- package/dist/cli/{print-D_xf8PPj.js → print-DosQIFSy.js} +1 -1
- package/dist/cli/{remove-BY_TOTRj.js → remove-DK2G_KHm.js} +5 -5
- package/dist/cli/{render-lJmHr0y9.js → render-5y5a1QZr.js} +3 -3
- package/dist/cli/{run-BJ6IMs_H.js → run-Bs4OKdDI.js} +7 -7
- package/dist/cli/{run-DkB0fhc9.js → run-MRET30vh.js} +20 -20
- package/dist/cli/{shared-Bh-6IqJS.js → shared-BLqbvY2x.js} +1 -1
- package/dist/cli/{snapshot-Bzcigm7T.js → snapshot-uTUlEzY2.js} +2 -2
- package/dist/cli/{source-WoLdcQzN.js → source-D2gJHKwC.js} +1 -1
- package/dist/cli/{spec-CZMiLhv_.js → spec-Cm2einTO.js} +2 -2
- package/dist/cli/{spec-BF_dpwzW.js → spec-DUjOtciN.js} +1 -1
- package/dist/cli/{sql-layout-rFP0oq1N.js → sql-layout-lUWenjJw.js} +2 -2
- package/dist/cli/{sync-mANmYYoL.js → sync-CZMRB3aU.js} +2 -2
- package/dist/cli/{upgrade-B0Q9wMs-.js → upgrade-BFEIMC7U.js} +3 -3
- package/dist/cli/{upgrade-BrNLt92u.js → upgrade-fVsJniXm.js} +2 -2
- package/dist/cli/{version-CyavsyGA.js → version-B2Dr6VmJ.js} +1 -1
- package/dist/client/index.d.ts +5 -5
- package/dist/client/index.js +1 -1
- package/dist/client/native/index.d.ts +5 -5
- package/dist/client/native/index.js +1 -1
- package/dist/{client-BAEbEA7t.js → client-C_mlji5R.js} +6 -5
- package/dist/{composite-BZYXlVBN.d.ts → composite-CfYyU3vs.d.ts} +4 -3
- package/dist/config/index.d.ts +1 -1
- package/dist/{context-TGX2eDS5.d.ts → context-8kb8m5uO.d.ts} +1 -1
- package/dist/{controller-DyPPMFSw.d.ts → controller-BW-5Opig.d.ts} +1 -1
- package/dist/{conversion-O-kyhXcC.d.ts → conversion-3iv2r7YC.d.ts} +1 -1
- package/dist/create/index.d.ts +2 -2
- package/dist/create/index.js +1 -1
- package/dist/{define-DFBnQyYg.d.ts → define-C5AcEkEJ.d.ts} +1 -1
- package/dist/{define-block-DvvcM54N.d.ts → define-block-C1RHXz7L.d.ts} +2 -2
- package/dist/edge/index.d.ts +5 -5
- package/dist/{entitlements-BnhDBxLW.d.ts → entitlements-sO977DO-.d.ts} +1 -1
- package/dist/{executor-CVIn6JHa.d.ts → executor-BcNWcD-4.d.ts} +2 -2
- package/dist/expo/index.d.ts +2 -2
- package/dist/{field-errors-DYtg1mwv.d.ts → field-errors-Dt86dCKk.d.ts} +12 -12
- package/dist/{framework-h5lP_5yj.d.ts → framework-BGdZVyPL.d.ts} +3 -3
- package/dist/hono/index.d.ts +4 -4
- package/dist/{index-BAHGLvGD.d.ts → index-CENWq3zb.d.ts} +1 -1
- package/dist/{index-Do2ChN_y.d.ts → index-CQhPHHDF.d.ts} +20 -2
- package/dist/{index-DN9mfmhA.d.ts → index-CTu-3fJB.d.ts} +51 -3
- package/dist/{index-BfK6olKm.d.ts → index-Cc4mIQS8.d.ts} +2 -2
- package/dist/{index-BfkltrGJ.d.ts → index-CcAmFNVi.d.ts} +2 -2
- package/dist/{index-Ab4UeEn_.d.ts → index-Craf712U.d.ts} +44 -12
- package/dist/{index-B4gcStOz.d.ts → index-DZKCc2WM.d.ts} +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/list/index.d.ts +1 -1
- package/dist/{list-query-BDVutLFr.d.ts → list-query-DYhgRTd5.d.ts} +1 -1
- package/dist/mapper/bin.js +5 -5
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/sdk/index.d.ts +2 -2
- package/dist/next/client/index.d.ts +81 -1
- package/dist/next/client/index.js +178 -2
- package/dist/next/index.d.ts +15 -14
- package/dist/orpc/index.d.ts +4 -4
- package/dist/otel/index.js +4 -4
- package/dist/{ports-DOhtuw3c.js → ports-CaVRXjxq.js} +1 -1
- package/dist/{ports-CpdiQv1D.d.ts → ports-mObLgH1z.d.ts} +1 -1
- package/dist/powersync/index.d.ts +2 -2
- package/dist/preset/index.d.ts +3 -3
- package/dist/{push-token-B3m3pWAv.d.ts → push-token-BSSvsWXZ.d.ts} +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/{rate-limit-o44SYkVY.js → rate-limit-Dt9vAY4E.js} +53 -0
- package/dist/react/index.d.ts +6 -6
- package/dist/react/index.js +3 -3
- package/dist/react/native/index.d.ts +1 -1
- package/dist/react/native/index.js +1 -1
- package/dist/react/server.d.ts +12 -7
- package/dist/react/server.js +8 -3
- package/dist/react/session.d.ts +5 -0
- package/dist/react/session.js +9 -1
- package/dist/{react-BuDYI62C.js → react-CZUZYn9E.js} +41 -4
- package/dist/react-router/index.d.ts +3 -3
- package/dist/realtime/index.d.ts +5 -5
- package/dist/realtime/index.js +2 -1
- package/dist/{record-form-DB_5Ue_B.d.ts → record-form-D9ILuVtA.d.ts} +1 -1
- package/dist/records/index.d.ts +7 -7
- package/dist/relay-gKvKJ3xf.js +208 -0
- package/dist/{resource-execute-CzHuDsil.d.ts → resource-execute-Cpnob1Vj.d.ts} +1 -1
- package/dist/{resource-types-DnKiH4G2.d.ts → resource-types-pMeLs68X.d.ts} +1 -1
- package/dist/{resources-B9cQxhjw.d.ts → resources-7iq2-5_A.d.ts} +1 -1
- package/dist/server/index.d.ts +147 -9
- package/dist/server/index.js +396 -1
- package/dist/{server-types-7QPzW2bF.d.ts → server-types-CK8mUIUT.d.ts} +5 -354
- package/dist/{session-CfBCJGgc.d.ts → session-DWmFv3vB.d.ts} +2 -1
- package/dist/session-status-qQLm7jzh.d.ts +82 -0
- package/dist/{sign-in-BsIaNx2C.js → sign-in-CmPK1AgM.js} +20 -10
- package/dist/solid/index.d.ts +7 -7
- package/dist/solid/index.js +1 -1
- package/dist/solid-start/index.d.ts +3 -3
- package/dist/spec/index.d.ts +1 -1
- package/dist/sql/index.d.ts +3 -3
- package/dist/sql/index.js +1 -1
- package/dist/{sql-OLVW4t08.js → sql-Dl3Alk43.js} +127 -0
- package/dist/sqlite/index.d.ts +1 -1
- package/dist/ssr/index.d.ts +1 -1
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +54 -1
- package/dist/support-B4ZBVxt3.d.ts +357 -0
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +1 -1
- package/dist/sveltekit/index.d.ts +4 -4
- package/dist/{tags-BjHflcHF.d.ts → tags-qvVk79pp.d.ts} +1 -1
- package/dist/tanstack-db/index.d.ts +1 -1
- package/dist/tanstack-start/index.d.ts +3 -3
- package/dist/{tenant-DzFsg0Pc.d.ts → tenant-DOKIJI62.d.ts} +1 -1
- package/dist/testing/index.d.ts +8 -7
- package/dist/{timeline-WrFMgd3U.d.ts → timeline-CvTuXYvn.d.ts} +3 -3
- package/dist/{topic-DNFEFGKI.d.ts → topic-CZXHhZye.d.ts} +2 -2
- package/dist/{verify-CMou8dWZ.d.ts → verify-CjvNLY7N.d.ts} +1 -1
- package/dist/vue/index.d.ts +7 -7
- package/dist/vue/index.js +1 -1
- package/dist/{watch-topic-Bo9S2R4P.d.ts → watch-topic-BAxoPKZx.d.ts} +1 -1
- package/dist/yjs/index.d.ts +1 -1
- package/package.json +1 -1
- package/skills/better-supabase-api/SKILL.md +3 -1
- package/skills/better-supabase-auth/SKILL.md +7 -0
- package/dist/cli/add-CUH0rORo.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { n as dbError } from "../errors-BVFgt8BZ.js";
|
|
2
2
|
import { n as isRecord } from "../guards-6F2bCtQR.js";
|
|
3
3
|
import { t as base64ToText } from "../base64-B9Wlsdj7.js";
|
|
4
|
+
import { n as validate } from "../standard-BA3mLh6w.js";
|
|
4
5
|
import { c as toProblem, i as isProblem, n as PROBLEM_TYPE_BASE, o as problemResponse, r as fromProblem, t as PROBLEM_CONTENT_TYPE } from "../problem-Bf0cJ7tk.js";
|
|
5
6
|
import { i as delegationOf, n as actorOf, r as agentClaims, t as IDENTITY_SCOPES } from "../actor-go7p9-CJ.js";
|
|
6
7
|
import { i as userContext, r as impersonatorOf } from "../impersonation-DM3oWNoD.js";
|
|
7
8
|
import { t as supportOf } from "../support-view-g4YLBeqX.js";
|
|
9
|
+
import { t as joinTopic } from "../channels-CfcFPuo_.js";
|
|
8
10
|
import { r as toCsv, t as csvColumns } from "../csv-2rXGPNtP.js";
|
|
9
11
|
import { A as suspendAccount, C as shouldRefresh, D as SERVER_EVENT_SOURCE, E as defineAuthorizationHooks, O as endSessions, S as shouldCheckSession, T as serverCore, a as unexpectedResponse, b as isPrefetch, c as extendServer, d as clearSupportCookie, f as supportCookie, h as TENANT_HEADER, i as resolveToken, j as deleteAccount, k as lastSignIns, l as supportStorage, m as supportCookieValue, n as flushEvents, o as withFlush, r as handle, s as createServer, u as SUPPORT_COOKIE, x as refreshFor, y as withSession } from "../adapter-Dyks1lX1.js";
|
|
10
12
|
import { n as PRIMARY_COOKIE } from "../replicas-Dyoh3CEg.js";
|
|
@@ -57,6 +59,399 @@ const impersonationEvents = defineEvents({
|
|
|
57
59
|
"support.denied": event()
|
|
58
60
|
});
|
|
59
61
|
//#endregion
|
|
62
|
+
//#region src/server/session-status.ts
|
|
63
|
+
const VOLATILE_CLAIMS = [
|
|
64
|
+
"exp",
|
|
65
|
+
"iat",
|
|
66
|
+
"nbf",
|
|
67
|
+
"jti"
|
|
68
|
+
];
|
|
69
|
+
const HEADERS = {
|
|
70
|
+
"content-type": "application/json",
|
|
71
|
+
"cache-control": "no-store, private"
|
|
72
|
+
};
|
|
73
|
+
/** JSON with object keys sorted at every level, so equal claims hash alike. */
|
|
74
|
+
function canonical(value) {
|
|
75
|
+
if (value === void 0) return "null";
|
|
76
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
77
|
+
if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, each]) => each !== void 0).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, each]) => `${JSON.stringify(key)}:${canonical(each)}`).join(",")}}`;
|
|
78
|
+
return JSON.stringify(value);
|
|
79
|
+
}
|
|
80
|
+
async function sha256Hex(text) {
|
|
81
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
82
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The fingerprint `sessionStatusRoute` reports for a user's claims and support
|
|
86
|
+
* session: a SHA-256 hex digest that changes when a claim that decides access
|
|
87
|
+
* changes (role, tenant, memberships, `aal`) or the support target does.
|
|
88
|
+
*/
|
|
89
|
+
async function claimsFingerprint(claims, support, ignore = []) {
|
|
90
|
+
const skip = /* @__PURE__ */ new Set([...VOLATILE_CLAIMS, ...ignore]);
|
|
91
|
+
return sha256Hex(canonical({
|
|
92
|
+
claims: Object.fromEntries(Object.entries(claims).filter(([key]) => !skip.has(key))),
|
|
93
|
+
support: support ? {
|
|
94
|
+
session: support.session.id,
|
|
95
|
+
target: support.session.targetUserId,
|
|
96
|
+
readOnly: support.session.readOnly
|
|
97
|
+
} : null
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
const statusOf = (resolution) => {
|
|
101
|
+
if (resolution.sessionEnded) return resolution.sessionEndedReason ?? "ended";
|
|
102
|
+
const { auth } = resolution;
|
|
103
|
+
if (auth.kind === "user") return "active";
|
|
104
|
+
return auth.kind === "anon" && auth.reason === "expired" ? "expired" : "signed_out";
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* A `GET` handler that tells a browser whether its session still holds, for
|
|
108
|
+
* apps that keep the session in httpOnly cookies and have no browser Supabase
|
|
109
|
+
* client: `useSessionWatch()` from `better-supabase/next/client` polls it. It
|
|
110
|
+
* answers `{ status, userId, fingerprint, expiresAt, support }` with
|
|
111
|
+
* `Cache-Control: no-store`.
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* // app/api/session-status/route.ts
|
|
115
|
+
* export const GET = sessionStatusRoute(bsb);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
function sessionStatusRoute(server, options = {}) {
|
|
119
|
+
const checkSession = options.checkSession ?? true;
|
|
120
|
+
const refresh = options.refresh ?? false;
|
|
121
|
+
return async (request) => {
|
|
122
|
+
if (request.method !== "GET" && request.method !== "HEAD") return new Response(null, {
|
|
123
|
+
status: 405,
|
|
124
|
+
headers: { allow: "GET, HEAD" }
|
|
125
|
+
});
|
|
126
|
+
const resolution = await server.resolve(request, {
|
|
127
|
+
checkSession,
|
|
128
|
+
refresh
|
|
129
|
+
});
|
|
130
|
+
const status = statusOf(resolution);
|
|
131
|
+
const { auth } = resolution;
|
|
132
|
+
const user = auth.kind === "user" ? auth : void 0;
|
|
133
|
+
const support = user && status === "active" ? await server.support.current(request, user) : void 0;
|
|
134
|
+
const body = {
|
|
135
|
+
status,
|
|
136
|
+
userId: user?.user.id ?? null,
|
|
137
|
+
fingerprint: user && status === "active" ? await claimsFingerprint(user.claims, support, options.ignoreClaims) : null,
|
|
138
|
+
expiresAt: user?.expiresAt ?? null,
|
|
139
|
+
support: support ? {
|
|
140
|
+
sessionId: support.session.id,
|
|
141
|
+
targetUserId: support.session.targetUserId,
|
|
142
|
+
readOnly: support.session.readOnly
|
|
143
|
+
} : null
|
|
144
|
+
};
|
|
145
|
+
return resolution.apply(new Response(request.method === "HEAD" ? null : JSON.stringify(body), {
|
|
146
|
+
status: 200,
|
|
147
|
+
headers: HEADERS
|
|
148
|
+
}));
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/server/realtime-relay.ts
|
|
153
|
+
/** The channels `liveQuery` joins: `bsb:t:<schema>.<table>[:...]`, private. */
|
|
154
|
+
const liveRelayTopic = {
|
|
155
|
+
private: true,
|
|
156
|
+
match: (topic) => /^bsb:t:[^:]+\.[^:]+(?::.+)?$/.test(topic) ? {} : null
|
|
157
|
+
};
|
|
158
|
+
var Verdict = class extends Error {
|
|
159
|
+
allowed;
|
|
160
|
+
constructor(allowed) {
|
|
161
|
+
super(allowed ? "allowed" : "denied");
|
|
162
|
+
this.allowed = allowed;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const RLS_DENIED = "42501";
|
|
166
|
+
function codeOf(cause) {
|
|
167
|
+
return typeof cause === "object" && cause !== null && "code" in cause ? cause.code : void 0;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether `claims` may receive or send on `topic`, by the `realtime.messages`
|
|
171
|
+
* policies, checked the way Realtime checks a join: a probe message is
|
|
172
|
+
* inserted, then read (receive) or written (send) as the caller, and the
|
|
173
|
+
* transaction rolls back.
|
|
174
|
+
*/
|
|
175
|
+
async function realtimeTopicAccess(postgres, input) {
|
|
176
|
+
const role = input.claims["role"] === "anon" ? "anon" : "authenticated";
|
|
177
|
+
const sub = input.claims["sub"];
|
|
178
|
+
const extension = input.extension ?? "broadcast";
|
|
179
|
+
const insert = "insert into realtime.messages (topic, extension, event, payload, private) values ($1, $2, 'bsb_relay_probe', '{}'::jsonb, true)";
|
|
180
|
+
const assume = async (sql) => {
|
|
181
|
+
await sql.queryRaw("select set_config('request.jwt.claims', $1, true), set_config('request.jwt.claim.sub', $2, true), set_config('request.jwt.claim.role', $3, true), set_config('realtime.topic', $4, true), set_config('role', $3, true)", [
|
|
182
|
+
JSON.stringify({
|
|
183
|
+
...input.claims,
|
|
184
|
+
role
|
|
185
|
+
}),
|
|
186
|
+
typeof sub === "string" ? sub : "",
|
|
187
|
+
role,
|
|
188
|
+
input.topic
|
|
189
|
+
]);
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
await postgres.transaction(async (sql) => {
|
|
193
|
+
if (input.action === "receive") {
|
|
194
|
+
const [probe] = await sql.queryRaw(`${insert} returning id::text as id`, [input.topic, extension]);
|
|
195
|
+
await assume(sql);
|
|
196
|
+
const [row] = await sql.queryRaw("select exists (select 1 from realtime.messages m where m.id::text = $1) as allowed", [probe?.id ?? ""]);
|
|
197
|
+
throw new Verdict(row?.allowed === true);
|
|
198
|
+
}
|
|
199
|
+
await assume(sql);
|
|
200
|
+
try {
|
|
201
|
+
await sql.queryRaw(insert, [input.topic, extension]);
|
|
202
|
+
} catch (cause) {
|
|
203
|
+
if (codeOf(cause) === RLS_DENIED) throw new Verdict(false);
|
|
204
|
+
throw cause;
|
|
205
|
+
}
|
|
206
|
+
throw new Verdict(true);
|
|
207
|
+
});
|
|
208
|
+
} catch (cause) {
|
|
209
|
+
if (cause instanceof Verdict) return cause.allowed;
|
|
210
|
+
throw cause;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const encoder = new TextEncoder();
|
|
215
|
+
const frame = (event, data) => encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
216
|
+
const PING = encoder.encode(": ping\n\n");
|
|
217
|
+
const STREAM_HEADERS = {
|
|
218
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
219
|
+
"cache-control": "no-store, no-transform",
|
|
220
|
+
"x-accel-buffering": "no"
|
|
221
|
+
};
|
|
222
|
+
function readOnlyError() {
|
|
223
|
+
return {
|
|
224
|
+
...dbError("forbidden", "The support session is read-only"),
|
|
225
|
+
code: "SUPPORT_READ_ONLY"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function isSendBody(value) {
|
|
229
|
+
if (typeof value !== "object" || value === null) return false;
|
|
230
|
+
const body = value;
|
|
231
|
+
return typeof body.topic === "string" && typeof body.event === "string" && body.event !== "";
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Realtime for callers without a browser token of their own: a support session
|
|
235
|
+
* ("view as user"), or an app that keeps sessions in httpOnly cookies. `GET
|
|
236
|
+
* ?topic=<name>` resolves the caller, checks the topic's access as that caller
|
|
237
|
+
* (the viewed user in a support session), joins upstream with the secret key
|
|
238
|
+
* and streams the topic's broadcasts and presence as server-sent events. `POST
|
|
239
|
+
* { topic, event, payload }` broadcasts for the caller after the same check on
|
|
240
|
+
* the send policy; read-only support sessions can't send.
|
|
241
|
+
* `relayRealtimeClient(url)` from `better-supabase/realtime` is the client.
|
|
242
|
+
*
|
|
243
|
+
* ```ts
|
|
244
|
+
* // app/api/realtime/route.ts
|
|
245
|
+
* export const { GET, POST } = realtimeRelay(bsb, {
|
|
246
|
+
* topics: [notifications, liveRelayTopic],
|
|
247
|
+
* postgres,
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
function realtimeRelay(server, options) {
|
|
252
|
+
const { postgres, authorize } = options;
|
|
253
|
+
if (!postgres && !authorize) throw new TypeError("realtimeRelay needs `postgres` to check the realtime.messages policies, or `authorize`");
|
|
254
|
+
const heartbeatMs = options.heartbeatMs ?? 25e3;
|
|
255
|
+
const maxDurationMs = options.maxDurationMs ?? 24e4;
|
|
256
|
+
const maxQueued = Math.max(1, options.maxQueued ?? 256);
|
|
257
|
+
const retryMs = options.retryMs ?? 1e3;
|
|
258
|
+
const refresh = options.refresh ?? false;
|
|
259
|
+
let upstream = options.upstream;
|
|
260
|
+
const upstreamClient = () => {
|
|
261
|
+
upstream ??= server.admin().$client;
|
|
262
|
+
return upstream;
|
|
263
|
+
};
|
|
264
|
+
const problem = (error) => problemResponse(error, { format: options.problem });
|
|
265
|
+
const topicOf = (name) => options.topics.find((each) => each.match(name) !== null);
|
|
266
|
+
const allowed = async (input) => {
|
|
267
|
+
try {
|
|
268
|
+
if (authorize) return await authorize(input);
|
|
269
|
+
if (input.auth.kind === "service") return true;
|
|
270
|
+
return postgres ? await realtimeTopicAccess(postgres, {
|
|
271
|
+
claims: input.claims,
|
|
272
|
+
topic: input.topic,
|
|
273
|
+
action: input.action
|
|
274
|
+
}) : false;
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const claimsOf = (auth, support) => {
|
|
280
|
+
if (support) return support.claims;
|
|
281
|
+
switch (auth.kind) {
|
|
282
|
+
case "user": return auth.claims;
|
|
283
|
+
case "apiKey": return auth.claims;
|
|
284
|
+
case "anon": return { role: "anon" };
|
|
285
|
+
case "service": return { role: "service_role" };
|
|
286
|
+
case "invalid": return { role: "anon" };
|
|
287
|
+
default: return auth;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
/** Resolves the caller and checks `action` on `name`; a Response refuses. */
|
|
291
|
+
const admit = async (request, name, action) => {
|
|
292
|
+
const topic = topicOf(name);
|
|
293
|
+
if (!topic) return { refusal: problem(dbError("not_found", `No relayed topic "${name}"`)) };
|
|
294
|
+
const ctx = await server.context(request, { refresh });
|
|
295
|
+
const { auth, support } = ctx;
|
|
296
|
+
if (auth.kind === "invalid") return { refusal: ctx.apply(problem(auth.error)) };
|
|
297
|
+
if (!(auth.kind !== "anon") && (topic.private || action === "send")) return { refusal: ctx.apply(problem(dbError("unauthorized", "Sign in to join this topic"))) };
|
|
298
|
+
if (action === "send" && support?.session.readOnly === true) return { refusal: ctx.apply(problem(readOnlyError())) };
|
|
299
|
+
if (!(!topic.private && action === "receive" ? true : await allowed({
|
|
300
|
+
topic: name,
|
|
301
|
+
action,
|
|
302
|
+
auth,
|
|
303
|
+
support,
|
|
304
|
+
claims: claimsOf(auth, support)
|
|
305
|
+
}))) return { refusal: ctx.apply(problem(dbError("forbidden", `Not allowed to ${action} on "${name}"`))) };
|
|
306
|
+
return {
|
|
307
|
+
topic,
|
|
308
|
+
ctx
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
const expiryOf = (auth, support) => {
|
|
312
|
+
const times = [];
|
|
313
|
+
if (auth.kind === "user" && auth.expiresAt !== null) times.push(auth.expiresAt * 1e3);
|
|
314
|
+
if (support) times.push(support.session.expiresAt.epochMilliseconds);
|
|
315
|
+
return times.length === 0 ? void 0 : Math.min(...times);
|
|
316
|
+
};
|
|
317
|
+
const GET = async (request) => {
|
|
318
|
+
const name = new URL(request.url).searchParams.get("topic");
|
|
319
|
+
if (!name) return problem(dbError("invalid_request", "Pass ?topic=<name>"));
|
|
320
|
+
const admitted = await admit(request, name, "receive");
|
|
321
|
+
if ("refusal" in admitted) return admitted.refusal;
|
|
322
|
+
const { topic, ctx } = admitted;
|
|
323
|
+
const expiresAt = expiryOf(ctx.resolution.auth, ctx.support);
|
|
324
|
+
const client = upstreamClient();
|
|
325
|
+
let finish = () => void 0;
|
|
326
|
+
const stream = new ReadableStream({
|
|
327
|
+
start(controller) {
|
|
328
|
+
let closed = false;
|
|
329
|
+
let subscribed = false;
|
|
330
|
+
let leave;
|
|
331
|
+
const timers = [];
|
|
332
|
+
const enqueue = (chunk) => {
|
|
333
|
+
try {
|
|
334
|
+
controller.enqueue(chunk);
|
|
335
|
+
} catch {
|
|
336
|
+
finish(null);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const push = (chunk) => {
|
|
340
|
+
if (closed) return;
|
|
341
|
+
if ((controller.desiredSize ?? 1) <= 0) {
|
|
342
|
+
finish("overflow");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
enqueue(chunk);
|
|
346
|
+
};
|
|
347
|
+
finish = (reason) => {
|
|
348
|
+
if (closed) return;
|
|
349
|
+
closed = true;
|
|
350
|
+
for (const timer of timers) {
|
|
351
|
+
clearTimeout(timer);
|
|
352
|
+
clearInterval(timer);
|
|
353
|
+
}
|
|
354
|
+
request.signal.removeEventListener("abort", aborted);
|
|
355
|
+
leave?.();
|
|
356
|
+
if (reason !== null) enqueue(frame("end", { reason }));
|
|
357
|
+
try {
|
|
358
|
+
controller.close();
|
|
359
|
+
} catch {}
|
|
360
|
+
};
|
|
361
|
+
const aborted = () => {
|
|
362
|
+
finish(null);
|
|
363
|
+
};
|
|
364
|
+
const onStatus = (status) => {
|
|
365
|
+
switch (status) {
|
|
366
|
+
case "joining": return;
|
|
367
|
+
case "subscribed":
|
|
368
|
+
push(frame("status", {
|
|
369
|
+
status,
|
|
370
|
+
rejoin: subscribed
|
|
371
|
+
}));
|
|
372
|
+
subscribed = true;
|
|
373
|
+
return;
|
|
374
|
+
case "error":
|
|
375
|
+
if (subscribed) push(frame("status", { status }));
|
|
376
|
+
else finish("upstream_error");
|
|
377
|
+
return;
|
|
378
|
+
case "closed":
|
|
379
|
+
finish("closed");
|
|
380
|
+
return;
|
|
381
|
+
default: finish(status);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const subscriber = {
|
|
385
|
+
onStatus,
|
|
386
|
+
receive: (message) => {
|
|
387
|
+
push(frame("broadcast", {
|
|
388
|
+
event: message.event,
|
|
389
|
+
payload: message.payload
|
|
390
|
+
}));
|
|
391
|
+
},
|
|
392
|
+
...topic.presence ? { sync: (state) => {
|
|
393
|
+
push(frame("presence", state));
|
|
394
|
+
} } : {}
|
|
395
|
+
};
|
|
396
|
+
enqueue(encoder.encode(`retry: ${String(retryMs)}\n\n`));
|
|
397
|
+
if (request.signal.aborted) {
|
|
398
|
+
finish(null);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
request.signal.addEventListener("abort", aborted);
|
|
402
|
+
timers.push(setInterval(() => {
|
|
403
|
+
push(PING);
|
|
404
|
+
}, heartbeatMs), setTimeout(() => {
|
|
405
|
+
finish("max_duration");
|
|
406
|
+
}, maxDurationMs));
|
|
407
|
+
if (expiresAt !== void 0) timers.push(setTimeout(() => {
|
|
408
|
+
finish("expired");
|
|
409
|
+
}, Math.max(0, expiresAt - Date.now())));
|
|
410
|
+
try {
|
|
411
|
+
leave = joinTopic(client, name, topic.private, false, topic.presence === true, subscriber).leave;
|
|
412
|
+
} catch {
|
|
413
|
+
finish("upstream_error");
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
cancel() {
|
|
417
|
+
finish(null);
|
|
418
|
+
}
|
|
419
|
+
}, new CountQueuingStrategy({ highWaterMark: maxQueued }));
|
|
420
|
+
return ctx.apply(new Response(stream, {
|
|
421
|
+
status: 200,
|
|
422
|
+
headers: STREAM_HEADERS
|
|
423
|
+
}));
|
|
424
|
+
};
|
|
425
|
+
const POST = async (request) => {
|
|
426
|
+
const body = await request.json().catch(() => void 0);
|
|
427
|
+
if (!isSendBody(body)) return problem(dbError("invalid_request", "Send a JSON body with `topic`, `event` and `payload`"));
|
|
428
|
+
const known = topicOf(body.topic);
|
|
429
|
+
if (known && !known.clientSend) return problem(dbError("forbidden", `"${body.topic}" takes no client broadcasts`));
|
|
430
|
+
const admitted = await admit(request, body.topic, "send");
|
|
431
|
+
if ("refusal" in admitted) return admitted.refusal;
|
|
432
|
+
const { topic, ctx } = admitted;
|
|
433
|
+
const schema = topic.events?.[body.event];
|
|
434
|
+
const checked = schema ? await validate(schema, body.payload, "payload") : {
|
|
435
|
+
ok: true,
|
|
436
|
+
data: body.payload
|
|
437
|
+
};
|
|
438
|
+
if (!checked.ok) return ctx.apply(problem(checked.error));
|
|
439
|
+
const client = upstreamClient();
|
|
440
|
+
const open = client.getChannels().some((existing) => existing.topic === `realtime:${body.topic}`);
|
|
441
|
+
const channel = client.channel(body.topic, { config: { private: topic.private } });
|
|
442
|
+
try {
|
|
443
|
+
const sent = await channel.httpSend(body.event, checked.data);
|
|
444
|
+
return ctx.apply(sent.success ? new Response(null, { status: 202 }) : problem(dbError("network", sent.error)));
|
|
445
|
+
} finally {
|
|
446
|
+
if (!open) await client.removeChannel(channel);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
return {
|
|
450
|
+
GET,
|
|
451
|
+
POST
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
//#endregion
|
|
60
455
|
//#region src/server/entries/upstream.ts
|
|
61
456
|
/** Repository context from a `@supabase/server` context. */
|
|
62
457
|
function contextFromSupabase(ctx) {
|
|
@@ -452,4 +847,4 @@ function subscribeAuthzVersions(options) {
|
|
|
452
847
|
return subscription;
|
|
453
848
|
}
|
|
454
849
|
//#endregion
|
|
455
|
-
export { AUTH_CACHE_HEADERS, DB_CALLS_HEADER, IDENTITY_SCOPES, PERMISSION_CAP_CLAIM, PRIMARY_COOKIE, PROBLEM_CONTENT_TYPE, PROBLEM_TYPE_BASE, SERVER_EVENT_SOURCE, SUPPORT_COOKIE, TENANT_HEADER, aalOf, accessTokenResolver, actorOf, agentClaims, amrOf, authContext, authModeOf, authzVersionOf, bindAuthorizer, cacheTagsOf, checkAal, checkSession, clearSessionCookies, clearSupportCookie, clientIp, contextFromSupabase, createServer, csvColumns, csvResponse, defineAuthorizationHooks, defineAuthorizer, defineResource, delegationOf, deleteAccount, endSessions, extendServer, flushEvents, fromProblem, guard, handle, hasRole, impersonationEvents, impersonatorOf, isPrefetch, isProblem, isRateLimitPolicyName, isResult, isSessionSnapshot, issuedAtOf, jwtClaimsOf, lastSignIns, parseDbStats, passwordGrant, periodSeconds, problemResponse, rateLimitHeader, rateLimitPolicyHeader, readSession, refreshFor, refreshSession, requestMemo, resolveAuth, resolveToken, respond, rolesAt, safeRedirectPath, serverCore, sessionCacheKey, sessionCookieName, sessionMemo, sessionStatus, settle, shouldCheckSession, shouldRefresh, snapshotOf, sqlSupportStore, subjectReaders, subscribeAuthzVersions, supportClaims, supportCookie, supportCookieValue, supportOf, supportSessions, supportStorage, suspendAccount, tagCache, tagFor, toCsv, toProblem, toSession, unexpectedResponse, userClaimsOf, withBetterDb, withBetterPostgres, withBetterSupabase, withBlock, withDbStats, withFlush, withGuard, withServerTiming, withSession, writeSession };
|
|
850
|
+
export { AUTH_CACHE_HEADERS, DB_CALLS_HEADER, IDENTITY_SCOPES, PERMISSION_CAP_CLAIM, PRIMARY_COOKIE, PROBLEM_CONTENT_TYPE, PROBLEM_TYPE_BASE, SERVER_EVENT_SOURCE, SUPPORT_COOKIE, TENANT_HEADER, aalOf, accessTokenResolver, actorOf, agentClaims, amrOf, authContext, authModeOf, authzVersionOf, bindAuthorizer, cacheTagsOf, checkAal, checkSession, claimsFingerprint, clearSessionCookies, clearSupportCookie, clientIp, contextFromSupabase, createServer, csvColumns, csvResponse, defineAuthorizationHooks, defineAuthorizer, defineResource, delegationOf, deleteAccount, endSessions, extendServer, flushEvents, fromProblem, guard, handle, hasRole, impersonationEvents, impersonatorOf, isPrefetch, isProblem, isRateLimitPolicyName, isResult, isSessionSnapshot, issuedAtOf, jwtClaimsOf, lastSignIns, liveRelayTopic, parseDbStats, passwordGrant, periodSeconds, problemResponse, rateLimitHeader, rateLimitPolicyHeader, readSession, realtimeRelay, realtimeTopicAccess, refreshFor, refreshSession, requestMemo, resolveAuth, resolveToken, respond, rolesAt, safeRedirectPath, serverCore, sessionCacheKey, sessionCookieName, sessionMemo, sessionStatus, sessionStatusRoute, settle, shouldCheckSession, shouldRefresh, snapshotOf, sqlSupportStore, subjectReaders, subscribeAuthzVersions, supportClaims, supportCookie, supportCookieValue, supportOf, supportSessions, supportStorage, suspendAccount, tagCache, tagFor, toCsv, toProblem, toSession, unexpectedResponse, userClaimsOf, withBetterDb, withBetterPostgres, withBetterSupabase, withBlock, withDbStats, withFlush, withGuard, withServerTiming, withSession, writeSession };
|