permdock 0.0.0 → 0.2.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 +327 -1
- package/dist/a2a/index.d.ts +169 -0
- package/dist/a2a/index.js +232 -0
- package/dist/adapter-context-mlWsVGGg.js +23 -0
- package/dist/ai-sdk/index.d.ts +95 -0
- package/dist/ai-sdk/index.js +154 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +5 -0
- package/dist/approvals-DbYAuNkG.js +224 -0
- package/dist/approvers-DT4AVyIg.js +52 -0
- package/dist/arazzo-BAgw9ap6.js +70 -0
- package/dist/arazzo-cmvX2jDe.js +54 -0
- package/dist/authzen/index.d.ts +39 -0
- package/dist/authzen/index.js +302 -0
- package/dist/better-auth/index.d.ts +109 -0
- package/dist/better-auth/index.js +344 -0
- package/dist/better-supabase/index.d.ts +206 -0
- package/dist/better-supabase/index.js +620 -0
- package/dist/bind-CVy3Espv.d.ts +25 -0
- package/dist/bind-VnYn2HKq.js +97 -0
- package/dist/boundary-BM8a_P6E.js +13 -0
- package/dist/boundary-BmHxVH1J.d.ts +15 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/canonical-json-DemiK4Vp.js +26 -0
- package/dist/capability-4qPdO9gk.js +173 -0
- package/dist/capability-Dq43tT1u.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-BjZSovXQ.js +2 -0
- package/dist/catalog-doc-Dsc9y7sd.js +309 -0
- package/dist/catalog-h2tlKFkq.js +48 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/catalog-z8q6bi_r.js +55 -0
- package/dist/claude-agent/index.d.ts +72 -0
- package/dist/claude-agent/index.js +77 -0
- package/dist/clerk/index.d.ts +65 -0
- package/dist/clerk/index.js +301 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.js +5 -0
- package/dist/clients-C7zLFuWT.d.ts +12 -0
- package/dist/clients-DjU5ABMy.js +23 -0
- package/dist/cloud/index.d.ts +90 -0
- package/dist/cloud/index.js +404 -0
- package/dist/cloud-BGhHS-lY.js +50 -0
- package/dist/cloud-Bo3e9Ak3.js +118 -0
- package/dist/collect-DDDv_ESs.js +502 -0
- package/dist/collect-DLlujdHC.js +83 -0
- package/dist/collect-rG2IQ3t0.js +2 -0
- package/dist/commands-DWOrSQic.js +97 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile/index.d.ts +3 -0
- package/dist/compile/index.js +2 -0
- package/dist/compile-Ba4blROR.js +613 -0
- package/dist/compile-CNnl6uIt.d.ts +94 -0
- package/dist/config-BJiM-xAm.js +157 -0
- package/dist/config-Bn00Z3ks.js +36 -0
- package/dist/config-report-Dw_JcUqT.js +44 -0
- package/dist/connection-CU8MQxRi.js +194 -0
- package/dist/context-Ttp0ekMT.js +82 -0
- package/dist/convex/index.d.ts +45 -0
- package/dist/convex/index.js +65 -0
- package/dist/create-BWyx3jua.d.ts +245 -0
- package/dist/create-CPhDL6qx.js +253 -0
- package/dist/credential-8P81ff4u.js +306 -0
- package/dist/delegation-D8pUEuce.js +103 -0
- package/dist/describe-B_gVZEVi.js +94 -0
- package/dist/describe-PdoqESvg.d.ts +36 -0
- package/dist/diff-B8Tunc3o.js +451 -0
- package/dist/diff-CzqLxZ77.js +48 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-WpvG6p4k.js +1194 -0
- package/dist/doctor-collect-ByfYlNv1.js +664 -0
- package/dist/doctor-rOr-4ZfS.js +53 -0
- package/dist/drizzle/index.d.ts +73 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +49 -0
- package/dist/elysia/index.js +59 -0
- package/dist/errors-CWfoNg9N.js +47 -0
- package/dist/errors-Cz5zn0mW.js +123 -0
- package/dist/errors-DAqevpNk.d.ts +125 -0
- package/dist/errors-DRnGZHzF.js +30 -0
- package/dist/evaluation-items-CfCCejQd.js +40 -0
- package/dist/evaluations-tz6D9A2G.js +461 -0
- package/dist/eve/index.d.ts +86 -0
- package/dist/eve/index.js +176 -0
- package/dist/expiry-BoBJ8Tvc.js +7 -0
- package/dist/express/index.d.ts +23 -0
- package/dist/express/index.js +78 -0
- package/dist/fastify/index.d.ts +33 -0
- package/dist/fastify/index.js +92 -0
- package/dist/fields-BMl1RxoX.js +26 -0
- package/dist/files-DWUN9PzI.js +125 -0
- package/dist/fixtures-ht28Cplr.js +67 -0
- package/dist/freeze-CrU8nQeX.js +60 -0
- package/dist/from-snapshot-C3sHpZNS.js +569 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Cv5Sogfv.js +602 -0
- package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
- package/dist/headers-BZSDieNT.js +6 -0
- package/dist/headers-Cf-zWvsO.d.ts +6 -0
- package/dist/helpers-H_KARDVC.js +421 -0
- package/dist/hono/index.d.ts +48 -0
- package/dist/hono/index.js +99 -0
- package/dist/hosted-DJQw46hn.js +266 -0
- package/dist/http-BIhmLfCc.js +95 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-SO7qGttf.js +44 -0
- package/dist/ids-DC4V4CFu.js +18 -0
- package/dist/index-A0YNL8jZ.d.ts +177 -0
- package/dist/index-CtE07CBF.d.ts +6 -0
- package/dist/index-D3vbkoSI.d.ts +483 -0
- package/dist/index-D6_cSa1z.d.ts +201 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +26 -0
- package/dist/instance-options-BK_rNOSC.d.ts +11 -0
- package/dist/instance-options-Dhhcnm3y.js +22 -0
- package/dist/json-schema-BsAflUGW.js +106 -0
- package/dist/jwt/index.d.ts +91 -0
- package/dist/jwt/index.js +590 -0
- package/dist/kernel-GSQnMz3O.js +208 -0
- package/dist/kysely/index.d.ts +78 -0
- package/dist/kysely/index.js +129 -0
- package/dist/live-options-H2eG8dfJ.js +60 -0
- package/dist/load-DhWEB9P1.js +75 -0
- package/dist/load-DwJUafYN.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-iaoBXHKc.js +783 -0
- package/dist/manifest-uDS9y5de.d.ts +206 -0
- package/dist/map-AcxK9o_D.js +147 -0
- package/dist/map-claims-Z4QZOiFF.js +369 -0
- package/dist/map-error-pgUWNNtG.js +23 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-DndQ5tQ-.js +77 -0
- package/dist/mcp/index.d.ts +159 -0
- package/dist/mcp/index.js +603 -0
- package/dist/nest/index.d.ts +75 -0
- package/dist/nest/index.js +261 -0
- package/dist/next/client.d.ts +23 -0
- package/dist/next/client.js +33 -0
- package/dist/next/index.d.ts +93 -0
- package/dist/next/index.js +180 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +33 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +34 -0
- package/dist/openai/index.d.ts +62 -0
- package/dist/openai/index.js +92 -0
- package/dist/openapi/index.d.ts +176 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-Bbg4wGVL.js +164 -0
- package/dist/openapi-BqRBvAng.js +538 -0
- package/dist/openapi-CqqEbHsL.js +250 -0
- package/dist/openapi-import-cmwetbzw.js +5959 -0
- package/dist/openapi-schema--ARjyfQr.js +6902 -0
- package/dist/orpc/index.d.ts +51 -0
- package/dist/orpc/index.js +145 -0
- package/dist/otel/index.d.ts +13 -0
- package/dist/otel/index.js +202 -0
- package/dist/package-root-zGkxO3oN.js +30 -0
- package/dist/parse-BuyjxhZV.js +53 -0
- package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
- package/dist/parse-local-manifest-BrMozJCo.js +36 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +682 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-CzqXjCpU.js +3471 -0
- package/dist/permissions-CDgvKG5L.d.ts +52 -0
- package/dist/permissions-CpZrivYv.js +142 -0
- package/dist/permissions-DVoF6dMt.js +662 -0
- package/dist/permissions-DttnqqxC.d.ts +214 -0
- package/dist/pins-8ILTftC9.js +12 -0
- package/dist/policy-CFZAr_Al.d.ts +2252 -0
- package/dist/policy-D8UFyERw.d.ts +20 -0
- package/dist/policy-xyWg7QdR.js +1122 -0
- package/dist/powersync-A8x_Yyvh.js +2 -0
- package/dist/powersync-C7kLglqX.js +540 -0
- package/dist/powersync-yW1UUFzp.js +62 -0
- package/dist/prisma/index.d.ts +136 -0
- package/dist/prisma/index.js +455 -0
- package/dist/problem-B43ztKIm.js +191 -0
- package/dist/problem-details-CkL50EEf.d.ts +7 -0
- package/dist/problem-details-_AMOYz-w.js +144 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-CNsMzrVd.d.ts +27 -0
- package/dist/protected-Cs6kTaHo.js +100 -0
- package/dist/protected-bZs6rbT1.d.ts +40 -0
- package/dist/provider-Cjr62YMV.d.ts +6 -0
- package/dist/provider-DLzpb2Xz.js +52 -0
- package/dist/provider-store-DNyC1y48.js +43 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +7 -0
- package/dist/react/provider-client.d.ts +2 -0
- package/dist/react/provider-client.js +3 -0
- package/dist/react-native/index.d.ts +198 -0
- package/dist/react-native/index.js +696 -0
- package/dist/refs-KYb4ntaz.js +91 -0
- package/dist/relations-cjOL01hZ.js +505 -0
- package/dist/replay-BsOyTrdV.js +28 -0
- package/dist/replay-sqBGMnNs.d.ts +5 -0
- package/dist/resource-ref-CaS9yVPi.js +20 -0
- package/dist/revocations-Bhr0aMIC.js +27 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BMNydpHk.js +2521 -0
- package/dist/rls-CEvs64Kj.js +238 -0
- package/dist/rls-fields-BMasREhR.js +3502 -0
- package/dist/rls-graph-pc9fuXYm.js +435 -0
- package/dist/rls-import-D8F7j35Q.js +943 -0
- package/dist/rls-migrate-7wikfrVI.js +367 -0
- package/dist/rls-ownership-B5Lkq62W.js +843 -0
- package/dist/rls-shared-aOQ4hGTO.js +493 -0
- package/dist/rls-verify-D8B7dOTh.js +745 -0
- package/dist/row-check-Dt35czLy.d.ts +40 -0
- package/dist/rpc-DOK54_CS.js +89 -0
- package/dist/run-YXCJDNYk.js +174 -0
- package/dist/runtime-CB8gQFvS.js +11 -0
- package/dist/saas-Bj_FFkjN.js +1203 -0
- package/dist/schema-D3MnDMxa.js +300 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-BFGBBAqR.js +1445 -0
- package/dist/scopes-CVdPrWN-.js +239 -0
- package/dist/server/index.d.ts +134 -0
- package/dist/server/index.js +11 -0
- package/dist/server-DlZ28Oz5.js +268 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/shared-DRu8VFVM.js +123 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Gs6TTVa-.d.ts +104 -0
- package/dist/skills-Biq5uup3.js +192 -0
- package/dist/skills-JaymNCdK.js +56 -0
- package/dist/snapshot-Enxd0n-s.js +975 -0
- package/dist/snapshot-cache-D6jLelln.js +44 -0
- package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
- package/dist/solid/index.d.ts +93 -0
- package/dist/solid/index.js +188 -0
- package/dist/sources-B0UvrJ21.js +534 -0
- package/dist/sources-BcJgw0lt.d.ts +192 -0
- package/dist/sql-xdg5f4Fi.js +27 -0
- package/dist/src-CSbUiozF.js +568 -0
- package/dist/ssf/index.d.ts +4 -0
- package/dist/ssf/index.js +596 -0
- package/dist/store-DMAWwAJV.js +371 -0
- package/dist/store-D_pjn91-.d.ts +36 -0
- package/dist/store-options-yO0V6gRn.js +716 -0
- package/dist/stores-4_sqiOA1.js +87 -0
- package/dist/stores-DVR5NHLE.d.ts +52 -0
- package/dist/stream-DDpvynSB.js +66 -0
- package/dist/stream-DlvG2hs-.d.ts +12 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-BwBmCdTR.js +128 -0
- package/dist/supabase/index.d.ts +183 -0
- package/dist/supabase/index.js +5 -0
- package/dist/supabase/middleware.d.ts +82 -0
- package/dist/supabase/middleware.js +102 -0
- package/dist/supabase-CwafwxxF.js +83 -0
- package/dist/supabase-DTBaC20O.js +774 -0
- package/dist/supabase-config-Bs44dare.js +472 -0
- package/dist/supabase-hook-BuxLQLpU.js +1814 -0
- package/dist/supabase-hook-D5qVd_D8.js +2 -0
- package/dist/supautils-DYZnzRWV.js +91 -0
- package/dist/svelte/PermissionBoundary.svelte +37 -0
- package/dist/svelte/Protected.svelte +40 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.js +107 -0
- package/dist/svelte/runtime.d.ts +7 -0
- package/dist/svelte/runtime.js +4 -0
- package/dist/svelte/source.d.ts +13 -0
- package/dist/svelte/source.js +11 -0
- package/dist/terminal/index.d.ts +163 -0
- package/dist/terminal/index.js +833 -0
- package/dist/testing/index.d.ts +745 -0
- package/dist/testing/index.js +3672 -0
- package/dist/testing/saas/index.d.ts +194 -0
- package/dist/testing/saas/index.js +3 -0
- package/dist/testing/saas/permissions.d.ts +2 -0
- package/dist/testing/saas/permissions.js +2 -0
- package/dist/text-diff-BdkkYSbi.js +15 -0
- package/dist/thenable-okc2xDsj.js +10 -0
- package/dist/timeout-D51niuQP.js +8 -0
- package/dist/token-CIWxJOCh.js +50 -0
- package/dist/trpc/index.d.ts +61 -0
- package/dist/trpc/index.js +122 -0
- package/dist/types-9AWO9EGt.d.ts +197 -0
- package/dist/types-BPxgszlT.d.ts +550 -0
- package/dist/types-BVyImy5I.d.ts +120 -0
- package/dist/types-Bvc4oWT6.d.ts +9 -0
- package/dist/types-C3s9xVJG.d.ts +48 -0
- package/dist/types-Dnemwd77.d.ts +183 -0
- package/dist/types-kxznc7cn.d.ts +52 -0
- package/dist/types-vKyAMtcX.d.ts +157 -0
- package/dist/types-vNSDF7Dq.d.ts +66 -0
- package/dist/types-yF5LmQVD.d.ts +306 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +31 -0
- package/dist/usage-D07N9QJ3.js +42 -0
- package/dist/usage-D82l1CwK.js +2 -0
- package/dist/usage-DG1oeCca.js +693 -0
- package/dist/validation-error-CnjZ4t92.js +33 -0
- package/dist/validation-error-CtF23SrC.d.ts +26 -0
- package/dist/validity-uOp8UpYn.js +245 -0
- package/dist/verifier-BxBo6I44.js +466 -0
- package/dist/version-BvacVTZR.js +17 -0
- package/dist/views-Soe7bGSC.js +95 -0
- package/dist/vocabulary-DA8a8zif.d.ts +56 -0
- package/dist/vue/index.d.ts +79 -0
- package/dist/vue/index.js +196 -0
- package/dist/watch-Etoa_Mvr.js +2 -0
- package/dist/watch-UihESpsJ.js +128 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +307 -0
- package/package.json +450 -5
- package/rulesets/permdock.yaml +29 -0
- package/schemas/approval-request-v1.json +175 -0
- package/schemas/catalog-v1.json +535 -0
- package/schemas/openapi/oas-3.1.json +1298 -0
- package/schemas/openapi/oas-3.2.json +1516 -0
- package/schemas/openapi/overlay-1.1.json +79 -0
- package/schemas/supabase-claims-v1.json +144 -0
- package/schemas/supabase-manifest-v1.json +361 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +213 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +94 -0
- package/skills/permdock-audit/SKILL.md +67 -0
- package/skills/permdock-credentials/SKILL.md +55 -0
- package/skills/permdock-credentials/references/tokens-keys-links.md +132 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +62 -0
- package/skills/permdock-data/references/relationships.md +67 -0
- package/skills/permdock-tenancy/SKILL.md +80 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +89 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +492 -0
- package/skills/permdock-wire/references/policy-options.md +45 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { n as freezeDeep } from "../freeze-CrU8nQeX.js";
|
|
2
|
+
import { _ as listPermissions, p as isPermission } from "../permissions-DVoF6dMt.js";
|
|
3
|
+
import { t as anonymousSubject } from "../subject-C6dLxqHU.js";
|
|
4
|
+
import { t as mayUse } from "../may-use-DndQ5tQ-.js";
|
|
5
|
+
import { o as parseCredential, r as credentialSubject } from "../credential-8P81ff4u.js";
|
|
6
|
+
import { i as quoteSqlLiteral, o as sqlIdent } from "../sql-xdg5f4Fi.js";
|
|
7
|
+
import { o as subjectFromSupabaseSession, t as parseSupabaseManifest } from "../manifest-iaoBXHKc.js";
|
|
8
|
+
import { t as parseCatalog } from "../parse-BuyjxhZV.js";
|
|
9
|
+
import { AsyncResult, dbError } from "better-supabase";
|
|
10
|
+
//#region src/better-supabase/provider.ts
|
|
11
|
+
/**
|
|
12
|
+
* The helper each template calls (with `{scope}` unfilled), its default
|
|
13
|
+
* argument types and the role that calls it: `postgres` for the templates
|
|
14
|
+
* better-supabase runs inside its own `security definer` functions.
|
|
15
|
+
*/
|
|
16
|
+
const TEMPLATES = {
|
|
17
|
+
idsWith: {
|
|
18
|
+
helper: "permitted_{scope}_ids_by_permission",
|
|
19
|
+
listedAs: "permitted_{scope}_ids",
|
|
20
|
+
args: "text",
|
|
21
|
+
role: "authenticated",
|
|
22
|
+
sql: "permitted_{scope}_ids_by_permission({permission})"
|
|
23
|
+
},
|
|
24
|
+
isPlatform: {
|
|
25
|
+
helper: "permdock_has_permission",
|
|
26
|
+
listedAs: "permdock_has",
|
|
27
|
+
args: "text",
|
|
28
|
+
role: "authenticated",
|
|
29
|
+
sql: "permdock_has_permission({permission})"
|
|
30
|
+
},
|
|
31
|
+
idsWithFor: {
|
|
32
|
+
helper: "permitted_{scope}_ids_by_permission_for",
|
|
33
|
+
listedAs: "permitted_{scope}_ids_for",
|
|
34
|
+
args: "uuid, text",
|
|
35
|
+
role: "postgres",
|
|
36
|
+
sql: "permitted_{scope}_ids_by_permission_for({user}, {permission})"
|
|
37
|
+
},
|
|
38
|
+
isPlatformFor: {
|
|
39
|
+
helper: "permdock_has_permission_for",
|
|
40
|
+
listedAs: "permdock_has_for",
|
|
41
|
+
args: "uuid, text",
|
|
42
|
+
role: "postgres",
|
|
43
|
+
sql: "permdock_has_permission_for({user}, {permission})"
|
|
44
|
+
},
|
|
45
|
+
memberIds: {
|
|
46
|
+
helper: "member_{scope}_ids",
|
|
47
|
+
args: "",
|
|
48
|
+
role: "authenticated",
|
|
49
|
+
sql: "member_{scope}_ids()"
|
|
50
|
+
},
|
|
51
|
+
memberIdsFor: {
|
|
52
|
+
helper: "member_{scope}_ids_for",
|
|
53
|
+
args: "uuid",
|
|
54
|
+
role: "supabase_auth_admin",
|
|
55
|
+
sql: "member_{scope}_ids_for({user})"
|
|
56
|
+
},
|
|
57
|
+
canAssign: {
|
|
58
|
+
helper: "permdock_can_assign",
|
|
59
|
+
args: "text, text",
|
|
60
|
+
role: "authenticated",
|
|
61
|
+
sql: "permdock_can_assign({role}, {tenant}::text)"
|
|
62
|
+
},
|
|
63
|
+
canAssignFor: {
|
|
64
|
+
helper: "permdock_can_assign_for",
|
|
65
|
+
args: "uuid, text, text",
|
|
66
|
+
role: "postgres",
|
|
67
|
+
sql: "permdock_can_assign_for({user}, {role}, {tenant}::text)"
|
|
68
|
+
},
|
|
69
|
+
permissionsFor: {
|
|
70
|
+
helper: "permitted_{scope}_permission_keys_for",
|
|
71
|
+
args: "uuid, uuid",
|
|
72
|
+
role: "postgres",
|
|
73
|
+
sql: "array(select {schema}.permitted_{scope}_permission_keys_for({user}, {tenant}))"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const ASSIGN_ANY = {
|
|
77
|
+
canAssign: {
|
|
78
|
+
helper: "permdock_can_assign_any",
|
|
79
|
+
args: "text, uuid, text, text",
|
|
80
|
+
role: "authenticated",
|
|
81
|
+
sql: "permdock_can_assign_any({role}, {tenant}, '{scope}', {tenant}::text)"
|
|
82
|
+
},
|
|
83
|
+
canAssignFor: {
|
|
84
|
+
helper: "permdock_can_assign_any_for",
|
|
85
|
+
args: "uuid, text, uuid, text, text",
|
|
86
|
+
role: "postgres",
|
|
87
|
+
sql: "permdock_can_assign_any_for({user}, {role}, {tenant}, '{scope}', {tenant}::text)"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const listedName = (entry) => entry.listedAs ?? entry.helper;
|
|
91
|
+
const OPTIONAL = [
|
|
92
|
+
"idsWithFor",
|
|
93
|
+
"isPlatformFor",
|
|
94
|
+
"memberIds",
|
|
95
|
+
"memberIdsFor",
|
|
96
|
+
"canAssign",
|
|
97
|
+
"canAssignFor",
|
|
98
|
+
"permissionsFor"
|
|
99
|
+
];
|
|
100
|
+
/** Postgres aliases of the id types better-supabase reads (`uuid`, `text`, `bigint`, `integer`). */
|
|
101
|
+
const ID_TYPES = {
|
|
102
|
+
uuid: "uuid",
|
|
103
|
+
text: "text",
|
|
104
|
+
bigint: "bigint",
|
|
105
|
+
int8: "bigint",
|
|
106
|
+
integer: "integer",
|
|
107
|
+
int: "integer",
|
|
108
|
+
int4: "integer"
|
|
109
|
+
};
|
|
110
|
+
const withSchema = (schema, sql) => sql.includes("{schema}") ? sql.replaceAll("{schema}", schema) : `${schema}.${sql}`;
|
|
111
|
+
/** `p_user uuid, p_grant text` as `uuid, text`. */
|
|
112
|
+
const argTypes = (args) => args.split(",").map((arg) => arg.trim().split(/\s+/u).slice(1).join(" ")).filter((type) => type !== "").join(", ");
|
|
113
|
+
function tenantScopeOf(manifest, explicit) {
|
|
114
|
+
const names = manifest.rls.scopes.map((scope) => scope.name);
|
|
115
|
+
if (explicit !== void 0) {
|
|
116
|
+
if (!names.includes(explicit)) throw new TypeError(`PermDock: scope "${explicit}" is not in the manifest's rls.scopes (${names.join(", ")}).`);
|
|
117
|
+
return explicit;
|
|
118
|
+
}
|
|
119
|
+
const roots = manifest.rls.scopes.filter((scope) => scope.within === void 0);
|
|
120
|
+
const [root] = roots;
|
|
121
|
+
if (root === void 0 || roots.length > 1) throw new TypeError(`PermDock: the manifest has ${roots.length} root scopes (${roots.map((scope) => scope.name).join(", ") || "none"}), so it can't tell which scope tenants are. Pass { scope } with one of ${names.join(", ")}.`);
|
|
122
|
+
return root.name;
|
|
123
|
+
}
|
|
124
|
+
function covers(membership, scope) {
|
|
125
|
+
return !("value" in membership.scope) || membership.scope.value === scope;
|
|
126
|
+
}
|
|
127
|
+
/** `source` as better-supabase reads it, for a source that covers `scope`. */
|
|
128
|
+
function membershipOf(source, scope, problems) {
|
|
129
|
+
if ("through" in source.user) {
|
|
130
|
+
problems.push(`${source.table} reads its user id through ${source.user.through.table}, which better-supabase's membership reads don't follow.`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
table: source.table,
|
|
135
|
+
userColumn: source.user.column,
|
|
136
|
+
scope: "column" in source.scope ? { column: source.scope.column } : { value: scope },
|
|
137
|
+
idColumn: source.id.column
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function roleSourceOf(source) {
|
|
141
|
+
const role = source.role;
|
|
142
|
+
if (Array.isArray(role) || !("column" in role) || !("through" in role)) return;
|
|
143
|
+
return {
|
|
144
|
+
table: source.table,
|
|
145
|
+
role: {
|
|
146
|
+
column: role.column,
|
|
147
|
+
through: {
|
|
148
|
+
table: role.through.table,
|
|
149
|
+
id: role.through.id,
|
|
150
|
+
column: role.through.column
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function disabledRow(row) {
|
|
156
|
+
return {
|
|
157
|
+
table: row.table,
|
|
158
|
+
id: row.id,
|
|
159
|
+
...row.disabledAt === void 0 ? {} : { disabledAt: row.disabledAt },
|
|
160
|
+
...row.status === void 0 || row.active === void 0 ? {} : {
|
|
161
|
+
status: row.status,
|
|
162
|
+
active: row.active
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function permissionsOf(catalog, scopes) {
|
|
167
|
+
return catalog.permissions.map((permission) => {
|
|
168
|
+
const granted = /* @__PURE__ */ new Set();
|
|
169
|
+
for (const grant of catalog.grants ?? []) if (grant.permission === permission.key && typeof grant.scope === "string" && scopes.has(grant.scope)) granted.add(grant.scope);
|
|
170
|
+
return {
|
|
171
|
+
key: permission.key,
|
|
172
|
+
sqlComplete: !permission.rowConditions,
|
|
173
|
+
...granted.size > 0 ? { scopes: [...granted].toSorted() } : {}
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function tokenHookOf(manifest) {
|
|
178
|
+
const owned = manifest.claims.filter((claim) => claim.source === "permdock");
|
|
179
|
+
const registered = manifest.claims.filter((claim) => claim.source !== "permdock");
|
|
180
|
+
const budgeted = manifest.claims.filter((claim) => claim.budget);
|
|
181
|
+
return {
|
|
182
|
+
function: `${manifest.hook.schema}.${manifest.hook.function}`,
|
|
183
|
+
tenantClaim: manifest.tenantClaim,
|
|
184
|
+
ownedClaims: owned.map((claim) => claim.name),
|
|
185
|
+
...registered.length > 0 ? { registeredClaims: registered.map((claim) => ({
|
|
186
|
+
name: claim.name,
|
|
187
|
+
function: claim.source
|
|
188
|
+
})) } : {},
|
|
189
|
+
budget: {
|
|
190
|
+
claims: budgeted.map((claim) => claim.name),
|
|
191
|
+
bytes: manifest.budget.bytes,
|
|
192
|
+
...owned.some((claim) => claim.name === "memberships_truncated") ? { truncatedClaim: "memberships_truncated" } : {}
|
|
193
|
+
},
|
|
194
|
+
markers: {
|
|
195
|
+
hook: `-- permdock:hook ${manifest.markers.hook}`,
|
|
196
|
+
grants: `-- permdock:grants ${manifest.markers.grants}`
|
|
197
|
+
},
|
|
198
|
+
grantsCommand: `permdock supabase hook generate --grants-out supabase/migrations/<timestamp>_permdock_hook_grants.sql`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* better-supabase's `authorization` config value, built from PermDock's
|
|
203
|
+
* manifest and catalog: the scopes, the `rls generate` helpers as SQL
|
|
204
|
+
* templates, the membership tables, suspension rows, the token hook and
|
|
205
|
+
* every catalog key with `sqlComplete` set from its `rowConditions`. A
|
|
206
|
+
* `_for` template and `permissionsFor` are set only when the manifest lists
|
|
207
|
+
* their helper, which `rls.mode: 'database'` writes; `canApprove` only with
|
|
208
|
+
* `approver`. Throws when the manifest or catalog is invalid, or when the
|
|
209
|
+
* tenant scope is ambiguous.
|
|
210
|
+
*/
|
|
211
|
+
function authorizationProvider(options) {
|
|
212
|
+
const manifest = parseSupabaseManifest(options.manifest);
|
|
213
|
+
const catalog = options.catalog === void 0 ? void 0 : parseCatalog(options.catalog);
|
|
214
|
+
const rls = manifest.rls;
|
|
215
|
+
const problems = [];
|
|
216
|
+
if (catalog === void 0) problems.push("No catalog: better-supabase cannot check that the permission keys its SQL modules use (ai_chat, workflow, inbox and the rest) exist. Pass `catalog`.");
|
|
217
|
+
const tenantScope = tenantScopeOf(manifest, options.scope);
|
|
218
|
+
const scopeNames = rls.scopes.map((scope) => scope.name);
|
|
219
|
+
const helpers = new Map(rls.helpers.map((helper) => [helper.name, helper]));
|
|
220
|
+
const schema = sqlIdent(rls.schema);
|
|
221
|
+
const rootTenant = rls.scopes.find((scope) => scope.name === tenantScope)?.within === void 0;
|
|
222
|
+
const assignAny = rls.customRoles === true && rootTenant;
|
|
223
|
+
const entryOf = (template) => assignAny && (template === "canAssign" || template === "canAssignFor") ? ASSIGN_ANY[template] : TEMPLATES[template];
|
|
224
|
+
const listed = (template, scope) => helpers.has(listedName(entryOf(template)).replaceAll("{scope}", scope));
|
|
225
|
+
const templates = [
|
|
226
|
+
"idsWith",
|
|
227
|
+
"isPlatform",
|
|
228
|
+
...OPTIONAL.filter((template) => listed(template, tenantScope))
|
|
229
|
+
];
|
|
230
|
+
for (const template of ["idsWith", "isPlatform"]) if (!listed(template, tenantScope)) problems.push(`The manifest's rls.helpers has no ${rls.schema}.${listedName(TEMPLATES[template]).replaceAll("{scope}", tenantScope)}. Run \`permdock rls generate\`, then \`permdock supabase inspect --out\`.`);
|
|
231
|
+
if (rls.customRoles === true && !rootTenant && templates.includes("canAssign")) problems.push(`The tenant scope "${tenantScope}" is not a root scope, so canAssign checks declared roles only: ${rls.schema}.${ASSIGN_ANY.canAssign.helper}, which also answers for custom roles, takes the root tenant id.`);
|
|
232
|
+
const optional = {};
|
|
233
|
+
const requires = [];
|
|
234
|
+
const required = /* @__PURE__ */ new Set();
|
|
235
|
+
for (const template of templates) {
|
|
236
|
+
const entry = entryOf(template);
|
|
237
|
+
optional[template] = withSchema(schema, entry.sql);
|
|
238
|
+
for (const scope of entry.helper.includes("{scope}") ? scopeNames : [tenantScope]) {
|
|
239
|
+
const name = entry.helper.replaceAll("{scope}", scope);
|
|
240
|
+
const fn = `${schema}.${sqlIdent(name)}`;
|
|
241
|
+
if (required.has(fn)) continue;
|
|
242
|
+
required.add(fn);
|
|
243
|
+
const helper = helpers.get(name);
|
|
244
|
+
const args = helper === void 0 ? entry.args : argTypes(helper.args);
|
|
245
|
+
requires.push({
|
|
246
|
+
function: fn,
|
|
247
|
+
...args === "" ? {} : { args },
|
|
248
|
+
role: entry.role
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const memberships = [];
|
|
253
|
+
const roleSources = [];
|
|
254
|
+
for (const source of rls.memberships) {
|
|
255
|
+
if (!covers(source, tenantScope)) continue;
|
|
256
|
+
const membership = membershipOf(source, tenantScope, problems);
|
|
257
|
+
if (membership) memberships.push(membership);
|
|
258
|
+
const roleSource = roleSourceOf(source);
|
|
259
|
+
if (roleSource) roleSources.push(roleSource);
|
|
260
|
+
}
|
|
261
|
+
const users = rls.suspension?.users;
|
|
262
|
+
const tenants = rls.suspension?.scopes?.[tenantScope];
|
|
263
|
+
const approver = options.approver;
|
|
264
|
+
let canApprove;
|
|
265
|
+
if (approver !== void 0) {
|
|
266
|
+
if (catalog !== void 0 && !catalog.permissions.some((permission) => permission.key === approver.key)) problems.push(`The approver permission ${approver.key} is not in the catalog. Run \`permdock catalog\` after adding it to the policy.`);
|
|
267
|
+
const name = `permitted_${tenantScope}_ids_by_permission`;
|
|
268
|
+
canApprove = `{tenant} in (select ${schema}.${sqlIdent(name)}(${quoteSqlLiteral(approver.key)}))`;
|
|
269
|
+
requires.push({
|
|
270
|
+
function: `${schema}.${sqlIdent(name)}`,
|
|
271
|
+
args: "text",
|
|
272
|
+
role: "postgres"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const functions = {
|
|
276
|
+
...optional,
|
|
277
|
+
idsWith: `${schema}.${TEMPLATES.idsWith.sql}`,
|
|
278
|
+
isPlatform: `${schema}.${TEMPLATES.isPlatform.sql}`,
|
|
279
|
+
...canApprove === void 0 ? {} : { canApprove }
|
|
280
|
+
};
|
|
281
|
+
const scopes = rls.scopes.map((scope) => {
|
|
282
|
+
const idType = ID_TYPES[scope.type.trim().toLowerCase()];
|
|
283
|
+
if (idType === void 0) problems.push(`Scope "${scope.name}" has ids of type ${scope.type}; better-supabase reads uuid, text, bigint and integer ids.`);
|
|
284
|
+
return scope.within === void 0 ? {
|
|
285
|
+
name: scope.name,
|
|
286
|
+
idType: idType ?? scope.type
|
|
287
|
+
} : {
|
|
288
|
+
name: scope.name,
|
|
289
|
+
idType: idType ?? scope.type,
|
|
290
|
+
parent: scope.within
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
return freezeDeep({
|
|
294
|
+
apiVersion: 1,
|
|
295
|
+
name: "PermDock",
|
|
296
|
+
scopes,
|
|
297
|
+
tenantScope,
|
|
298
|
+
functions,
|
|
299
|
+
requires,
|
|
300
|
+
...catalog ? { permissions: permissionsOf(catalog, new Set(scopeNames)) } : {},
|
|
301
|
+
...memberships.length > 0 ? { memberships } : {},
|
|
302
|
+
...users || tenants ? { suspension: {
|
|
303
|
+
...users ? { user: disabledRow(users) } : {},
|
|
304
|
+
...tenants ? { tenant: disabledRow(tenants) } : {}
|
|
305
|
+
} } : {},
|
|
306
|
+
...roleSources.length > 0 ? { roleSources } : {},
|
|
307
|
+
...canApprove === void 0 ? {} : { approvals: { distinctApprover: true } },
|
|
308
|
+
decidingColumns: manifest.decidingColumns,
|
|
309
|
+
tokenHook: tokenHookOf(manifest),
|
|
310
|
+
...problems.length > 0 ? { problems } : {}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/better-supabase/policies.ts
|
|
315
|
+
/** PermDock splits a key with row conditions into `key#1`, `key#2`, ... in the policies it generates. */
|
|
316
|
+
const SPLIT_KEY = /#\d+$/u;
|
|
317
|
+
function checkedKey(where, permission, catalog, scope) {
|
|
318
|
+
const key = permission.key;
|
|
319
|
+
if (SPLIT_KEY.test(key)) throw new TypeError(`PermDock: ${where} "${key}" is one part of a permission split by row condition. Use the policies \`permdock rls generate\` writes for it.`);
|
|
320
|
+
const entry = catalog.permissions.find((candidate) => candidate.key === key);
|
|
321
|
+
if (!entry) throw new TypeError(`PermDock: ${where} "${key}" is not in permissions.catalog.json. Run \`permdock catalog\`.`);
|
|
322
|
+
if (entry.rowConditions) throw new TypeError(`PermDock: ${where} "${key}" has row conditions (rowConditions: true), which the SQL helpers don't check, so the policy would grant every object in the scope. Use the policies \`permdock rls generate\` writes for it.`);
|
|
323
|
+
const grantScope = scope === "platform" ? "global" : scope;
|
|
324
|
+
const scopes = (catalog.grants ?? []).flatMap((grant) => grant.permission === key && typeof grant.scope === "string" ? [grant.scope] : []);
|
|
325
|
+
if (scopes.length > 0 && !scopes.includes(grantScope)) throw new TypeError(`PermDock: ${where} "${key}" is granted at ${[...new Set(scopes)].join(", ")}, not at ${grantScope}. Check it at one of those scopes.`);
|
|
326
|
+
return key;
|
|
327
|
+
}
|
|
328
|
+
function target(options) {
|
|
329
|
+
const manifest = parseSupabaseManifest(options.manifest);
|
|
330
|
+
const catalog = parseCatalog(options.catalog);
|
|
331
|
+
const names = manifest.rls.scopes.map((scope) => scope.name);
|
|
332
|
+
if (options.scope !== "platform" && !names.includes(options.scope)) throw new TypeError(`PermDock: scope "${options.scope}" is not in the manifest's rls.scopes (${names.join(", ")}) and is not "platform".`);
|
|
333
|
+
const schema = sqlIdent(manifest.rls.schema);
|
|
334
|
+
return {
|
|
335
|
+
catalog,
|
|
336
|
+
sql: {
|
|
337
|
+
idsWith: `${schema}.permitted_{scope}_ids_by_permission({permission})`,
|
|
338
|
+
isPlatform: `${schema}.permdock_has_permission({permission})`
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* A better-supabase bucket `policy` that checks PermDock permissions with
|
|
344
|
+
* the `rls generate` helpers at `scope`. The helpers check role and scope
|
|
345
|
+
* only, so a permission the catalog doesn't mark `rowConditions: false`, or
|
|
346
|
+
* doesn't grant at `scope`, throws.
|
|
347
|
+
*/
|
|
348
|
+
function bucketPolicy(access, options) {
|
|
349
|
+
const { catalog, sql } = target(options);
|
|
350
|
+
const key = (name, permission) => checkedKey(`bucket access.${name}`, permission, catalog, options.scope);
|
|
351
|
+
return freezeDeep({
|
|
352
|
+
access: {
|
|
353
|
+
read: key("read", access.read),
|
|
354
|
+
...access.list ? { list: key("list", access.list) } : {},
|
|
355
|
+
write: key("write", access.write),
|
|
356
|
+
...access.delete ? { delete: key("delete", access.delete) } : {}
|
|
357
|
+
},
|
|
358
|
+
scope: options.scope,
|
|
359
|
+
...options.segment === void 0 ? {} : { segment: options.segment },
|
|
360
|
+
sql
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** As {@link bucketPolicy}, for a better-supabase Realtime topic. */
|
|
364
|
+
function topicPolicy(access, options) {
|
|
365
|
+
const { catalog, sql } = target(options);
|
|
366
|
+
const key = (name, permission) => checkedKey(`topic ${name}`, permission, catalog, options.scope);
|
|
367
|
+
return freezeDeep({
|
|
368
|
+
receive: key("receive", access.receive),
|
|
369
|
+
...access.send ? { send: key("send", access.send) } : {},
|
|
370
|
+
scope: options.scope,
|
|
371
|
+
...options.segment === void 0 ? {} : { segment: options.segment },
|
|
372
|
+
sql
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/better-supabase/api-keys.ts
|
|
377
|
+
const seconds = (instant) => Math.floor(instant.epochMilliseconds / 1e3);
|
|
378
|
+
/** When a key stops verifying: its expiry, or the end of a rotated key's grace period. */
|
|
379
|
+
function endOf(key) {
|
|
380
|
+
const { expiresAt, revokedAt } = key;
|
|
381
|
+
if (revokedAt === void 0) return expiresAt;
|
|
382
|
+
if (expiresAt === void 0) return revokedAt;
|
|
383
|
+
return revokedAt.epochMilliseconds < expiresAt.epochMilliseconds ? revokedAt : expiresAt;
|
|
384
|
+
}
|
|
385
|
+
/** `rls.apiKeys.serviceRoles` from the manifest, when it has `rls.apiKeys`. */
|
|
386
|
+
function manifestServiceRoles(manifest) {
|
|
387
|
+
return manifest === void 0 ? void 0 : parseSupabaseManifest(manifest).rls.apiKeys?.serviceRoles;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* A personal key as a `user` credential acting as its user, a tenant key as
|
|
391
|
+
* a `service` credential holding `roles` in its tenant; `null` when `*`
|
|
392
|
+
* has no expansion, a tenant key has no roles, or the record is invalid.
|
|
393
|
+
*/
|
|
394
|
+
function keyCredential(key, roles, allPermissions) {
|
|
395
|
+
const scopes = key.scopes.includes("*") ? allPermissions?.map((permission) => permission.key) : key.scopes;
|
|
396
|
+
if (scopes === void 0) return null;
|
|
397
|
+
const owner = key.userId;
|
|
398
|
+
const base = {
|
|
399
|
+
v: 1,
|
|
400
|
+
id: key.id,
|
|
401
|
+
permissions: scopes.map((permission) => ({ permission })),
|
|
402
|
+
createdBy: key.createdBy ?? owner ?? key.id,
|
|
403
|
+
createdAt: key.createdAt ?? 0,
|
|
404
|
+
...key.expiresAt === void 0 ? {} : { expiresAt: key.expiresAt },
|
|
405
|
+
name: key.name
|
|
406
|
+
};
|
|
407
|
+
if (owner !== void 0) return parseCredential({
|
|
408
|
+
...base,
|
|
409
|
+
kind: "user",
|
|
410
|
+
principal: owner,
|
|
411
|
+
...key.organizationId === void 0 ? {} : { tenant: key.organizationId }
|
|
412
|
+
}) ?? null;
|
|
413
|
+
if (roles === void 0 || key.organizationId === void 0) return null;
|
|
414
|
+
return parseCredential({
|
|
415
|
+
...base,
|
|
416
|
+
kind: "service",
|
|
417
|
+
principal: key.id,
|
|
418
|
+
tenant: key.organizationId,
|
|
419
|
+
roles
|
|
420
|
+
}) ?? null;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* better-supabase's API keys as a `CredentialVerifier`, for
|
|
424
|
+
* `subjectFromApiKey` from `permdock/server`, which reads PermDock's
|
|
425
|
+
* `pdk_<id>_<secret>` format: create the keys with `prefix: "pdk"`. The
|
|
426
|
+
* credential id is the key's `publicId`, the id in the token. A personal key
|
|
427
|
+
* is a `user` credential acting as its user (inside its tenant when the key
|
|
428
|
+
* is limited to one), a tenant key a `service` credential holding
|
|
429
|
+
* `serviceRoles` in its tenant, and the key's scopes are the credential's
|
|
430
|
+
* permissions. better-supabase records the use when it verifies, so there is
|
|
431
|
+
* no `touch`. A rotated key in its grace period verifies, and its credential
|
|
432
|
+
* expires when the grace period ends. An invalid, revoked, expired or
|
|
433
|
+
* rate-limited key, and a failed lookup, are `null`. `permdock/server` exports a different
|
|
434
|
+
* `apiKeyVerifier`, over the application's own key records.
|
|
435
|
+
*/
|
|
436
|
+
function apiKeyVerifier(options) {
|
|
437
|
+
const fallback = manifestServiceRoles(options.manifest);
|
|
438
|
+
return freezeDeep({ async verify(secret) {
|
|
439
|
+
try {
|
|
440
|
+
const checked = await options.keys.verify(secret);
|
|
441
|
+
if (!checked.ok || checked.data.status !== "ok") return null;
|
|
442
|
+
const { key } = checked.data;
|
|
443
|
+
if (key.state !== "active" && key.state !== "grace") return null;
|
|
444
|
+
const end = endOf(key);
|
|
445
|
+
const roles = typeof options.serviceRoles === "function" ? options.serviceRoles(key) : options.serviceRoles ?? fallback;
|
|
446
|
+
return keyCredential({
|
|
447
|
+
id: key.publicId,
|
|
448
|
+
name: key.name,
|
|
449
|
+
scopes: key.scopes,
|
|
450
|
+
createdAt: seconds(key.createdAt),
|
|
451
|
+
...key.organizationId === void 0 ? {} : { organizationId: key.organizationId },
|
|
452
|
+
...key.userId === void 0 ? {} : { userId: key.userId },
|
|
453
|
+
...key.createdBy === void 0 ? {} : { createdBy: key.createdBy },
|
|
454
|
+
...end === void 0 ? {} : { expiresAt: seconds(end) }
|
|
455
|
+
}, roles, options.allPermissions);
|
|
456
|
+
} catch {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
} });
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* The `claim` and `tenantClaim` options of better-supabase's
|
|
463
|
+
* `apiKeyClaims()`, from the manifest's `rls.apiKeys`, so a key's token
|
|
464
|
+
* carries the scopes ceiling, tenant and roles the `rls generate` helpers
|
|
465
|
+
* read. Throws when the manifest has no `rls.apiKeys`.
|
|
466
|
+
*/
|
|
467
|
+
function apiKeyClaimOptions(manifest) {
|
|
468
|
+
const rls = parseSupabaseManifest(manifest).rls;
|
|
469
|
+
const apiKeys = rls.apiKeys;
|
|
470
|
+
if (!apiKeys) throw new TypeError("PermDock: the manifest has no rls.apiKeys. Set rls.apiKeys in permdock.config.ts, then run `permdock rls generate` and `permdock supabase inspect --out`.");
|
|
471
|
+
return freezeDeep({
|
|
472
|
+
claim: {
|
|
473
|
+
name: apiKeys.claim,
|
|
474
|
+
scopes: apiKeys.scopes,
|
|
475
|
+
tenant: apiKeys.tenant,
|
|
476
|
+
roles: apiKeys.roles,
|
|
477
|
+
serviceRoles: apiKeys.serviceRoles
|
|
478
|
+
},
|
|
479
|
+
tenantClaim: rls.tenantClaim
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/better-supabase/credentials.ts
|
|
484
|
+
/**
|
|
485
|
+
* `provider` with every `getToken`, `startAuthorization`,
|
|
486
|
+
* `completeAuthorization` and `revoke` decided by PermDock first, on the
|
|
487
|
+
* `CredentialRef` as the row. Anything but `granted` is a `forbidden`
|
|
488
|
+
* error with hint `PERMDOCK_DENIED`, and a failed check is one too.
|
|
489
|
+
* `capabilities` and `verifyInbound` (a third party's request, with no
|
|
490
|
+
* user) pass through. Build it per request, with that request's PermDock.
|
|
491
|
+
*/
|
|
492
|
+
function credentialGuard(provider, options) {
|
|
493
|
+
const allowed = async (permission, ref) => {
|
|
494
|
+
try {
|
|
495
|
+
return (await options.permdock()).can(permission, ref);
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
const guarded = (permission, ref, run) => AsyncResult.from(async () => await allowed(permission, ref) ? run() : AsyncResult.err(dbError("forbidden", `PermDock denied ${permission.key}`, { hint: "PERMDOCK_DENIED" })));
|
|
501
|
+
const revoke = options.revoke ?? options.use;
|
|
502
|
+
const start = provider.startAuthorization?.bind(provider);
|
|
503
|
+
const complete = provider.completeAuthorization?.bind(provider);
|
|
504
|
+
const inbound = provider.verifyInbound?.bind(provider);
|
|
505
|
+
return Object.freeze({
|
|
506
|
+
apiVersion: 1,
|
|
507
|
+
name: provider.name,
|
|
508
|
+
getToken: (ref, getOptions) => guarded(options.use, ref, () => provider.getToken(ref, getOptions)),
|
|
509
|
+
capabilities: (ref) => provider.capabilities(ref),
|
|
510
|
+
...start === void 0 ? {} : { startAuthorization: (ref, startOptions) => guarded(options.use, ref, () => start(ref, startOptions)) },
|
|
511
|
+
...complete === void 0 ? {} : { completeAuthorization: (ref, completeOptions) => guarded(options.use, ref, () => complete(ref, completeOptions)) },
|
|
512
|
+
revoke: (ref, revokeOptions) => guarded(revoke, ref, () => provider.revoke(ref, revokeOptions)),
|
|
513
|
+
...inbound === void 0 ? {} : { verifyInbound: inbound }
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/better-supabase/mcp.ts
|
|
518
|
+
const refused = (reason) => ({
|
|
519
|
+
allowed: false,
|
|
520
|
+
reason
|
|
521
|
+
});
|
|
522
|
+
/**
|
|
523
|
+
* The `authorize` and `visible` hooks of better-supabase's `createMcp`, for
|
|
524
|
+
* tools whose `meta` is a PermDock permission. A tool without a permission
|
|
525
|
+
* is hidden and refused. `authorize` grants only a `granted` decision: an
|
|
526
|
+
* approval-required call is refused, because `createMcp`
|
|
527
|
+
* has no approval flow. A throw anywhere refuses.
|
|
528
|
+
*/
|
|
529
|
+
function toolPolicy(options) {
|
|
530
|
+
return Object.freeze({
|
|
531
|
+
async authorize(ctx, tool, args) {
|
|
532
|
+
const permission = tool.meta;
|
|
533
|
+
if (!isPermission(permission)) return refused("This tool has no PermDock permission.");
|
|
534
|
+
try {
|
|
535
|
+
const permdock = await options.permdock(ctx);
|
|
536
|
+
const data = await options.data?.(ctx, tool, args);
|
|
537
|
+
const decision = permdock.decide(permission, data);
|
|
538
|
+
switch (decision.outcome) {
|
|
539
|
+
case "granted": return { allowed: true };
|
|
540
|
+
case "approval-required": return refused(`${permission.key} needs approval, which this server does not collect.`);
|
|
541
|
+
case "denied": return refused(`PermDock denied ${permission.key}.`);
|
|
542
|
+
default: return decision;
|
|
543
|
+
}
|
|
544
|
+
} catch {
|
|
545
|
+
return refused("The permission check failed.");
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
async visible(ctx, tool) {
|
|
549
|
+
const permission = tool.meta;
|
|
550
|
+
if (!isPermission(permission)) return false;
|
|
551
|
+
try {
|
|
552
|
+
return mayUse(await options.permdock(ctx), permission);
|
|
553
|
+
} catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/better-supabase/subject.ts
|
|
561
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
562
|
+
/** `session` with the short codes in `claim` turned back into feature keys. */
|
|
563
|
+
function decodedFeatures(session, claim, keys) {
|
|
564
|
+
const claims = session?.claims;
|
|
565
|
+
if (session === null || session === void 0 || !isRecord(claims)) return session;
|
|
566
|
+
const coded = claims[claim];
|
|
567
|
+
if (!isRecord(coded)) return session;
|
|
568
|
+
const byCode = new Map(Object.entries(keys).map(([key, code]) => [code, key]));
|
|
569
|
+
const decoded = Object.fromEntries(Object.entries(coded).map(([tenant, codes]) => [tenant, Array.isArray(codes) ? codes.flatMap((code) => {
|
|
570
|
+
const key = typeof code === "string" ? byCode.get(code) : void 0;
|
|
571
|
+
return key === void 0 ? [] : [key];
|
|
572
|
+
}) : codes]));
|
|
573
|
+
return {
|
|
574
|
+
...session,
|
|
575
|
+
claims: {
|
|
576
|
+
...claims,
|
|
577
|
+
[claim]: decoded
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function isApiKeySession(session) {
|
|
582
|
+
if (typeof session !== "object" || session === null) return false;
|
|
583
|
+
const record = Object.fromEntries(Object.entries(session));
|
|
584
|
+
return record["kind"] === "apiKey" && typeof record["keyId"] === "string" && typeof record["name"] === "string" && Array.isArray(record["scopes"]) && record["scopes"].every((scope) => typeof scope === "string");
|
|
585
|
+
}
|
|
586
|
+
function apiKeySubject(session, options) {
|
|
587
|
+
const roles = typeof options.serviceRoles === "function" ? options.serviceRoles(session) : options.serviceRoles ?? manifestServiceRoles(options.manifest);
|
|
588
|
+
const credential = keyCredential({
|
|
589
|
+
id: session.keyId,
|
|
590
|
+
name: session.name,
|
|
591
|
+
scopes: session.scopes,
|
|
592
|
+
...session.organizationId === void 0 ? {} : { organizationId: session.organizationId },
|
|
593
|
+
...session.userId === void 0 ? {} : { userId: session.userId },
|
|
594
|
+
...session.createdBy === void 0 ? {} : { createdBy: session.createdBy },
|
|
595
|
+
...session.createdAt === void 0 ? {} : { createdAt: session.createdAt }
|
|
596
|
+
}, roles, listPermissions(options.permissions));
|
|
597
|
+
return credential === null ? anonymousSubject() : credentialSubject(credential, { permissions: options.permissions });
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* `subjectFromSupabaseSession` for better-supabase's `AuthSession`, reading
|
|
601
|
+
* `memberships` for the memberships and the entitlements module's `features`
|
|
602
|
+
* claim for `principal.plans` (`plans` names another claim). With `apiKeys`,
|
|
603
|
+
* a verified `apiKey` session is the key's credential subject: a personal key
|
|
604
|
+
* acts as its user within the key's scopes, a tenant key as a `service`
|
|
605
|
+
* principal holding `serviceRoles`. Without `apiKeys`, and for `anon`,
|
|
606
|
+
* `service` and `invalid` sessions, the subject is anonymous.
|
|
607
|
+
*/
|
|
608
|
+
function subjectFromBetterSupabase(session, options = {}) {
|
|
609
|
+
const { apiKeys, plans = "features", ...rest } = options;
|
|
610
|
+
if (isApiKeySession(session)) return apiKeys === void 0 ? anonymousSubject() : apiKeySubject(session, apiKeys);
|
|
611
|
+
const claim = typeof plans === "string" ? plans : plans.claim ?? "features";
|
|
612
|
+
const keys = typeof plans === "string" ? void 0 : plans.keys;
|
|
613
|
+
return subjectFromSupabaseSession(keys === void 0 ? session : decodedFeatures(session, claim, keys), {
|
|
614
|
+
memberships: "memberships",
|
|
615
|
+
...rest,
|
|
616
|
+
plans: claim
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
//#endregion
|
|
620
|
+
export { apiKeyClaimOptions, apiKeyVerifier, authorizationProvider, bucketPolicy, credentialGuard, subjectFromBetterSupabase, toolPolicy, topicPolicy };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { f as ApprovalStore } from "./types-vKyAMtcX.js";
|
|
2
|
+
import { t as InstanceOptions } from "./instance-options-BK_rNOSC.js";
|
|
3
|
+
import { r as PdpFactory } from "./types-BVyImy5I.js";
|
|
4
|
+
import { o as OtelWrap } from "./types-vNSDF7Dq.js";
|
|
5
|
+
import { T as TenantOption, b as WebBotAuthVerifier } from "./create-BWyx3jua.js";
|
|
6
|
+
//#region src/server/bind.d.ts
|
|
7
|
+
/** The options of every framework adapter; `TContext` is what its callbacks receive. */
|
|
8
|
+
type ServerAdapterOptions<TContext, TUser = unknown> = InstanceOptions & ServerAdapterServices & {
|
|
9
|
+
readonly subject: (context: TContext) => TUser | Promise<TUser>;
|
|
10
|
+
/** The agent or service acting for the subject; anything but an `Actor` is ignored. */
|
|
11
|
+
readonly actor?: (context: TContext) => unknown;
|
|
12
|
+
readonly tenant?: TenantOption<TContext>;
|
|
13
|
+
};
|
|
14
|
+
/** The services an adapter hands to its kernel unchanged. */
|
|
15
|
+
type ServerAdapterServices = {
|
|
16
|
+
readonly store?: ApprovalStore;
|
|
17
|
+
/** `createPermDock` from `permdock/pdp`; `protect` then decides delegated permissions remotely. */
|
|
18
|
+
readonly pdp?: PdpFactory;
|
|
19
|
+
/** `(permdock) => withOtel(permdock, options)` from `permdock/otel`. */
|
|
20
|
+
readonly otel?: OtelWrap;
|
|
21
|
+
/** `(request) => verifyWebBotAuth(request, options)`; a verified bot becomes the actor. */
|
|
22
|
+
readonly webBotAuth?: WebBotAuthVerifier;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ServerAdapterServices as n, ServerAdapterOptions as t };
|