permdock 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 +37 -35
- package/dist/{schema-Bol6gqXA.js → 2020-C9VN-x8c.js} +5 -130
- package/dist/a2a/index.d.ts +6 -6
- package/dist/a2a/index.js +14 -17
- package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
- package/dist/adoption-scan-B4R5fnMU.js +107 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +1 -0
- package/dist/ai-sdk/index.d.ts +18 -35
- package/dist/ai-sdk/index.js +37 -23
- package/dist/{app-data-CV8pqqMi.js → app-data-MuyzVqX8.js} +2 -1
- package/dist/approval-header-CJACtHkv.js +23 -0
- package/dist/approval-prompt-Dg5LeAcT.js +62 -0
- package/dist/approval-prompt-Djo_PyZc.d.ts +57 -0
- package/dist/approvals/index.d.ts +5 -3
- package/dist/approvals/index.js +8 -5
- package/dist/approvals-DJ6Yd5z1.js +995 -0
- package/dist/arazzo-B6lebmiQ.js +36 -0
- package/dist/{arazzo-C4BcskbX.js → arazzo-BWhNF-k0.js} +20 -14
- package/dist/args-IIolxxKA.js +197 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +27 -0
- package/dist/audience-Dg8XsRaO.js +55 -0
- package/dist/auth-DRLEizbL.js +178 -0
- package/dist/authzen/index.d.ts +26 -6
- package/dist/authzen/index.js +94 -79
- package/dist/automation-BMZPIVUM.d.ts +114 -0
- package/dist/automation-DVjOckjV.js +157 -0
- package/dist/better-auth/index.d.ts +8 -6
- package/dist/better-auth/index.js +9 -8
- package/dist/better-supabase/index.d.ts +357 -30
- package/dist/better-supabase/index.js +936 -194
- package/dist/{bind-BzqG8SMb.d.ts → bind--9EFVYWF.d.ts} +12 -10
- package/dist/{bind-kk-uyCwH.js → bind-CZzhx-Vj.js} +14 -10
- package/dist/body-CH5of8Ne.js +10 -0
- package/dist/body-text-QcXok_SI.js +52 -0
- package/dist/{build-instance-DIIV9DbC.js → build-instance-BMnAudHn.js} +4 -3
- package/dist/{canonical-json-CFKyFOke.js → canonical-json-BC8_nMTw.js} +5 -3
- package/dist/{capability-CksyCWDH.d.ts → capability-BSslw1QV.d.ts} +44 -25
- package/dist/{capability-txATtKV4.js → capability-N1K9IpqS.js} +68 -42
- package/dist/casbin-Cr7RLtA3.js +30 -0
- package/dist/casbin-Dyu2gnUD.js +234 -0
- package/dist/casl-64gpT-8x.js +24 -0
- package/dist/casl-DjEDhTCP.js +299 -0
- package/dist/catalog/index.d.ts +74 -2
- package/dist/catalog/index.js +51 -2
- package/dist/{catalog-tZUyOfOv.js → catalog-BECMPrwc.js} +51 -6
- package/dist/catalog-DjhMYYCq.js +1 -0
- package/dist/catalog-R_A__RsH.js +52 -0
- package/dist/{catalog-doc-DWtg0gGR.js → catalog-doc-C9wjsfNY.js} +45 -23
- package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
- package/dist/chat/index.d.ts +2 -0
- package/dist/chat/index.js +224 -0
- package/dist/chat-sdk/index.d.ts +87 -0
- package/dist/chat-sdk/index.js +197 -0
- package/dist/claims-map-7oQ3ue4X.js +13 -0
- package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
- package/dist/claude-agent/index.d.ts +7 -14
- package/dist/claude-agent/index.js +24 -27
- package/dist/clerk/index.d.ts +9 -8
- package/dist/clerk/index.js +9 -14
- package/dist/cli/bin.js +36 -17
- package/dist/cli/index.d.ts +19 -5
- package/dist/cli/index.js +164 -4
- package/dist/cloud/index.d.ts +102 -63
- package/dist/cloud/index.js +239 -340
- package/dist/cloud-CIoHNevm.js +87 -0
- package/dist/codemod-Bmj9dWMH.js +37 -0
- package/dist/codemod-Cxvvc70T.js +2084 -0
- package/dist/{collect-2SrB7lfk.js → collect-CrZJJnEF.js} +75 -11
- package/dist/collect-DloHbDVa.js +55 -0
- package/dist/collect-FlXJlhus.js +1 -0
- package/dist/command-L8EstFNm.js +6 -0
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -2
- package/dist/{compile-CcEu2ZQ1.js → compile-CMqMx_IN.js} +11 -8
- package/dist/{compile-CtTP7eur.d.ts → compile-Ckm2HqBZ.d.ts} +5 -3
- package/dist/completion-BqhRBJD8.js +143 -0
- package/dist/config-B46rm9sc.js +26 -0
- package/dist/{config-BIjREpyE.js → config-bs_kZeYq.js} +12 -7
- package/dist/{config-report--Xb9APJS.js → config-report-DUgj62Ai.js} +15 -5
- package/dist/{connection-DUsczpB5.js → connection-DjhIJSs2.js} +6 -5
- package/dist/{context-DieZVJXu.js → context-bbFHR-0D.js} +2 -2
- package/dist/convex/index.d.ts +11 -10
- package/dist/convex/index.js +6 -4
- package/dist/create-0VZ7Pdiu.d.ts +107 -0
- package/dist/create-BDS7wc3y.d.ts +138 -0
- package/dist/create-BFfm17UA.d.ts +110 -0
- package/dist/create-BWRSXgOF.js +403 -0
- package/dist/create-C3b4B5eF.js +130 -0
- package/dist/{create-BBO24vj6.js → create-DnrmLnLW.js} +31 -32
- package/dist/{credential-kue0qpbm.js → credential-DM2um3ha.js} +152 -31
- package/dist/credentials-DPyHmYz5.js +74 -0
- package/dist/delegation-DaXVdDhM.js +286 -0
- package/dist/delivery/index.d.ts +5 -0
- package/dist/delivery/index.js +5 -0
- package/dist/delivery-DCAgNyCn.js +499 -0
- package/dist/delivery-event-xX4obB8F.js +40 -0
- package/dist/{denied-text-DfRk15uw.d.ts → denied-text-D8jDHgsl.d.ts} +5 -5
- package/dist/{describe-C3ldA9os.d.ts → describe-BZUsixiL.d.ts} +2 -2
- package/dist/{describe-Cgkp7eP0.js → describe-BybMRY7A.js} +6 -2
- package/dist/device-BARQ8eYG.js +193 -0
- package/dist/diff-CxZo9pTq.js +37 -0
- package/dist/{diff-rpqoe7Ag.js → diff-DCePiN6M.js} +60 -54
- package/dist/doctor-CxiwSfqH.js +44 -0
- package/dist/{doctor-owlv6ddh.js → doctor-DFjUxf2f.js} +245 -50
- package/dist/drizzle/index.d.ts +5 -3
- package/dist/drizzle/index.js +2 -2
- package/dist/elysia/index.d.ts +16 -13
- package/dist/elysia/index.js +13 -12
- package/dist/env-DJZrPjRi.js +8 -0
- package/dist/errors-BDUwg2qC.js +27 -0
- package/dist/{errors-drnfxYtm.js → errors-BKy0EfhN.js} +5 -9
- package/dist/errors-BWXBMaKK.d.ts +11 -0
- package/dist/{errors-OD_4c__H.d.ts → errors-C2PXIv4c.d.ts} +2 -2
- package/dist/errors-CWTY6uih.js +143 -0
- package/dist/evaluate-_R8QkWaf.js +1732 -0
- package/dist/evaluations-16UqqQ6W.d.ts +32 -0
- package/dist/{evaluations-C9YJsv3H.js → evaluations-DZ2CbV5k.js} +145 -34
- package/dist/eve/index.d.ts +12 -16
- package/dist/eve/index.js +22 -28
- package/dist/event-time-CsegxXxB.js +23 -0
- package/dist/expiry-D4_YKRKD.js +52 -0
- package/dist/express/index.d.ts +13 -10
- package/dist/express/index.js +12 -11
- package/dist/fastify/index.d.ts +25 -22
- package/dist/fastify/index.js +13 -12
- package/dist/fetch-adapter-g0ypLkID.js +120 -0
- package/dist/fetch-adapter-qKJBuRqy.d.ts +31 -0
- package/dist/fields-D9tPVUUS.js +203 -0
- package/dist/files-C5ZI27eV.js +124 -0
- package/dist/{fixtures-h2eG2OZV.js → fixtures-B7XrLeY7.js} +8 -7
- package/dist/flags-C3DktBDY.js +86 -0
- package/dist/{from-snapshot-7REQZe0H.js → from-snapshot-D2lZt4Dd.js} +55 -52
- package/dist/{grantee-CXY-0C87.js → grantee-Cn1HyUsI.js} +3 -3
- package/dist/{grantee-conditions-MQs5NIuX.js → grantee-conditions-CcXf8hMM.js} +4 -3
- package/dist/{grantee-match-fxYp9Nmt.js → grantee-match-DHArqY82.js} +5 -4
- package/dist/h3/index.d.ts +37 -0
- package/dist/h3/index.js +35 -0
- package/dist/headers-BIjfMaoC.js +8 -0
- package/dist/headers-CIbDTUTK.d.ts +7 -0
- package/dist/{helpers-C9YmyBIa.js → helpers-DTcZOzhF.js} +45 -38
- package/dist/hono/index.d.ts +12 -9
- package/dist/hono/index.js +13 -12
- package/dist/hosted-BZIAYvon.js +315 -0
- package/dist/hosted-DBDX0M-P.d.ts +78 -0
- package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
- package/dist/http-DXJ37RDA.js +46 -0
- package/dist/{http-BEbdMZ05.js → http-ujKm0JXE.js} +1 -2
- package/dist/{import-UHk1N7vH.js → import-CNV7I2Dw.js} +25 -20
- package/dist/{import-BFIlhetD.js → import-DMmOG27O.js} +190 -53
- package/dist/index-BcHcPyIo.d.ts +110 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index-Chrs5fQ-.d.ts +210 -0
- package/dist/index-CxGUswIC.d.ts +431 -0
- package/dist/index-D8EovfsP.d.ts +91 -0
- package/dist/{index-UdVQgq48.d.ts → index-DOVf7RJv.d.ts} +431 -81
- package/dist/{index-BwMe94Xq.d.ts → index-SGdIQyzc.d.ts} +34 -16
- package/dist/index.d.ts +19 -15
- package/dist/index.js +31 -21
- package/dist/init-DeMYsnYJ.js +96 -0
- package/dist/init-GG6Jh8HD.js +95 -0
- package/dist/{instance-options-plkrsAog.d.ts → instance-options-Omfd793B.d.ts} +2 -2
- package/dist/instance-snapshot-Cxq4ygFc.js +600 -0
- package/dist/instant-BluqDvyS.js +19 -0
- package/dist/internals-Dk8A8fX3.js +17 -0
- package/dist/{json-schema-DBkdUuCO.js → json-schema-B-vAwlde.js} +45 -6
- package/dist/json-schema-CN_iLFH2.d.ts +40 -0
- package/dist/jwt/index.d.ts +18 -11
- package/dist/jwt/index.js +156 -135
- package/dist/{kernel-CB3LrIJM.js → kernel-5JatXyND.js} +52 -24
- package/dist/kysely/index.d.ts +5 -3
- package/dist/kysely/index.js +2 -2
- package/dist/load-BxEtwlBS.js +1 -0
- package/dist/{load-C3_oTHyO.js → load-DFS_ry0W.js} +22 -6
- package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
- package/dist/login-Dfe5RlYk.js +68 -0
- package/dist/login-imhuwvn3.js +254 -0
- package/dist/manifest-C8IzW3Do.js +42 -0
- package/dist/manifest-CtKR3GXL.js +370 -0
- package/dist/manifest-json-CAGzls-C.js +9 -0
- package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
- package/dist/manifest-schema-C0kK_8V0.js +399 -0
- package/dist/{map-claims-AVlBaJoa.js → map-claims-D3oRmTk0.js} +13 -10
- package/dist/{map-error-BE27PYkE.js → map-error-TCAdw-eW.js} +3 -3
- package/dist/{markers-CiwAOZtO.js → markers-C6joDI3o.js} +2 -1
- package/dist/mcp/index.d.ts +22 -12
- package/dist/mcp/index.js +57 -39
- package/dist/{migrate-BwlB5-Wk.js → migrate-BShP7Vf4.js} +53 -14
- package/dist/nest/index.d.ts +12 -9
- package/dist/nest/index.js +15 -14
- package/dist/next/index.d.ts +18 -21
- package/dist/next/index.js +13 -11
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +2 -2
- package/dist/node/index.d.ts +10 -10
- package/dist/node/index.js +7 -9
- package/dist/{ocsf-uYFXGVw2.js → ocsf-BFp-PZZA.js} +22 -11
- package/dist/openai/index.d.ts +9 -16
- package/dist/openai/index.js +17 -24
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-DBzHQbov.js → openapi-B7HZ0f-r.js} +8 -8
- package/dist/{openapi-BJdBmYDG.js → openapi-DTaEmTSW.js} +10 -9
- package/dist/openapi-ahr8rHu3.js +138 -0
- package/dist/operation-findings-DgATDpvh.js +36 -0
- package/dist/operations-CxybZErY.d.ts +59 -0
- package/dist/orpc/index.d.ts +13 -10
- package/dist/orpc/index.js +20 -15
- package/dist/otel/index.d.ts +3 -2
- package/dist/otel/index.js +30 -7
- package/dist/output-Dyw8fyPF.js +88 -0
- package/dist/output-files-B3IZJksC.js +1 -0
- package/dist/{output-files-CgIsvdSa.js → output-files-DhRBBE4n.js} +24 -26
- package/dist/{permission-keys-B26jeyUv.js → ownership-BatFgAA4.js} +1698 -869
- package/dist/{package-root-DtNnpmUd.js → package-root-Dk_ETdkp.js} +1 -1
- package/dist/{parse-CVFQHk6c.js → parse-Cxq0WxrA.js} +2 -2
- package/dist/{parse-local-manifest-C-q02Pal.js → parse-local-manifest-CVSs6J9I.js} +1 -1
- package/dist/{parse-local-manifest-DvYUR0QL.d.ts → parse-local-manifest-DgqtruPn.d.ts} +1 -1
- package/dist/parse-request-D_tUXG6M.js +26 -0
- package/dist/paths-DXSHAMIa.js +42 -0
- package/dist/pdp/index.d.ts +7 -3
- package/dist/pdp/index.js +45 -26
- package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
- package/dist/peer-CRvC7Hg1.js +1 -0
- package/dist/permdock-B9JxkBwC.js +868 -0
- package/dist/permdock-D7eeLJER.d.ts +502 -0
- package/dist/{permission-registry-BsblpB-z.js → permission-registry-Br4jSNVi.js} +3 -3
- package/dist/permissions-B-wCP5ZC.js +30 -0
- package/dist/{permissions-CCJ6lmxB.js → permissions-CZKiUfFM.js} +4 -3
- package/dist/{permissions-COH-KRgT.d.ts → permissions-oEWfl-kl.d.ts} +1 -2
- package/dist/policies-CO1oOymN.js +295 -0
- package/dist/{policy-ur0BBK_e.d.ts → policy-Czo4n3Hs.d.ts} +2 -2
- package/dist/{hosted-CBHgkzDl.js → policy-DLZJ5gAp.js} +17 -273
- package/dist/policy-from-catalog-DzhEc3Y9.js +450 -0
- package/dist/policy-module-CTlwMvLI.js +89 -0
- package/dist/{policy-types-sp9P1iIV.d.ts → policy-types-DzwivBgR.d.ts} +434 -631
- package/dist/postgrest-CW0IqC-U.js +566 -0
- package/dist/postgrest-NUqZT-RB.d.ts +100 -0
- package/dist/powersync-BHfuehf3.js +62 -0
- package/dist/powersync-CbzkqL_M.js +1 -0
- package/dist/{powersync-cZe4kzhL.js → powersync-bYUDLOAO.js} +23 -24
- package/dist/preset/index.d.ts +146 -0
- package/dist/preset/index.js +1 -0
- package/dist/preset-DZdU0tne.js +1768 -0
- package/dist/principal-source-pHneukrx.js +103 -0
- package/dist/prisma/index.d.ts +6 -3
- package/dist/prisma/index.js +2 -2
- package/dist/{problem-CnWraVhC.js → problem-CTviyH5L.js} +13 -23
- package/dist/{problem-details-rIIo8B2C.d.ts → problem-details-DXlt-vks.d.ts} +1 -1
- package/dist/{problem-details-DECUnXAn.js → problem-details-cn5X30gZ.js} +43 -40
- package/dist/problem-types-CJ2325SZ.js +5 -0
- package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
- package/dist/{protected-B3_P7xuw.d.ts → protected-DEPHc2xV.d.ts} +16 -8
- package/dist/{protected-BN7BUdY1.js → protected-DfjY5mLA.js} +21 -4
- package/dist/{provider-Bi-0HNL3.js → provider-CC-u3bYP.js} +2 -2
- package/dist/{provider-kwHC9CZD.d.ts → provider-DMVt6yYn.d.ts} +1 -1
- package/dist/{provider-store-Ctt-81Ni.js → provider-store-B5Yrkhzr.js} +2 -2
- package/dist/pull-BgIfno6w.js +256 -0
- package/dist/pull-ByOks_Tg.js +1 -0
- package/dist/push-CmFVBOeI.js +116 -0
- package/dist/pushed-policy-BImMTTIM.d.ts +57 -0
- package/dist/pushed-policy-DobP7AHI.js +238 -0
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +6 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -2
- package/dist/react-native/index.d.ts +24 -72
- package/dist/react-native/index.js +24 -22
- package/dist/{refs-f3imMjNW.js → refs-K6eWUJ0H.js} +1 -1
- package/dist/{relation-walk-BXnlLtg2.js → relation-walk-3OyDBF0S.js} +2 -2
- package/dist/{relations-CvmSpG67.js → relations-BnCWnJGI.js} +3 -3
- package/dist/renames-CGj7YwcQ.js +175 -0
- package/dist/{replay-COpmZUfx.d.ts → replay-D7iKcLFe.d.ts} +1 -1
- package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
- package/dist/{request-context-BVM7aWNs.js → request-context-BTRTG7OT.js} +3 -3
- package/dist/request-schema-CMreVuFM.js +148 -0
- package/dist/resend/index.d.ts +126 -0
- package/dist/resend/index.js +379 -0
- package/dist/{resource-relations-D1Un3pcz.js → resource-relations-D_jw0BFx.js} +1 -1
- package/dist/{retire-BFcxL6un.js → retire-CALjIQ7e.js} +2 -2
- package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
- package/dist/rls-BA8an5Ap.js +336 -0
- package/dist/{rls-BEktUaqg.js → rls-j6t43t7n.js} +287 -689
- package/dist/{role-builder-B4GbKM-i.js → role-builder-CXbis5-X.js} +14 -207
- package/dist/{rpc-nYC3cR_u.js → rpc-aPyNi6tA.js} +7 -7
- package/dist/run-CNsApDpd.js +1 -0
- package/dist/run-DOfMtc9V.js +723 -0
- package/dist/run-ticket-BjhTOi6u.d.ts +124 -0
- package/dist/run-ticket-sxFI6LAk.js +337 -0
- package/dist/{runtime-D_tMXnsq.js → runtime-D3VTAiCA.js} +1 -1
- package/dist/{saas-DtaA4JuW.js → saas-FO-Q8oj0.js} +16 -14
- package/dist/scan-BOI5tJ-6.js +29 -0
- package/dist/{schema-DjQBcAP9.js → schema-B4v0Qrac.js} +102 -8
- package/dist/schema-yLuBWWsh.js +130 -0
- package/dist/scim/index.d.ts +2 -2
- package/dist/scim/index.js +1 -2
- package/dist/{scim-vFR1O5oG.js → scim-ClHtpyV3.js} +208 -76
- package/dist/{scopes-tvpa34ep.js → scopes-CUPSjo9q.js} +12 -7
- package/dist/server/index.d.ts +12 -143
- package/dist/server/index.js +12 -9
- package/dist/server-DqCT4CgZ.js +453 -0
- package/dist/{server-types-DdqBwZN_.d.ts → server-types-BLd4MnBX.d.ts} +51 -68
- package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
- package/dist/{shared-1uhI-e-b.js → shared-CD11nBbC.js} +18 -8
- package/dist/{shared-JhMeOsDF.js → shared-DvvFzYXe.js} +8 -9
- package/dist/signals-D3NcTKUS.js +32 -0
- package/dist/sink-BEZSlMS_.js +957 -0
- package/dist/sink-Bj_lDUDq.d.ts +225 -0
- package/dist/{skills-BZdRQkxs.js → skills-B6lVdD_W.js} +13 -6
- package/dist/skills-CTYwkl44.js +51 -0
- package/dist/snapshot-CXUWfsNy.js +269 -0
- package/dist/{snapshot-cache-Cod40D-X.js → snapshot-cache-DZHQ0KF6.js} +11 -5
- package/dist/{snapshot-cache-J5T7t_vD.d.ts → snapshot-cache-DqK9eDmM.d.ts} +7 -5
- package/dist/solid/index.d.ts +11 -12
- package/dist/solid/index.js +7 -7
- package/dist/solid-start/index.d.ts +52 -0
- package/dist/solid-start/index.js +49 -0
- package/dist/{sources-Dljy06zL.d.ts → sources-BV9j0DeZ.d.ts} +7 -13
- package/dist/{sources-B1-b-R0t.js → sources-vs5dGOsS.js} +125 -119
- package/dist/{sql-t8teWOfo.js → sql-B5T1IL83.js} +13 -1
- package/dist/src-DLfUWFAI.js +1851 -0
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +384 -47
- package/dist/standard-webhooks-6lg8v_is.d.ts +88 -0
- package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
- package/dist/storage-C2bWdvJH.js +132 -0
- package/dist/{store-ILkLUj_o.js → store-4u_hxjWa.js} +40 -53
- package/dist/{store-options-CbUHDoSe.js → store-options-BqEakYRa.js} +26 -21
- package/dist/{stores-BHXdSbw9.js → stores-BbsD4fAR.js} +2 -2
- package/dist/{stores-Cxz_80g-.d.ts → stores-ClZUgeoP.d.ts} +7 -8
- package/dist/{stream-Dwsg6WwE.d.ts → stream-7-xmnf3U.d.ts} +2 -2
- package/dist/{stream-BMui98KK.js → stream-CvdUS9V1.js} +9 -2
- package/dist/style-DiKWnAvn.js +1 -0
- package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
- package/dist/subject-BGoM5o8B.js +17 -0
- package/dist/supabase/index.d.ts +23 -106
- package/dist/supabase/index.js +5 -4
- package/dist/supabase/middleware.d.ts +7 -6
- package/dist/supabase/middleware.js +13 -11
- package/dist/supabase-9pqI57pW.js +92 -0
- package/dist/{supabase-C1TWlnYt.js → supabase-Dxlp8A94.js} +98 -205
- package/dist/supabase-hook-CXcGpHqq.js +1 -0
- package/dist/{supabase-hook-Cva3ikXz.js → supabase-hook-DdDQhMlw.js} +951 -60
- package/dist/support-session-C6xJQYut.js +158 -0
- package/dist/svelte/Protected.svelte +4 -4
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +10 -10
- package/dist/svelte/runtime.d.ts +3 -3
- package/dist/svelte/runtime.js +3 -3
- package/dist/svelte/source.d.ts +7 -7
- package/dist/svelte/source.js +4 -4
- package/dist/sveltekit/index.d.ts +51 -0
- package/dist/sveltekit/index.js +27 -0
- package/dist/tanstack-start/index.d.ts +51 -0
- package/dist/tanstack-start/index.js +40 -0
- package/dist/target-Bw37TFNA.js +51 -0
- package/dist/{tenancy-DshjqY0n.js → tenancy-DN2QAx9z.js} +5 -5
- package/dist/terminal/index.d.ts +11 -9
- package/dist/terminal/index.js +23 -287
- package/dist/testing/index.d.ts +311 -38
- package/dist/testing/index.js +3475 -201
- package/dist/testing/saas/index.d.ts +9 -8
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -2
- package/dist/text-BMFRfBqc.js +8 -0
- package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
- package/dist/thenable-yD7SabqG.js +31 -0
- package/dist/time-DkWjdL98.js +71 -0
- package/dist/{token-Bx9qy30p.js → token-BLZcjQgt.js} +15 -6
- package/dist/{tool-refusal-CQ7OEia9.js → tool-refusal-CMLN5xhn.js} +2 -2
- package/dist/trpc/index.d.ts +12 -9
- package/dist/trpc/index.js +16 -14
- package/dist/{types-7rJVLWe5.d.ts → types-5Ut_pbKy.d.ts} +61 -11
- package/dist/{store-9KO4BlkB.d.ts → types-BNORmDM8.d.ts} +100 -75
- package/dist/types-BjV-fqcA.d.ts +57 -0
- package/dist/{types-CUcFZfpU.d.ts → types-BqAKcRNW.d.ts} +2 -1
- package/dist/types-C8-KNsFf.d.ts +107 -0
- package/dist/{types-Br9IXUBG.d.ts → types-CCpc2Dcv.d.ts} +77 -17
- package/dist/{types-DpWfQdSh.d.ts → types-CZTnPFzM.d.ts} +46 -11
- package/dist/{types-CqrjB9o6.d.ts → types-CdlxsYQh.d.ts} +23 -11
- package/dist/{types-CIlTojhq.d.ts → types-ClZaWzfb.d.ts} +9 -6
- package/dist/types-DX0LaKTb.d.ts +157 -0
- package/dist/types-DiQjupG2.d.ts +332 -0
- package/dist/types-DqzsnlSA.js +67 -0
- package/dist/types-_sHy8BQf.d.ts +65 -0
- package/dist/{types-ByUo56un.d.ts → types-fbZ7JEXr.d.ts} +23 -22
- package/dist/{types-DHrmBJLH.d.ts → types-tIgIeIKJ.d.ts} +7 -7
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CHOtwL19.js +1 -0
- package/dist/usage-DHtE8j4E.js +32 -0
- package/dist/{usage-CG9nOYZw.js → usage-T5mXq98v.js} +54 -11
- package/dist/{validation-error-BSSw0l_8.d.ts → validation-error-Bg5Tpluk.d.ts} +1 -1
- package/dist/{validity-dVrUEPJt.js → validity-CZ7-y0Cj.js} +32 -15
- package/dist/{verifier-DMr_Zmb_.js → verifier-DHuh-okk.js} +18 -7
- package/dist/{verify-BztUB3GX.js → verify-DbCL9bnB.js} +645 -50
- package/dist/{version-BEogC-rB.js → version-CEbQ5UeM.js} +4 -3
- package/dist/version-DXkYUISx.js +1 -0
- package/dist/{views-CJsrBS1f.js → views-DTIIO-oQ.js} +22 -5
- package/dist/{permission-types-Dg39z9Kv.d.ts → vocabulary-D6lPdb4Z.d.ts} +65 -2
- package/dist/vue/index.d.ts +9 -10
- package/dist/vue/index.js +8 -8
- package/dist/waiter-BGUtGYoU.js +67 -0
- package/dist/waiter-CKy4MQSY.d.ts +36 -0
- package/dist/watch-Cb-NcaUw.js +1 -0
- package/dist/{watch-zeucB7eF.js → watch-CszoTJMq.js} +44 -5
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +3 -3
- package/dist/where-scope-Dv-WTP-w.js +306 -0
- package/dist/who-can-D0QoYbEb.js +364 -0
- package/dist/wire-delegation-Cv3W2eaM.js +36 -0
- package/dist/wire-denial-C7PaVYEM.js +27 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.js +223 -0
- package/dist/workflow-sdk/index.d.ts +56 -0
- package/dist/workflow-sdk/index.js +78 -0
- package/dist/{wrap-Cr93qqdP.d.ts → wrap-Bd1Tejur.d.ts} +2 -1
- package/dist/{wrap-DWsU_5Bd.js → wrap-gX61EtpB.js} +7 -6
- package/package.json +136 -37
- package/schemas/access-event-v1.json +118 -0
- package/schemas/approval-request-v1.json +34 -1
- package/schemas/approval-signature-v1.json +94 -0
- package/schemas/automation-event-v1.json +113 -0
- package/schemas/capability-v1.json +84 -0
- package/schemas/catalog-event-v1.json +87 -0
- package/schemas/catalog-v1.json +123 -12
- package/schemas/cli-commands-v1.json +164 -0
- package/schemas/cli-result-v1.json +96 -0
- package/schemas/cloud-api-v1.json +5407 -0
- package/schemas/connector-v1.json +408 -0
- package/schemas/credential-event-v1.json +81 -0
- package/schemas/credential-v1.json +134 -0
- package/schemas/decision-event-v1.json +288 -0
- package/schemas/delivery-event-v1.json +117 -0
- package/schemas/directory-event-v1.json +85 -0
- package/schemas/evidence-export-v1.json +54 -0
- package/schemas/membership-event-v1.json +93 -0
- package/schemas/policy-v1.json +68 -0
- package/schemas/pushed-policy-v1.json +236 -0
- package/schemas/run-v1.json +162 -0
- package/schemas/snapshot-v1.json +138 -0
- package/schemas/supabase-claims-v1.json +4 -4
- package/schemas/supabase-manifest-v1.json +5 -1
- package/schemas/support-session-v1.json +111 -0
- package/skills/permdock/SKILL.md +16 -12
- package/skills/permdock-agents/SKILL.md +40 -23
- package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
- package/skills/permdock-agents/references/runtimes.md +22 -18
- package/skills/permdock-approvals/SKILL.md +29 -13
- package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +5 -5
- package/skills/permdock-credentials/references/tokens-keys-links.md +14 -10
- package/skills/permdock-data/SKILL.md +6 -6
- package/skills/permdock-data/references/queries-and-rls.md +9 -8
- package/skills/permdock-data/references/relationships.md +3 -3
- package/skills/permdock-tenancy/SKILL.md +20 -7
- package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +5 -4
- package/skills/permdock-wire/SKILL.md +17 -11
- package/skills/permdock-wire/references/adapters.md +139 -51
- package/skills/permdock-wire/references/policy-options.md +5 -5
- package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
- package/dist/approvals-DZL76lKW.js +0 -240
- package/dist/arazzo-CD8d0WLb.js +0 -54
- package/dist/catalog-6yWAX6Ps.js +0 -2
- package/dist/catalog-WHG4kBq7.js +0 -48
- package/dist/cloud-C7bSDgX4.js +0 -50
- package/dist/cloud-PeBN3IEn.js +0 -118
- package/dist/collect-Bw2Jdq1q.js +0 -2
- package/dist/collect-CQx4hX4N.js +0 -83
- package/dist/commands-Cpip4ULx.js +0 -97
- package/dist/config-DLt6BvV7.js +0 -36
- package/dist/diff-Cnghh_Ip.js +0 -48
- package/dist/doctor-CuVJvhV_.js +0 -53
- package/dist/duration-xK2jGFVc.js +0 -25
- package/dist/errors-CWfoNg9N.js +0 -47
- package/dist/errors-DRnGZHzF.js +0 -30
- package/dist/expiry-BoBJ8Tvc.js +0 -7
- package/dist/fields-fwfXN01n.js +0 -1016
- package/dist/headers-BZSDieNT.js +0 -6
- package/dist/headers-Cf-zWvsO.d.ts +0 -6
- package/dist/ids-DC4V4CFu.js +0 -18
- package/dist/index-DgM7w96K.d.ts +0 -6
- package/dist/index-Dz7didpC.d.ts +0 -201
- package/dist/load-B_shHrmd.js +0 -2
- package/dist/manifest-BngWKryL.js +0 -770
- package/dist/may-use-BOn_dR_w.js +0 -78
- package/dist/openapi-DcQ1Kg40.js +0 -164
- package/dist/ownership-BgnbOJFT.js +0 -851
- package/dist/permdock-L26udHSk.js +0 -3646
- package/dist/powersync-BFSbSElQ.js +0 -62
- package/dist/powersync-DCDgZgfZ.js +0 -2
- package/dist/rls-55DQTuf5.js +0 -250
- package/dist/run-DMcNW_8T.js +0 -174
- package/dist/server-ZYEHtNHS.js +0 -268
- package/dist/sink-BTtmfPnE.js +0 -141
- package/dist/sink-CPN4zLfs.d.ts +0 -104
- package/dist/skills-Dle4ZoLF.js +0 -56
- package/dist/snapshot-Dc9CHPME.js +0 -1078
- package/dist/src-Bb7no-hS.js +0 -639
- package/dist/subject-C6dLxqHU.js +0 -24
- package/dist/supabase-Dbqr141s.js +0 -83
- package/dist/supabase-hook-BULg7Ut_.js +0 -2
- package/dist/thenable-QSmvt-vZ.js +0 -65
- package/dist/types-C0dee0Qf.d.ts +0 -197
- package/dist/types-DvupZE0w.d.ts +0 -9
- package/dist/usage-4G6t643F.js +0 -42
- package/dist/usage-iFLJ0-rr.js +0 -2
- package/dist/vocabulary-hhEFLFoL.d.ts +0 -65
- package/dist/watch-C8TpsAl4.js +0 -2
|
@@ -1,17 +1,647 @@
|
|
|
1
|
-
import { r as isRecord } from "../compact-CAf557ZB.js";
|
|
1
|
+
import { r as isRecord, t as compact } from "../compact-CAf557ZB.js";
|
|
2
2
|
import { n as freezeDeep } from "../freeze-CrU8nQeX.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { l as asApprover } from "../role-builder-CXbis5-X.js";
|
|
4
|
+
import { a as holder, i as flattenApprovers, r as approverLeaves } from "../approvers-CqPOLD-o.js";
|
|
5
|
+
import { t as loadPrincipal } from "../principal-source-pHneukrx.js";
|
|
6
|
+
import { a as isId, r as idList } from "../expiry-D4_YKRKD.js";
|
|
7
|
+
import { c as listPermissions, n as findPermission, o as isPermission } from "../permission-registry-Br4jSNVi.js";
|
|
8
|
+
import { i as dateAt, m as rfc3339, p as nowSeconds, u as isoAt } from "../time-DkWjdL98.js";
|
|
9
|
+
import { t as canonicalJson } from "../canonical-json-BC8_nMTw.js";
|
|
10
|
+
import { r as sha256Digest } from "../sha256-D9ihqmvd.js";
|
|
11
|
+
import { t as decideSupportSession } from "../support-session-C6xJQYut.js";
|
|
12
|
+
import { d as scopeList } from "../scopes-CUPSjo9q.js";
|
|
13
|
+
import { o as flattenGrantee } from "../grantee-Cn1HyUsI.js";
|
|
14
|
+
import { t as byCodePoint } from "../compare-pfyVnBjE.js";
|
|
15
|
+
import { o as scopesReaching, t as getPermDock } from "../permdock-B9JxkBwC.js";
|
|
16
|
+
import { t as anonymousSubject } from "../subject-BGoM5o8B.js";
|
|
17
|
+
import { i as linkPolicyViolation, n as capabilityOf, r as capabilitySubject } from "../capability-N1K9IpqS.js";
|
|
18
|
+
import { r as credentialSubject, s as parseCredential } from "../credential-DM2um3ha.js";
|
|
19
|
+
import { t as AccessReviewError } from "../errors-BDUwg2qC.js";
|
|
9
20
|
import { t as policyRowConditionKeys } from "../row-conditions-h2HythXQ.js";
|
|
10
|
-
import {
|
|
11
|
-
import "../
|
|
12
|
-
import
|
|
13
|
-
import { t as
|
|
21
|
+
import { n as subscribersFor } from "../audience-Dg8XsRaO.js";
|
|
22
|
+
import { l as sqlIdent, s as quoteSqlLiteral } from "../sql-B5T1IL83.js";
|
|
23
|
+
import "../sources-vs5dGOsS.js";
|
|
24
|
+
import { c as subjectFromSupabaseSession, t as callRpc } from "../postgrest-CW0IqC-U.js";
|
|
25
|
+
import { t as parseSupabaseManifest } from "../manifest-C8IzW3Do.js";
|
|
26
|
+
import { t as parseCatalog } from "../parse-Cxq0WxrA.js";
|
|
14
27
|
import { AsyncResult, dbError } from "better-supabase";
|
|
28
|
+
//#region src/better-supabase/access-reviews.ts
|
|
29
|
+
const NOT_MEMBER = "ORGANIZATION_NOT_MEMBER";
|
|
30
|
+
/** The `hint` better-supabase's block functions raise their code in, or the SQLSTATE. */
|
|
31
|
+
function errorCode(error) {
|
|
32
|
+
if ("hint" in error && typeof error.hint === "string" && error.hint !== "") return error.hint;
|
|
33
|
+
return "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
34
|
+
}
|
|
35
|
+
var RpcFailure = class extends Error {
|
|
36
|
+
code;
|
|
37
|
+
constructor(fn, error) {
|
|
38
|
+
super(`${fn}: ${error.message}`);
|
|
39
|
+
this.code = errorCode(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Whether a member holding `role` at `scope` holds `key` through it. A role
|
|
44
|
+
* the policy does not declare, such as a custom role, counts as holding it.
|
|
45
|
+
*/
|
|
46
|
+
function roleGrants(policy, role, scope, key) {
|
|
47
|
+
if (!policy.index.declaredRoles.has(role)) return true;
|
|
48
|
+
return policy.grants.some((grant) => grant.effect === "allow" && grant.permission.key === key && flattenGrantee(grant.to).some((item) => item.kind === "role" && item.role === role && item.scope === scope));
|
|
49
|
+
}
|
|
50
|
+
function fallbackRole(demoteTo, via) {
|
|
51
|
+
if (demoteTo === void 0 || typeof demoteTo === "string") return demoteTo;
|
|
52
|
+
for (const role of via) if (Object.hasOwn(demoteTo, role)) return demoteTo[role];
|
|
53
|
+
}
|
|
54
|
+
function invitationsOf(data) {
|
|
55
|
+
if (!Array.isArray(data)) return [];
|
|
56
|
+
return data.flatMap((row) => isRecord(row) && typeof row["id"] === "string" && typeof row["email"] === "string" && typeof row["role"] === "string" ? [{
|
|
57
|
+
id: row["id"],
|
|
58
|
+
email: row["email"],
|
|
59
|
+
role: row["role"]
|
|
60
|
+
}] : []);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Applies the revoked items of an access review to better-supabase's
|
|
64
|
+
* organizations and invitations blocks, then marks each one applied. A
|
|
65
|
+
* revoked member moves to the `demoteTo` role when the policy says that
|
|
66
|
+
* role lacks the permission, and is removed otherwise. With `email`, the
|
|
67
|
+
* tenant's open invitations to the member whose role grants the permission
|
|
68
|
+
* are revoked too. A member who is no longer in the organization counts as
|
|
69
|
+
* applied. Any other error leaves the item unapplied in `failed`, and one
|
|
70
|
+
* item's failure never stops the others. Throws `review-not-found` for an
|
|
71
|
+
* unknown review.
|
|
72
|
+
*/
|
|
73
|
+
function accessReviewApplier(options) {
|
|
74
|
+
const schema = options.schema ?? "better_supabase";
|
|
75
|
+
const scope = options.scope ?? options.policy.scopes[0]?.name ?? "tenant";
|
|
76
|
+
const rpc = async (fn, args) => {
|
|
77
|
+
const result = await callRpc(options.client, schema, fn, args);
|
|
78
|
+
if (result.error !== null) throw new RpcFailure(fn, result.error);
|
|
79
|
+
return result.data;
|
|
80
|
+
};
|
|
81
|
+
const changeMember = async (review, item) => {
|
|
82
|
+
const role = fallbackRole(options.demoteTo, item.via);
|
|
83
|
+
try {
|
|
84
|
+
if (role !== void 0 && !roleGrants(options.policy, role, scope, item.permission)) await rpc("update_member_role", {
|
|
85
|
+
organization: review.tenant,
|
|
86
|
+
member: item.principal,
|
|
87
|
+
role
|
|
88
|
+
});
|
|
89
|
+
else await rpc("remove_member", {
|
|
90
|
+
organization: review.tenant,
|
|
91
|
+
member: item.principal
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof RpcFailure && (error.code === NOT_MEMBER || error.code === "P0002")) return;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const revokeInvitations = async (review, item) => {
|
|
99
|
+
const email = await options.email?.(item.principal);
|
|
100
|
+
if (email === void 0 || email === null || email === "") return;
|
|
101
|
+
const wanted = email.toLowerCase();
|
|
102
|
+
const open = invitationsOf(await rpc("list_organization_invitations", { organization: review.tenant }));
|
|
103
|
+
await Promise.all(open.filter((invitation) => invitation.email.toLowerCase() === wanted && roleGrants(options.policy, invitation.role, scope, item.permission)).map((invitation) => rpc("revoke_invitation", { invitation_id: invitation.id })));
|
|
104
|
+
};
|
|
105
|
+
const stillHeld = async (review, item) => {
|
|
106
|
+
if (options.permdockFor === void 0) return false;
|
|
107
|
+
const permission = findPermission(options.policy.permissions, item.permission);
|
|
108
|
+
if (permission === void 0) throw new Error(`${item.permission} is not in the policy, so the change cannot be checked`);
|
|
109
|
+
const decide = (await options.permdockFor(item.principal, review.tenant)).decide;
|
|
110
|
+
return decide(permission).outcome !== "denied";
|
|
111
|
+
};
|
|
112
|
+
/** `undefined` once the item is marked applied, else why it is not. */
|
|
113
|
+
const applyItem = async (review, item) => {
|
|
114
|
+
try {
|
|
115
|
+
await changeMember(review, item);
|
|
116
|
+
await revokeInvitations(review, item);
|
|
117
|
+
if (await stillHeld(review, item)) return `${item.principal} still holds ${item.permission} after the change`;
|
|
118
|
+
await options.reviews.markApplied(review.id, item.id, dateAt(options.now).toISOString());
|
|
119
|
+
return;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return error instanceof Error ? error.message : String(error);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
return async (reviewId) => {
|
|
125
|
+
const review = await options.reviews.get(reviewId);
|
|
126
|
+
if (review === null) throw new AccessReviewError("review-not-found", `access review ${reviewId} not found`);
|
|
127
|
+
const applied = [];
|
|
128
|
+
const failed = [];
|
|
129
|
+
let cursor;
|
|
130
|
+
do {
|
|
131
|
+
const page = await options.reviews.items({
|
|
132
|
+
review: reviewId,
|
|
133
|
+
status: "revoked",
|
|
134
|
+
...cursor === void 0 ? {} : { cursor }
|
|
135
|
+
});
|
|
136
|
+
const pending = page.items.filter((item) => item.appliedAt === void 0);
|
|
137
|
+
const causes = await Promise.all(pending.map((item) => applyItem(review, item)));
|
|
138
|
+
for (const [index, item] of pending.entries()) {
|
|
139
|
+
const cause = causes[index];
|
|
140
|
+
if (cause === void 0) applied.push(item.id);
|
|
141
|
+
else failed.push({
|
|
142
|
+
item: item.id,
|
|
143
|
+
cause
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
cursor = page.next;
|
|
147
|
+
} while (cursor !== void 0);
|
|
148
|
+
return {
|
|
149
|
+
applied,
|
|
150
|
+
failed
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/better-supabase/api-keys.ts
|
|
156
|
+
const EPOCH = "1970-01-01T00:00:00.000Z";
|
|
157
|
+
const rfc3339Of = (instant) => isoAt(instant.epochMilliseconds);
|
|
158
|
+
/** When a key stops verifying: its expiry, or the end of a rotated key's grace period. */
|
|
159
|
+
function endOf(key) {
|
|
160
|
+
const { expiresAt, revokedAt } = key;
|
|
161
|
+
if (revokedAt === void 0) return expiresAt;
|
|
162
|
+
if (expiresAt === void 0) return revokedAt;
|
|
163
|
+
return revokedAt.epochMilliseconds < expiresAt.epochMilliseconds ? revokedAt : expiresAt;
|
|
164
|
+
}
|
|
165
|
+
/** `rls.apiKeys.serviceRoles` from the manifest, when it has `rls.apiKeys`. */
|
|
166
|
+
function manifestServiceRoles(manifest) {
|
|
167
|
+
return manifest === void 0 ? void 0 : parseSupabaseManifest(manifest).rls.apiKeys?.serviceRoles;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* A personal key as a `user` credential acting as its user, a tenant key as
|
|
171
|
+
* a `service` credential holding `roles` in its tenant; `null` when `*`
|
|
172
|
+
* has no expansion, a tenant key has no roles, or the record is invalid.
|
|
173
|
+
*/
|
|
174
|
+
function keyCredential(key, roles, allPermissions) {
|
|
175
|
+
const scopes = key.scopes.includes("*") ? allPermissions?.map((permission) => permission.key) : key.scopes;
|
|
176
|
+
if (scopes === void 0) return null;
|
|
177
|
+
const owner = key.userId;
|
|
178
|
+
const base = {
|
|
179
|
+
v: 1,
|
|
180
|
+
id: key.id,
|
|
181
|
+
permissions: scopes.map((permission) => ({ permission })),
|
|
182
|
+
createdBy: key.createdBy ?? owner ?? key.id,
|
|
183
|
+
createdAt: key.createdAt ?? EPOCH,
|
|
184
|
+
...key.expiresAt === void 0 ? {} : { expiresAt: key.expiresAt },
|
|
185
|
+
name: key.name
|
|
186
|
+
};
|
|
187
|
+
if (owner !== void 0) return parseCredential({
|
|
188
|
+
...base,
|
|
189
|
+
kind: "user",
|
|
190
|
+
principal: owner,
|
|
191
|
+
...key.organizationId === void 0 ? {} : { tenant: key.organizationId }
|
|
192
|
+
}) ?? null;
|
|
193
|
+
if (roles === void 0 || key.organizationId === void 0) return null;
|
|
194
|
+
return parseCredential({
|
|
195
|
+
...base,
|
|
196
|
+
kind: "service",
|
|
197
|
+
principal: key.id,
|
|
198
|
+
tenant: key.organizationId,
|
|
199
|
+
roles
|
|
200
|
+
}) ?? null;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* better-supabase's API keys as a `CredentialVerifier`, for
|
|
204
|
+
* `subjectFromApiKey` from `permdock/server`, which reads PermDock's
|
|
205
|
+
* `pdk_<id>_<secret>` format: create the keys with `prefix: "pdk"`. The
|
|
206
|
+
* credential id is the key's `publicId`, the id in the token. A personal key
|
|
207
|
+
* is a `user` credential acting as its user (inside its tenant when the key
|
|
208
|
+
* is limited to one), a tenant key a `service` credential holding
|
|
209
|
+
* `serviceRoles` in its tenant, and the key's scopes are the credential's
|
|
210
|
+
* permissions. better-supabase records the use when it verifies, so there is
|
|
211
|
+
* no `touch`. A rotated key in its grace period verifies, and its credential
|
|
212
|
+
* expires when the grace period ends. An invalid, revoked, expired or
|
|
213
|
+
* rate-limited key, and a failed lookup, are `null`. `permdock/server` exports a different
|
|
214
|
+
* `apiKeyVerifier`, over the application's own key records.
|
|
215
|
+
*/
|
|
216
|
+
function apiKeyVerifier(options) {
|
|
217
|
+
const fallback = manifestServiceRoles(options.manifest);
|
|
218
|
+
return freezeDeep({ async verify(secret) {
|
|
219
|
+
try {
|
|
220
|
+
const checked = await options.keys.verify(secret);
|
|
221
|
+
if (!checked.ok || checked.data.status !== "ok") return null;
|
|
222
|
+
const { key } = checked.data;
|
|
223
|
+
if (key.state !== "active" && key.state !== "grace") return null;
|
|
224
|
+
const end = endOf(key);
|
|
225
|
+
const roles = typeof options.serviceRoles === "function" ? options.serviceRoles(key) : options.serviceRoles ?? fallback;
|
|
226
|
+
return keyCredential({
|
|
227
|
+
id: key.publicId,
|
|
228
|
+
name: key.name,
|
|
229
|
+
scopes: key.scopes,
|
|
230
|
+
createdAt: rfc3339Of(key.createdAt),
|
|
231
|
+
...key.organizationId === void 0 ? {} : { organizationId: key.organizationId },
|
|
232
|
+
...key.userId === void 0 ? {} : { userId: key.userId },
|
|
233
|
+
...key.createdBy === void 0 ? {} : { createdBy: key.createdBy },
|
|
234
|
+
...end === void 0 ? {} : { expiresAt: rfc3339Of(end) }
|
|
235
|
+
}, roles, options.allPermissions);
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
} });
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The `claim` and `tenantClaim` options of better-supabase's
|
|
243
|
+
* `apiKeyClaims()`, from the manifest's `rls.apiKeys`, so a key's token
|
|
244
|
+
* carries the scopes ceiling, tenant and roles the `rls generate` helpers
|
|
245
|
+
* read. Throws when the manifest has no `rls.apiKeys`.
|
|
246
|
+
*/
|
|
247
|
+
function apiKeyClaimOptions(manifest) {
|
|
248
|
+
const rls = parseSupabaseManifest(manifest).rls;
|
|
249
|
+
const apiKeys = rls.apiKeys;
|
|
250
|
+
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`.");
|
|
251
|
+
return freezeDeep({
|
|
252
|
+
claim: {
|
|
253
|
+
name: apiKeys.claim,
|
|
254
|
+
scopes: apiKeys.scopes,
|
|
255
|
+
tenant: apiKeys.tenant,
|
|
256
|
+
roles: apiKeys.roles,
|
|
257
|
+
serviceRoles: apiKeys.serviceRoles
|
|
258
|
+
},
|
|
259
|
+
tenantClaim: rls.tenantClaim
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/better-supabase/subject.ts
|
|
264
|
+
/** `session` with the short codes in `claim` turned back into feature keys. */
|
|
265
|
+
function decodedFeatures(session, claim, keys) {
|
|
266
|
+
const claims = session?.claims;
|
|
267
|
+
if (session === null || session === void 0 || !isRecord(claims)) return session;
|
|
268
|
+
const coded = claims[claim];
|
|
269
|
+
if (!isRecord(coded)) return session;
|
|
270
|
+
const byCode = new Map(Object.entries(keys).map(([key, code]) => [code, key]));
|
|
271
|
+
const decoded = Object.fromEntries(Object.entries(coded).map(([tenant, codes]) => [tenant, Array.isArray(codes) ? codes.flatMap((code) => {
|
|
272
|
+
const key = typeof code === "string" ? byCode.get(code) : void 0;
|
|
273
|
+
return key === void 0 ? [] : [key];
|
|
274
|
+
}) : codes]));
|
|
275
|
+
return {
|
|
276
|
+
...session,
|
|
277
|
+
claims: {
|
|
278
|
+
...claims,
|
|
279
|
+
[claim]: decoded
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function isApiKeySession(session) {
|
|
284
|
+
if (typeof session !== "object" || session === null) return false;
|
|
285
|
+
const record = Object.fromEntries(Object.entries(session));
|
|
286
|
+
return record["kind"] === "apiKey" && typeof record["keyId"] === "string" && typeof record["name"] === "string" && Array.isArray(record["scopes"]) && record["scopes"].every((scope) => typeof scope === "string");
|
|
287
|
+
}
|
|
288
|
+
function apiKeySubject(session, options) {
|
|
289
|
+
const roles = typeof options.serviceRoles === "function" ? options.serviceRoles(session) : options.serviceRoles ?? manifestServiceRoles(options.manifest);
|
|
290
|
+
const credential = keyCredential({
|
|
291
|
+
id: session.keyId,
|
|
292
|
+
name: session.name,
|
|
293
|
+
scopes: session.scopes,
|
|
294
|
+
...session.organizationId === void 0 ? {} : { organizationId: session.organizationId },
|
|
295
|
+
...session.userId === void 0 ? {} : { userId: session.userId },
|
|
296
|
+
...session.createdBy === void 0 ? {} : { createdBy: session.createdBy },
|
|
297
|
+
...session.createdAt === void 0 || !Number.isFinite(session.createdAt) ? {} : { createdAt: rfc3339(session.createdAt) }
|
|
298
|
+
}, roles, listPermissions(options.permissions));
|
|
299
|
+
return credential === null ? anonymousSubject() : credentialSubject(credential, { permissions: options.permissions });
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* `subjectFromSupabaseSession` for better-supabase's `AuthSession`, reading
|
|
303
|
+
* `memberships` for the memberships and the entitlements module's `features`
|
|
304
|
+
* claim for `principal.plans` (`plans` names another claim). With `apiKeys`,
|
|
305
|
+
* a verified `apiKey` session is the key's credential subject: a personal key
|
|
306
|
+
* acts as its user within the key's scopes, a tenant key as a `service`
|
|
307
|
+
* principal holding `serviceRoles`. Without `apiKeys`, and for `anon`,
|
|
308
|
+
* `service` and `invalid` sessions, the subject is anonymous.
|
|
309
|
+
*/
|
|
310
|
+
function subjectFromBetterSupabase(session, options = {}) {
|
|
311
|
+
const { apiKeys, plans = "features", claims, ...rest } = options;
|
|
312
|
+
if (isApiKeySession(session)) return apiKeys === void 0 ? anonymousSubject() : apiKeySubject(session, apiKeys);
|
|
313
|
+
const claim = typeof plans === "string" ? plans : plans.claim ?? "features";
|
|
314
|
+
const keys = typeof plans === "string" ? void 0 : plans.keys;
|
|
315
|
+
return subjectFromSupabaseSession(keys === void 0 ? session : decodedFeatures(session, claim, keys), {
|
|
316
|
+
...rest,
|
|
317
|
+
claims: {
|
|
318
|
+
memberships: "memberships",
|
|
319
|
+
...claims,
|
|
320
|
+
plans: claim
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/better-supabase/subscribers.ts
|
|
326
|
+
function permissionFor(events, type) {
|
|
327
|
+
if (Object.hasOwn(events, type)) return events[type];
|
|
328
|
+
let best;
|
|
329
|
+
for (const pattern of Object.keys(events)) if ((pattern === "*" || pattern.endsWith(".*") && type.startsWith(pattern.slice(0, -1))) && (best === void 0 || pattern.length > best.length)) best = pattern;
|
|
330
|
+
return best === void 0 ? void 0 : events[best];
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* The per-subscriber check for better-supabase's webhooks-out, outbox and
|
|
334
|
+
* realtime fan-out: the subscribers whose `principal` `decideFor` grants
|
|
335
|
+
* the event type's permission on its row, in the event's tenant, as the
|
|
336
|
+
* sender. An unmapped type, an event without a tenant or sender, a sender
|
|
337
|
+
* outside the tenant and a throw reach no one.
|
|
338
|
+
*/
|
|
339
|
+
function subscriberPolicy(options) {
|
|
340
|
+
return async (event, subscribers) => {
|
|
341
|
+
try {
|
|
342
|
+
const permission = permissionFor(options.events, event.type);
|
|
343
|
+
const tenant = event.tenant;
|
|
344
|
+
const senderId = options.sender?.(event) ?? event.actorId;
|
|
345
|
+
if (permission === void 0 || tenant === null || senderId === null || senderId === void 0) return [];
|
|
346
|
+
const sender = await loadPrincipal(options.principals, senderId, tenant);
|
|
347
|
+
if (sender === null) return [];
|
|
348
|
+
const permdock = await getPermDock(options.policy, {
|
|
349
|
+
principal: sender,
|
|
350
|
+
context: {}
|
|
351
|
+
}, {
|
|
352
|
+
...options.options,
|
|
353
|
+
principals: options.principals,
|
|
354
|
+
tenant
|
|
355
|
+
});
|
|
356
|
+
if (permdock.subject.principal?.tenant !== tenant) return [];
|
|
357
|
+
return await subscribersFor(permdock, {
|
|
358
|
+
permission,
|
|
359
|
+
row: options.row === void 0 ? event.payload : options.row(event)
|
|
360
|
+
}, subscribers);
|
|
361
|
+
} catch {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/better-supabase/support.ts
|
|
368
|
+
/**
|
|
369
|
+
* Bound to the admin, target, tenant, reason and mode, so the same start
|
|
370
|
+
* gets the same approval token each time.
|
|
371
|
+
*/
|
|
372
|
+
function requestId(input) {
|
|
373
|
+
return sha256Digest(canonicalJson({
|
|
374
|
+
t: "better-supabase-support",
|
|
375
|
+
admin: input.admin.user.id,
|
|
376
|
+
target: input.targetUserId,
|
|
377
|
+
tenant: input.tenant ?? null,
|
|
378
|
+
reason: input.reason,
|
|
379
|
+
readOnly: input.readOnly
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* The `authorize` option of better-supabase's `supportSessions`: the admin's
|
|
384
|
+
* verified claims become a PermDock subject, and the start is decided with
|
|
385
|
+
* `permdock.decideSupportSession` in the requested tenant. `true` only for
|
|
386
|
+
* `granted`: an `approval-required` start passes once `approved` returns a
|
|
387
|
+
* distinct approver for its token. `readOnly: false` needs `writable`. A
|
|
388
|
+
* throw is `false`.
|
|
389
|
+
*/
|
|
390
|
+
function supportPolicy(options) {
|
|
391
|
+
return async (input) => {
|
|
392
|
+
try {
|
|
393
|
+
const base = await getPermDock(options.policy, subjectFromBetterSupabase({
|
|
394
|
+
kind: "user",
|
|
395
|
+
claims: input.admin.claims
|
|
396
|
+
}, options.subject), options.options);
|
|
397
|
+
if (base.subject.principal?.id !== input.admin.user.id) return false;
|
|
398
|
+
if (!input.readOnly && options.writable === void 0) return false;
|
|
399
|
+
const permdock = input.tenant === void 0 ? base : base.tenant(input.tenant);
|
|
400
|
+
const request = {
|
|
401
|
+
id: requestId(input),
|
|
402
|
+
principal: input.targetUserId,
|
|
403
|
+
reason: input.reason,
|
|
404
|
+
...input.tenant === void 0 ? {} : { tenant: input.tenant },
|
|
405
|
+
...input.readOnly ? {} : { permissions: options.writable },
|
|
406
|
+
...typeof input.metadata["reference"] === "string" ? { reference: input.metadata["reference"] } : {}
|
|
407
|
+
};
|
|
408
|
+
const settings = options.options?.settings;
|
|
409
|
+
const decide = (approved) => decideSupportSession(permdock, request, {
|
|
410
|
+
...settings === void 0 ? {} : { settings },
|
|
411
|
+
...options.approval === false ? { approval: false } : {},
|
|
412
|
+
...approved === void 0 ? {} : {
|
|
413
|
+
approved: approved.token,
|
|
414
|
+
approvedBy: approved.approvedBy
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
const decision = await decide();
|
|
418
|
+
switch (decision.outcome) {
|
|
419
|
+
case "granted": return true;
|
|
420
|
+
case "denied": return false;
|
|
421
|
+
case "approval-required": {
|
|
422
|
+
const found = await options.approved?.(input, {
|
|
423
|
+
token: decision.token,
|
|
424
|
+
session: decision.session
|
|
425
|
+
});
|
|
426
|
+
if (found === void 0) return false;
|
|
427
|
+
return (await decide({
|
|
428
|
+
token: decision.token,
|
|
429
|
+
approvedBy: found.approvedBy
|
|
430
|
+
})).outcome === "granted";
|
|
431
|
+
}
|
|
432
|
+
default: return decision;
|
|
433
|
+
}
|
|
434
|
+
} catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/better-supabase/hooks.ts
|
|
441
|
+
/** better-supabase's claim that caps a context to the permission keys it lists. */
|
|
442
|
+
const PERMISSION_CAP_CLAIM = "bs_permission_cap";
|
|
443
|
+
/** Whether `permdock` is granted `key` with no row; an unknown key and `approval-required` are `false`. */
|
|
444
|
+
function grants$1(permdock, key) {
|
|
445
|
+
const permission = findPermission(permdock.permissions, key);
|
|
446
|
+
return permission !== void 0 && permdock.decide(permission, void 0).outcome === "granted";
|
|
447
|
+
}
|
|
448
|
+
/** A list matches when every entry does, `anyOf` when one does; other leaves match nobody, as in SQL. */
|
|
449
|
+
function matches(approvers, userId, permdock) {
|
|
450
|
+
const items = flattenApprovers(approvers);
|
|
451
|
+
return items.length > 0 && items.every((item) => {
|
|
452
|
+
switch (item.kind) {
|
|
453
|
+
case "user": return item.id === userId;
|
|
454
|
+
case "permission": return grants$1(permdock, item.permission);
|
|
455
|
+
case "any-of": return item.of.some((entry) => matches(entry, userId, permdock));
|
|
456
|
+
case "actor":
|
|
457
|
+
case "anyone":
|
|
458
|
+
case "assurance":
|
|
459
|
+
case "authenticated":
|
|
460
|
+
case "inherit":
|
|
461
|
+
case "plan":
|
|
462
|
+
case "relation":
|
|
463
|
+
case "role": return false;
|
|
464
|
+
default: return item;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* better-supabase's `AuthorizationHooks` (`createServer(betterSupabase,
|
|
470
|
+
* { authorization })`), decided by `policy`. Every hook builds the
|
|
471
|
+
* principal's instance from the claims `claims` loads now, never from the
|
|
472
|
+
* request, and fails closed: a throw, an unknown key and `approval-required`
|
|
473
|
+
* refuse. `automation` and `delegation` are always set; `identities`,
|
|
474
|
+
* `delivery`, `approverOf`, `support` and `tickets` only with the options
|
|
475
|
+
* they read. `delegation.permissions` maps each scope to the keys whose
|
|
476
|
+
* `.scope` it is, was renamed from, or that a policy `oauthScopes` entry
|
|
477
|
+
* covers, and reads no `authorization_details`.
|
|
478
|
+
*/
|
|
479
|
+
function authorizationHooks(options) {
|
|
480
|
+
const { policy } = options;
|
|
481
|
+
const instance = async (claims, tenant) => {
|
|
482
|
+
const subject = subjectFromBetterSupabase({
|
|
483
|
+
kind: "user",
|
|
484
|
+
claims
|
|
485
|
+
}, options.subject);
|
|
486
|
+
if (subject.principal === null) return null;
|
|
487
|
+
const permdock = await getPermDock(policy, subject, {
|
|
488
|
+
...options.options,
|
|
489
|
+
...tenant === null || tenant === void 0 ? {} : { tenant }
|
|
490
|
+
});
|
|
491
|
+
return tenant === null || tenant === void 0 || permdock.subject.principal?.tenant === tenant ? permdock : null;
|
|
492
|
+
};
|
|
493
|
+
const holdsAll = async (claims, automation) => {
|
|
494
|
+
const permdock = await instance(claims, automation.tenant);
|
|
495
|
+
return permdock !== null && automation.permissions.every((key) => grants$1(permdock, key));
|
|
496
|
+
};
|
|
497
|
+
const claimsOf = async (userId, tenant) => {
|
|
498
|
+
const claims = await options.claims(userId, tenant === null || tenant === void 0 ? {} : { tenant });
|
|
499
|
+
return isRecord(claims) && claims["sub"] === userId ? claims : null;
|
|
500
|
+
};
|
|
501
|
+
const automation = {
|
|
502
|
+
async mayPublish(ref) {
|
|
503
|
+
try {
|
|
504
|
+
return ref.publisher !== null && await holdsAll(ref.claims, ref);
|
|
505
|
+
} catch {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
async runAs(ref) {
|
|
510
|
+
try {
|
|
511
|
+
const claims = ref.publisher === null ? await options.service?.(ref.tenant) : await claimsOf(ref.publisher, ref.tenant);
|
|
512
|
+
if (!isRecord(claims) || !await holdsAll(claims, ref)) return null;
|
|
513
|
+
return Object.freeze({
|
|
514
|
+
...claims,
|
|
515
|
+
[PERMISSION_CAP_CLAIM]: [...ref.permissions]
|
|
516
|
+
});
|
|
517
|
+
} catch {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
const delegation = { permissions(ref) {
|
|
523
|
+
try {
|
|
524
|
+
const scopes = new Set(ref.scopes);
|
|
525
|
+
return listPermissions(policy.permissions).filter((permission) => scopesReaching(policy, permission).some((scope) => scopes.has(scope))).map((permission) => permission.key);
|
|
526
|
+
} catch {
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
} };
|
|
530
|
+
const { principals, events, approvers, support, tickets } = options;
|
|
531
|
+
const approverOf = approvers === void 0 ? void 0 : async (approver, request) => {
|
|
532
|
+
try {
|
|
533
|
+
if (approver.userId === request.requester || !Object.hasOwn(approvers, request.kind)) return false;
|
|
534
|
+
const given = approvers[request.kind];
|
|
535
|
+
if (given === void 0 || approver.claims["sub"] !== approver.userId) return false;
|
|
536
|
+
const permdock = await instance(approver.claims, request.tenant);
|
|
537
|
+
return permdock !== null && matches(asApprover(given), approver.userId, permdock);
|
|
538
|
+
} catch {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
const identities = principals?.principalByIdentity === void 0 ? void 0 : { async principalByIdentity(identity) {
|
|
543
|
+
try {
|
|
544
|
+
const found = await principals.principalByIdentity?.(identity);
|
|
545
|
+
if (found === null || found === void 0) return null;
|
|
546
|
+
return found.kind === "contact" ? {
|
|
547
|
+
kind: "contact",
|
|
548
|
+
contactId: found.id
|
|
549
|
+
} : {
|
|
550
|
+
kind: "user",
|
|
551
|
+
userId: found.id
|
|
552
|
+
};
|
|
553
|
+
} catch {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
} };
|
|
557
|
+
const fanOut = principals === void 0 || events === void 0 ? void 0 : subscriberPolicy({
|
|
558
|
+
policy,
|
|
559
|
+
principals,
|
|
560
|
+
events,
|
|
561
|
+
...options.row === void 0 ? {} : { row: options.row },
|
|
562
|
+
...options.options === void 0 ? {} : { options: options.options }
|
|
563
|
+
});
|
|
564
|
+
return Object.freeze({
|
|
565
|
+
apiVersion: 1,
|
|
566
|
+
name: "PermDock",
|
|
567
|
+
automation: Object.freeze(automation),
|
|
568
|
+
delegation: Object.freeze(delegation),
|
|
569
|
+
...approverOf === void 0 ? {} : { approverOf },
|
|
570
|
+
...identities === void 0 ? {} : { identities: Object.freeze(identities) },
|
|
571
|
+
...fanOut === void 0 ? {} : { delivery: Object.freeze({
|
|
572
|
+
subscribers: fanOut,
|
|
573
|
+
audience: fanOut
|
|
574
|
+
}) },
|
|
575
|
+
...support === void 0 ? {} : { support: Object.freeze({ authorize: supportPolicy({
|
|
576
|
+
...support,
|
|
577
|
+
policy
|
|
578
|
+
}) }) },
|
|
579
|
+
...tickets === void 0 ? {} : { tickets: Object.freeze({ async subject(ticket) {
|
|
580
|
+
try {
|
|
581
|
+
const found = await tickets(ticket);
|
|
582
|
+
if (!isRecord(found) || typeof found.userId !== "string") return null;
|
|
583
|
+
const claims = await claimsOf(found.userId, found.tenant);
|
|
584
|
+
return claims === null ? null : {
|
|
585
|
+
claims,
|
|
586
|
+
...found.tenant === void 0 ? {} : { tenant: found.tenant }
|
|
587
|
+
};
|
|
588
|
+
} catch {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
} }) }
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/better-supabase/approvers.ts
|
|
596
|
+
/** `rls.approvals`' approver check, executable by `authenticated`. */
|
|
597
|
+
const IS_APPROVER = "permdock_is_approver";
|
|
598
|
+
/** The resource a cover's `resources` names for a request kind: `job` for `job.update`, else the kind. */
|
|
599
|
+
function resourceOf(kind) {
|
|
600
|
+
const dot = kind.indexOf(".");
|
|
601
|
+
return dot <= 0 ? kind : kind.slice(0, dot);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* better-supabase's `isApprover` template: one `case` over `{kind}` whose
|
|
605
|
+
* branches call `permdock_is_approver` with the kind's approvers as JSON.
|
|
606
|
+
* A kind with no branch is `false`. Only `user`, `holder` and `anyOf`
|
|
607
|
+
* approvers match in SQL; other leaves are reported and match nobody.
|
|
608
|
+
*/
|
|
609
|
+
function isApproverTemplate(input) {
|
|
610
|
+
const problems = [];
|
|
611
|
+
if (!input.listed) return { problems: [`approvers needs ${input.schema}.${IS_APPROVER}, which \`permdock rls generate\` writes with rls.approvals. Run it, then \`permdock supabase inspect --out\`.`] };
|
|
612
|
+
const fn = `${sqlIdent(input.schema)}.${sqlIdent(IS_APPROVER)}`;
|
|
613
|
+
const keys = input.catalog === void 0 ? void 0 : new Set(input.catalog.permissions.map((permission) => permission.key));
|
|
614
|
+
const branches = [];
|
|
615
|
+
for (const [kind, given] of Object.entries(input.approvers)) {
|
|
616
|
+
if (kind === "") {
|
|
617
|
+
problems.push("approvers has an empty kind, which no request has.");
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const approvers = asApprover(given);
|
|
621
|
+
for (const leaf of approverLeaves(approvers)) {
|
|
622
|
+
if (leaf.kind === "user") continue;
|
|
623
|
+
if (leaf.kind !== "permission") problems.push(`The ${kind} approvers name a ${leaf.kind} approver, which ${IS_APPROVER} cannot check in SQL, so it matches nobody. Use user(), holder() or anyOf().`);
|
|
624
|
+
else if (keys !== void 0 && !keys.has(leaf.permission)) problems.push(`The ${kind} approver permission ${leaf.permission} is not in the catalog. Run \`permdock catalog\` after adding it to the policy.`);
|
|
625
|
+
}
|
|
626
|
+
const json = `${quoteSqlLiteral(JSON.stringify(approvers))}::jsonb`;
|
|
627
|
+
branches.push(input.covers === true ? `when ${quoteSqlLiteral(kind)} then ${fn}(${json}, ({tenant})::text, ({requester})::text, ${quoteSqlLiteral(resourceOf(kind))})` : `when ${quoteSqlLiteral(kind)} then ${fn}(${json}, ({tenant})::text)`);
|
|
628
|
+
}
|
|
629
|
+
if (branches.length === 0) {
|
|
630
|
+
problems.push("approvers names no kind, so isApprover is not set.");
|
|
631
|
+
return { problems };
|
|
632
|
+
}
|
|
633
|
+
if (!input.rootTenant) problems.push(`The tenant scope "${input.tenantScope}" is not a root scope, and ${IS_APPROVER} checks holder() approvers at the root scope, so they match nobody.`);
|
|
634
|
+
return {
|
|
635
|
+
template: `case {kind} ${branches.join(" ")} else false end`,
|
|
636
|
+
requires: {
|
|
637
|
+
function: fn,
|
|
638
|
+
args: input.covers === true ? "jsonb, text, text, text" : "jsonb, text",
|
|
639
|
+
role: "postgres"
|
|
640
|
+
},
|
|
641
|
+
problems
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
15
645
|
//#region src/better-supabase/provider.ts
|
|
16
646
|
/**
|
|
17
647
|
* The helper each template calls (with `{scope}` unfilled), its default
|
|
@@ -93,6 +723,13 @@ const ASSIGN_ANY = {
|
|
|
93
723
|
}
|
|
94
724
|
};
|
|
95
725
|
const listedName = (entry) => entry.listedAs ?? entry.helper;
|
|
726
|
+
/**
|
|
727
|
+
* No client role may execute `permitted_<scope>_users`, so `holdersOf` is
|
|
728
|
+
* required for `postgres`, inside better-supabase's `security definer`
|
|
729
|
+
* functions; better-supabase fills it for the tenant scope only.
|
|
730
|
+
*/
|
|
731
|
+
const HOLDERS_OF = "permitted_{scope}_users";
|
|
732
|
+
const ACTOR_MAY_WRITE = "permdock_actor_may_write";
|
|
96
733
|
const OPTIONAL = [
|
|
97
734
|
"idsWithFor",
|
|
98
735
|
"isPlatformFor",
|
|
@@ -102,10 +739,16 @@ const OPTIONAL = [
|
|
|
102
739
|
"canAssignFor",
|
|
103
740
|
"permissionsFor"
|
|
104
741
|
];
|
|
105
|
-
/**
|
|
742
|
+
/**
|
|
743
|
+
* Postgres aliases of the id types better-supabase reads (`uuid`, `text`,
|
|
744
|
+
* `bigint`, `integer`), matching its `moduleIdType` from
|
|
745
|
+
* `better-supabase/sql`, which this runtime entry does not import.
|
|
746
|
+
*/
|
|
106
747
|
const ID_TYPES = {
|
|
107
748
|
uuid: "uuid",
|
|
108
749
|
text: "text",
|
|
750
|
+
varchar: "text",
|
|
751
|
+
"character varying": "text",
|
|
109
752
|
bigint: "bigint",
|
|
110
753
|
int8: "bigint",
|
|
111
754
|
integer: "integer",
|
|
@@ -200,17 +843,97 @@ function tokenHookOf(manifest) {
|
|
|
200
843
|
hook: `-- permdock:hook ${manifest.markers.hook}`,
|
|
201
844
|
grants: `-- permdock:grants ${manifest.markers.grants}`
|
|
202
845
|
},
|
|
203
|
-
grantsCommand:
|
|
846
|
+
grantsCommand: grantsCommandOf(manifest.hook.schemas ?? "pg-delta")
|
|
204
847
|
};
|
|
205
848
|
}
|
|
849
|
+
/** The command better-supabase doctor prints when the hook lacks its grants. */
|
|
850
|
+
function grantsCommandOf(schemas) {
|
|
851
|
+
switch (schemas) {
|
|
852
|
+
case "pg-delta": return "permdock supabase hook generate && supabase db schema declarative sync -f permdock_hook";
|
|
853
|
+
case "migra": return "permdock supabase hook generate --grants-out supabase/migrations/<timestamp>_permdock_hook_grants.sql";
|
|
854
|
+
case "migrations": return "permdock supabase hook generate --out supabase/migrations/<timestamp>_permdock_hook.sql";
|
|
855
|
+
default: return schemas;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* better-supabase's `permitted` template. An instance of a scope (`{resource}`
|
|
860
|
+
* names the scope) is checked against that scope's ids; any other resource
|
|
861
|
+
* at the tenant, and with a catalog only for a key without row conditions.
|
|
862
|
+
* Without a catalog better-supabase refuses every key anyway.
|
|
863
|
+
*/
|
|
864
|
+
function permittedTemplate(schema, scopes, tenantScope, catalog) {
|
|
865
|
+
const idsOf = (scope) => `select ids::text from ${schema}.${sqlIdent(`permitted_${scope}_ids_by_permission`)}({permission}) as ids`;
|
|
866
|
+
const branches = scopes.map((scope) => `when {resource} = ${quoteSqlLiteral(scope)} then {id} in (${idsOf(scope)})`);
|
|
867
|
+
const atTenant = `{tenant} in (${idsOf(tenantScope)})`;
|
|
868
|
+
const complete = catalog?.permissions.filter((permission) => !permission.rowConditions).map((permission) => quoteSqlLiteral(permission.key));
|
|
869
|
+
const otherwise = complete === void 0 ? atTenant : complete.length === 0 ? `false and ${atTenant}` : `{permission} in (${complete.join(", ")}) and ${atTenant}`;
|
|
870
|
+
return `case ${branches.join(" ")} else ${otherwise} end`;
|
|
871
|
+
}
|
|
872
|
+
const RISKS = {
|
|
873
|
+
low: "low",
|
|
874
|
+
medium: "medium",
|
|
875
|
+
high: "high",
|
|
876
|
+
critical: "high"
|
|
877
|
+
};
|
|
878
|
+
const EFFECTS = {
|
|
879
|
+
read: "read",
|
|
880
|
+
write: "write",
|
|
881
|
+
delete: "delete",
|
|
882
|
+
send: "external",
|
|
883
|
+
spend: "external",
|
|
884
|
+
external: "external"
|
|
885
|
+
};
|
|
886
|
+
/**
|
|
887
|
+
* The catalog's `defineOperations` entries as better-supabase operations. A
|
|
888
|
+
* name with a `.` is used as written (`workflow.node.http`); any other is
|
|
889
|
+
* the tool `ai.tool.<name>` and `mcp.tool.<name>`. `critical` risk is
|
|
890
|
+
* `high`, and `send` and `spend` effects are `external`.
|
|
891
|
+
*/
|
|
892
|
+
function operationsOf(catalog) {
|
|
893
|
+
const operations = {};
|
|
894
|
+
for (const operation of catalog.operations ?? []) {
|
|
895
|
+
const effects = [...new Set((operation.meta.effects ?? []).map((effect) => EFFECTS[effect]))];
|
|
896
|
+
const meta = {
|
|
897
|
+
...operation.meta.risk === void 0 ? {} : { risk: RISKS[operation.meta.risk] },
|
|
898
|
+
...effects.length > 0 ? { effects } : {},
|
|
899
|
+
...operation.meta.title === void 0 ? {} : { title: operation.meta.title }
|
|
900
|
+
};
|
|
901
|
+
const entry = {
|
|
902
|
+
permission: operation.permission,
|
|
903
|
+
...Object.keys(meta).length > 0 ? { meta } : {}
|
|
904
|
+
};
|
|
905
|
+
const names = operation.name.includes(".") ? [operation.name] : [`ai.tool.${operation.name}`, `mcp.tool.${operation.name}`];
|
|
906
|
+
for (const name of names) operations[name] = entry;
|
|
907
|
+
}
|
|
908
|
+
return operations;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* better-supabase's `delegation.scopes`: each permission's own scope, and
|
|
912
|
+
* each coarse scope the catalog's `oauthScopes` lists, to the permission keys
|
|
913
|
+
* it reaches, sorted by code point. A key the catalog does not list is
|
|
914
|
+
* dropped, so a stale `oauthScopes` entry grants nothing.
|
|
915
|
+
*/
|
|
916
|
+
function delegatedScopes(catalog) {
|
|
917
|
+
const delegated = {};
|
|
918
|
+
const known = /* @__PURE__ */ new Set();
|
|
919
|
+
for (const permission of catalog?.permissions ?? []) {
|
|
920
|
+
known.add(permission.key);
|
|
921
|
+
(delegated[permission.scope] ??= /* @__PURE__ */ new Set()).add(permission.key);
|
|
922
|
+
}
|
|
923
|
+
for (const entry of catalog?.oauthScopes ?? []) for (const key of entry.permissions) if (known.has(key)) (delegated[entry.scope] ??= /* @__PURE__ */ new Set()).add(key);
|
|
924
|
+
return Object.fromEntries(Object.keys(delegated).toSorted(byCodePoint).map((scope) => [scope, [...delegated[scope] ?? []].toSorted(byCodePoint)]));
|
|
925
|
+
}
|
|
206
926
|
/**
|
|
207
927
|
* better-supabase's `authorization` config value, built from PermDock's
|
|
208
928
|
* manifest and catalog: the scopes, the `rls generate` helpers as SQL
|
|
209
929
|
* templates, the membership tables, suspension rows, the token hook and
|
|
210
930
|
* every catalog key with `sqlComplete` set from its `rowConditions`. A
|
|
211
931
|
* `_for` template and `permissionsFor` are set only when the manifest lists
|
|
212
|
-
* their helper, which `rls.mode: 'database'` writes; `
|
|
213
|
-
* `
|
|
932
|
+
* their helper, which `rls.mode: 'database'` writes; `isApprover` only with
|
|
933
|
+
* `approvers` and `permdock_is_approver`, and otherwise `canApprove` only
|
|
934
|
+
* with `approver`. `holdersOf` (`permitted_<tenant scope>_users`) and
|
|
935
|
+
* `actorMayWrite` (`permdock_actor_may_write()`) are set when the manifest
|
|
936
|
+
* lists their helper. Throws when the manifest or catalog is invalid, or when the
|
|
214
937
|
* tenant scope is ambiguous.
|
|
215
938
|
*/
|
|
216
939
|
function authorizationProvider(options) {
|
|
@@ -266,25 +989,60 @@ function authorizationProvider(options) {
|
|
|
266
989
|
const users = rls.suspension?.users;
|
|
267
990
|
const tenants = rls.suspension?.scopes?.[tenantScope];
|
|
268
991
|
const approver = options.approver;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
992
|
+
const approvers = approver === void 0 ? options.approvers : {
|
|
993
|
+
"ai-tool": holder(approver),
|
|
994
|
+
...options.approvers
|
|
995
|
+
};
|
|
996
|
+
let isApprover;
|
|
997
|
+
if (approvers !== void 0) {
|
|
998
|
+
const built = isApproverTemplate({
|
|
999
|
+
approvers,
|
|
1000
|
+
schema: rls.schema,
|
|
1001
|
+
listed: helpers.has(IS_APPROVER),
|
|
1002
|
+
covers: /\bp_requester\b/u.test(helpers.get("permdock_is_approver")?.args ?? ""),
|
|
1003
|
+
tenantScope,
|
|
1004
|
+
rootTenant,
|
|
1005
|
+
catalog
|
|
1006
|
+
});
|
|
1007
|
+
problems.push(...built.problems);
|
|
1008
|
+
isApprover = built.template;
|
|
1009
|
+
if (built.requires !== void 0) requires.push(built.requires);
|
|
1010
|
+
}
|
|
1011
|
+
const holdersName = HOLDERS_OF.replace("{scope}", tenantScope);
|
|
1012
|
+
const holders = helpers.get(holdersName);
|
|
1013
|
+
let holdersOf;
|
|
1014
|
+
if (holders !== void 0) {
|
|
1015
|
+
const fn = `${schema}.${sqlIdent(holders.name)}`;
|
|
1016
|
+
holdersOf = `${fn}({permission}, {tenant})`;
|
|
274
1017
|
requires.push({
|
|
275
|
-
function:
|
|
276
|
-
args:
|
|
1018
|
+
function: fn,
|
|
1019
|
+
args: argTypes(holders.args),
|
|
277
1020
|
role: "postgres"
|
|
278
1021
|
});
|
|
279
1022
|
}
|
|
1023
|
+
let actorMayWrite;
|
|
1024
|
+
if (helpers.has(ACTOR_MAY_WRITE)) {
|
|
1025
|
+
const fn = `${schema}.${sqlIdent(ACTOR_MAY_WRITE)}`;
|
|
1026
|
+
actorMayWrite = `${fn}()`;
|
|
1027
|
+
requires.push({
|
|
1028
|
+
function: fn,
|
|
1029
|
+
role: "authenticated"
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
const permitted = listed("idsWith", tenantScope) ? permittedTemplate(schema, scopeNames.filter((scope) => listed("idsWith", scope)), tenantScope, catalog) : void 0;
|
|
280
1033
|
const functions = {
|
|
281
1034
|
...optional,
|
|
282
1035
|
idsWith: `${schema}.${TEMPLATES.idsWith.sql}`,
|
|
283
1036
|
isPlatform: `${schema}.${TEMPLATES.isPlatform.sql}`,
|
|
284
|
-
...
|
|
1037
|
+
...holdersOf === void 0 ? {} : { holdersOf },
|
|
1038
|
+
...actorMayWrite === void 0 ? {} : { actorMayWrite },
|
|
1039
|
+
...isApprover === void 0 ? {} : { isApprover },
|
|
1040
|
+
...permitted === void 0 ? {} : { permitted }
|
|
285
1041
|
};
|
|
1042
|
+
const operations = catalog === void 0 ? {} : operationsOf(catalog);
|
|
1043
|
+
const delegated = delegatedScopes(catalog);
|
|
286
1044
|
const scopes = rls.scopes.map((scope) => {
|
|
287
|
-
const idType = ID_TYPES[scope.type.trim().toLowerCase()];
|
|
1045
|
+
const idType = ID_TYPES[scope.type.trim().toLowerCase().replaceAll(/\s+/gu, " ")];
|
|
288
1046
|
if (idType === void 0) problems.push(`Scope "${scope.name}" has ids of type ${scope.type}; better-supabase reads uuid, text, bigint and integer ids.`);
|
|
289
1047
|
return scope.within === void 0 ? {
|
|
290
1048
|
name: scope.name,
|
|
@@ -309,179 +1067,14 @@ function authorizationProvider(options) {
|
|
|
309
1067
|
...tenants ? { tenant: disabledRow(tenants) } : {}
|
|
310
1068
|
} } : {},
|
|
311
1069
|
...roleSources.length > 0 ? { roleSources } : {},
|
|
312
|
-
...
|
|
1070
|
+
...Object.keys(operations).length > 0 ? { operations } : {},
|
|
1071
|
+
...Object.keys(delegated).length > 0 ? { delegation: { scopes: delegated } } : {},
|
|
313
1072
|
decidingColumns: manifest.decidingColumns,
|
|
314
1073
|
tokenHook: tokenHookOf(manifest),
|
|
315
1074
|
...problems.length > 0 ? { problems } : {}
|
|
316
1075
|
});
|
|
317
1076
|
}
|
|
318
1077
|
//#endregion
|
|
319
|
-
//#region src/better-supabase/api-keys.ts
|
|
320
|
-
const seconds = (instant) => Math.floor(instant.epochMilliseconds / 1e3);
|
|
321
|
-
/** When a key stops verifying: its expiry, or the end of a rotated key's grace period. */
|
|
322
|
-
function endOf(key) {
|
|
323
|
-
const { expiresAt, revokedAt } = key;
|
|
324
|
-
if (revokedAt === void 0) return expiresAt;
|
|
325
|
-
if (expiresAt === void 0) return revokedAt;
|
|
326
|
-
return revokedAt.epochMilliseconds < expiresAt.epochMilliseconds ? revokedAt : expiresAt;
|
|
327
|
-
}
|
|
328
|
-
/** `rls.apiKeys.serviceRoles` from the manifest, when it has `rls.apiKeys`. */
|
|
329
|
-
function manifestServiceRoles(manifest) {
|
|
330
|
-
return manifest === void 0 ? void 0 : parseSupabaseManifest(manifest).rls.apiKeys?.serviceRoles;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* A personal key as a `user` credential acting as its user, a tenant key as
|
|
334
|
-
* a `service` credential holding `roles` in its tenant; `null` when `*`
|
|
335
|
-
* has no expansion, a tenant key has no roles, or the record is invalid.
|
|
336
|
-
*/
|
|
337
|
-
function keyCredential(key, roles, allPermissions) {
|
|
338
|
-
const scopes = key.scopes.includes("*") ? allPermissions?.map((permission) => permission.key) : key.scopes;
|
|
339
|
-
if (scopes === void 0) return null;
|
|
340
|
-
const owner = key.userId;
|
|
341
|
-
const base = {
|
|
342
|
-
v: 1,
|
|
343
|
-
id: key.id,
|
|
344
|
-
permissions: scopes.map((permission) => ({ permission })),
|
|
345
|
-
createdBy: key.createdBy ?? owner ?? key.id,
|
|
346
|
-
createdAt: key.createdAt ?? 0,
|
|
347
|
-
...key.expiresAt === void 0 ? {} : { expiresAt: key.expiresAt },
|
|
348
|
-
name: key.name
|
|
349
|
-
};
|
|
350
|
-
if (owner !== void 0) return parseCredential({
|
|
351
|
-
...base,
|
|
352
|
-
kind: "user",
|
|
353
|
-
principal: owner,
|
|
354
|
-
...key.organizationId === void 0 ? {} : { tenant: key.organizationId }
|
|
355
|
-
}) ?? null;
|
|
356
|
-
if (roles === void 0 || key.organizationId === void 0) return null;
|
|
357
|
-
return parseCredential({
|
|
358
|
-
...base,
|
|
359
|
-
kind: "service",
|
|
360
|
-
principal: key.id,
|
|
361
|
-
tenant: key.organizationId,
|
|
362
|
-
roles
|
|
363
|
-
}) ?? null;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* better-supabase's API keys as a `CredentialVerifier`, for
|
|
367
|
-
* `subjectFromApiKey` from `permdock/server`, which reads PermDock's
|
|
368
|
-
* `pdk_<id>_<secret>` format: create the keys with `prefix: "pdk"`. The
|
|
369
|
-
* credential id is the key's `publicId`, the id in the token. A personal key
|
|
370
|
-
* is a `user` credential acting as its user (inside its tenant when the key
|
|
371
|
-
* is limited to one), a tenant key a `service` credential holding
|
|
372
|
-
* `serviceRoles` in its tenant, and the key's scopes are the credential's
|
|
373
|
-
* permissions. better-supabase records the use when it verifies, so there is
|
|
374
|
-
* no `touch`. A rotated key in its grace period verifies, and its credential
|
|
375
|
-
* expires when the grace period ends. An invalid, revoked, expired or
|
|
376
|
-
* rate-limited key, and a failed lookup, are `null`. `permdock/server` exports a different
|
|
377
|
-
* `apiKeyVerifier`, over the application's own key records.
|
|
378
|
-
*/
|
|
379
|
-
function apiKeyVerifier(options) {
|
|
380
|
-
const fallback = manifestServiceRoles(options.manifest);
|
|
381
|
-
return freezeDeep({ async verify(secret) {
|
|
382
|
-
try {
|
|
383
|
-
const checked = await options.keys.verify(secret);
|
|
384
|
-
if (!checked.ok || checked.data.status !== "ok") return null;
|
|
385
|
-
const { key } = checked.data;
|
|
386
|
-
if (key.state !== "active" && key.state !== "grace") return null;
|
|
387
|
-
const end = endOf(key);
|
|
388
|
-
const roles = typeof options.serviceRoles === "function" ? options.serviceRoles(key) : options.serviceRoles ?? fallback;
|
|
389
|
-
return keyCredential({
|
|
390
|
-
id: key.publicId,
|
|
391
|
-
name: key.name,
|
|
392
|
-
scopes: key.scopes,
|
|
393
|
-
createdAt: seconds(key.createdAt),
|
|
394
|
-
...key.organizationId === void 0 ? {} : { organizationId: key.organizationId },
|
|
395
|
-
...key.userId === void 0 ? {} : { userId: key.userId },
|
|
396
|
-
...key.createdBy === void 0 ? {} : { createdBy: key.createdBy },
|
|
397
|
-
...end === void 0 ? {} : { expiresAt: seconds(end) }
|
|
398
|
-
}, roles, options.allPermissions);
|
|
399
|
-
} catch {
|
|
400
|
-
return null;
|
|
401
|
-
}
|
|
402
|
-
} });
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* The `claim` and `tenantClaim` options of better-supabase's
|
|
406
|
-
* `apiKeyClaims()`, from the manifest's `rls.apiKeys`, so a key's token
|
|
407
|
-
* carries the scopes ceiling, tenant and roles the `rls generate` helpers
|
|
408
|
-
* read. Throws when the manifest has no `rls.apiKeys`.
|
|
409
|
-
*/
|
|
410
|
-
function apiKeyClaimOptions(manifest) {
|
|
411
|
-
const rls = parseSupabaseManifest(manifest).rls;
|
|
412
|
-
const apiKeys = rls.apiKeys;
|
|
413
|
-
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`.");
|
|
414
|
-
return freezeDeep({
|
|
415
|
-
claim: {
|
|
416
|
-
name: apiKeys.claim,
|
|
417
|
-
scopes: apiKeys.scopes,
|
|
418
|
-
tenant: apiKeys.tenant,
|
|
419
|
-
roles: apiKeys.roles,
|
|
420
|
-
serviceRoles: apiKeys.serviceRoles
|
|
421
|
-
},
|
|
422
|
-
tenantClaim: rls.tenantClaim
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
//#endregion
|
|
426
|
-
//#region src/better-supabase/subject.ts
|
|
427
|
-
/** `session` with the short codes in `claim` turned back into feature keys. */
|
|
428
|
-
function decodedFeatures(session, claim, keys) {
|
|
429
|
-
const claims = session?.claims;
|
|
430
|
-
if (session === null || session === void 0 || !isRecord(claims)) return session;
|
|
431
|
-
const coded = claims[claim];
|
|
432
|
-
if (!isRecord(coded)) return session;
|
|
433
|
-
const byCode = new Map(Object.entries(keys).map(([key, code]) => [code, key]));
|
|
434
|
-
const decoded = Object.fromEntries(Object.entries(coded).map(([tenant, codes]) => [tenant, Array.isArray(codes) ? codes.flatMap((code) => {
|
|
435
|
-
const key = typeof code === "string" ? byCode.get(code) : void 0;
|
|
436
|
-
return key === void 0 ? [] : [key];
|
|
437
|
-
}) : codes]));
|
|
438
|
-
return {
|
|
439
|
-
...session,
|
|
440
|
-
claims: {
|
|
441
|
-
...claims,
|
|
442
|
-
[claim]: decoded
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
function isApiKeySession(session) {
|
|
447
|
-
if (typeof session !== "object" || session === null) return false;
|
|
448
|
-
const record = Object.fromEntries(Object.entries(session));
|
|
449
|
-
return record["kind"] === "apiKey" && typeof record["keyId"] === "string" && typeof record["name"] === "string" && Array.isArray(record["scopes"]) && record["scopes"].every((scope) => typeof scope === "string");
|
|
450
|
-
}
|
|
451
|
-
function apiKeySubject(session, options) {
|
|
452
|
-
const roles = typeof options.serviceRoles === "function" ? options.serviceRoles(session) : options.serviceRoles ?? manifestServiceRoles(options.manifest);
|
|
453
|
-
const credential = keyCredential({
|
|
454
|
-
id: session.keyId,
|
|
455
|
-
name: session.name,
|
|
456
|
-
scopes: session.scopes,
|
|
457
|
-
...session.organizationId === void 0 ? {} : { organizationId: session.organizationId },
|
|
458
|
-
...session.userId === void 0 ? {} : { userId: session.userId },
|
|
459
|
-
...session.createdBy === void 0 ? {} : { createdBy: session.createdBy },
|
|
460
|
-
...session.createdAt === void 0 ? {} : { createdAt: session.createdAt }
|
|
461
|
-
}, roles, listPermissions(options.permissions));
|
|
462
|
-
return credential === null ? anonymousSubject() : credentialSubject(credential, { permissions: options.permissions });
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* `subjectFromSupabaseSession` for better-supabase's `AuthSession`, reading
|
|
466
|
-
* `memberships` for the memberships and the entitlements module's `features`
|
|
467
|
-
* claim for `principal.plans` (`plans` names another claim). With `apiKeys`,
|
|
468
|
-
* a verified `apiKey` session is the key's credential subject: a personal key
|
|
469
|
-
* acts as its user within the key's scopes, a tenant key as a `service`
|
|
470
|
-
* principal holding `serviceRoles`. Without `apiKeys`, and for `anon`,
|
|
471
|
-
* `service` and `invalid` sessions, the subject is anonymous.
|
|
472
|
-
*/
|
|
473
|
-
function subjectFromBetterSupabase(session, options = {}) {
|
|
474
|
-
const { apiKeys, plans = "features", ...rest } = options;
|
|
475
|
-
if (isApiKeySession(session)) return apiKeys === void 0 ? anonymousSubject() : apiKeySubject(session, apiKeys);
|
|
476
|
-
const claim = typeof plans === "string" ? plans : plans.claim ?? "features";
|
|
477
|
-
const keys = typeof plans === "string" ? void 0 : plans.keys;
|
|
478
|
-
return subjectFromSupabaseSession(keys === void 0 ? session : decodedFeatures(session, claim, keys), {
|
|
479
|
-
memberships: "memberships",
|
|
480
|
-
...rest,
|
|
481
|
-
plans: claim
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
//#endregion
|
|
485
1078
|
//#region src/better-supabase/authorizer.ts
|
|
486
1079
|
const OPERATIONS = /* @__PURE__ */ new Set([
|
|
487
1080
|
"list",
|
|
@@ -535,7 +1128,7 @@ function authorizer(options) {
|
|
|
535
1128
|
kind: "user",
|
|
536
1129
|
claims
|
|
537
1130
|
};
|
|
538
|
-
const base = await
|
|
1131
|
+
const base = await getPermDock(options.policy, subjectFromBetterSupabase(session, options.subject), options.options);
|
|
539
1132
|
return typeof tenant === "string" ? base.tenant(tenant) : base;
|
|
540
1133
|
};
|
|
541
1134
|
return Object.freeze({
|
|
@@ -738,7 +1331,7 @@ function toolPolicy(options) {
|
|
|
738
1331
|
const permission = tool.meta;
|
|
739
1332
|
if (!isPermission(permission)) return false;
|
|
740
1333
|
try {
|
|
741
|
-
return
|
|
1334
|
+
return (await options.permdock(ctx)).mayUse(permission);
|
|
742
1335
|
} catch {
|
|
743
1336
|
return false;
|
|
744
1337
|
}
|
|
@@ -746,4 +1339,153 @@ function toolPolicy(options) {
|
|
|
746
1339
|
});
|
|
747
1340
|
}
|
|
748
1341
|
//#endregion
|
|
749
|
-
|
|
1342
|
+
//#region src/better-supabase/principals.ts
|
|
1343
|
+
/**
|
|
1344
|
+
* A `PrincipalSource` over better-supabase: `principal(id)` maps the claims
|
|
1345
|
+
* `claims` loads with `subjectFromBetterSupabase`, and `principalByIdentity`
|
|
1346
|
+
* follows `identities` to a user or a contact. Claims for another user, a
|
|
1347
|
+
* link to a non-contact principal and a missing link are `null`.
|
|
1348
|
+
*/
|
|
1349
|
+
function principalSource(options) {
|
|
1350
|
+
const principal = async (id, lookup = {}) => {
|
|
1351
|
+
const claims = await options.claims(id, lookup.tenant === void 0 ? {} : { tenant: lookup.tenant });
|
|
1352
|
+
if (!isRecord(claims)) return null;
|
|
1353
|
+
const found = subjectFromBetterSupabase({
|
|
1354
|
+
kind: "user",
|
|
1355
|
+
claims
|
|
1356
|
+
}, options.subject).principal;
|
|
1357
|
+
return found?.id === id ? freezeDeep(structuredClone(found)) : null;
|
|
1358
|
+
};
|
|
1359
|
+
const { identities } = options;
|
|
1360
|
+
return Object.freeze({
|
|
1361
|
+
principal,
|
|
1362
|
+
...identities === void 0 ? {} : { async principalByIdentity(identity) {
|
|
1363
|
+
const link = await identities(identity);
|
|
1364
|
+
if (!isRecord(link)) return null;
|
|
1365
|
+
if ("user" in link) return typeof link.user === "string" ? principal(link.user) : null;
|
|
1366
|
+
const contact = link.contact;
|
|
1367
|
+
return isRecord(contact) && contact.kind === "contact" ? freezeDeep(structuredClone(contact)) : null;
|
|
1368
|
+
} }
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
//#endregion
|
|
1372
|
+
//#region src/better-supabase/share-links.ts
|
|
1373
|
+
const DEFAULT_TTL = 300;
|
|
1374
|
+
const MAX_TTL = 3600;
|
|
1375
|
+
function ownEntry(record, key) {
|
|
1376
|
+
return Object.hasOwn(record, key) ? record[key] : void 0;
|
|
1377
|
+
}
|
|
1378
|
+
function isRow(value) {
|
|
1379
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1380
|
+
const row = Object.fromEntries(Object.entries(value));
|
|
1381
|
+
const expiresAt = row["expiresAt"];
|
|
1382
|
+
return isId(row["id"]) && isId(row["organizationId"]) && isId(row["subjectType"]) && isId(row["subjectId"]) && typeof row["role"] === "string" && (row["versionId"] === void 0 || typeof row["versionId"] === "string") && (expiresAt === void 0 || typeof expiresAt === "object" && expiresAt !== null && "epochMilliseconds" in expiresAt && typeof expiresAt.epochMilliseconds === "number" && Number.isFinite(expiresAt.epochMilliseconds));
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Resolves a better-supabase share-link token into the `link` subject it
|
|
1386
|
+
* acts as, for `exchangeCapability` and `--capabilities` RLS. The row is
|
|
1387
|
+
* read on every call, so a revoked or expired link stops resolving at
|
|
1388
|
+
* once. An unknown token, subject type or role, a malformed row, a broken
|
|
1389
|
+
* link policy, a `resolve` error and a throw are the anonymous subject. So
|
|
1390
|
+
* is a link pinned to a version: a capability grants the subject's current
|
|
1391
|
+
* rows, so a pinned link reads through `versions.getShared(token)` instead.
|
|
1392
|
+
*/
|
|
1393
|
+
async function subjectFromShareLink(token, options) {
|
|
1394
|
+
if (token === void 0 || token === null || token.length === 0) return anonymousSubject();
|
|
1395
|
+
const deny = (reason, cause) => {
|
|
1396
|
+
options.onAuth?.(compact({
|
|
1397
|
+
reason,
|
|
1398
|
+
cause,
|
|
1399
|
+
source: "share-link",
|
|
1400
|
+
requestId: options.requestId
|
|
1401
|
+
}));
|
|
1402
|
+
return anonymousSubject();
|
|
1403
|
+
};
|
|
1404
|
+
let row;
|
|
1405
|
+
try {
|
|
1406
|
+
const resolved = await options.shareLinks.resolve(token);
|
|
1407
|
+
if (!resolved.ok) return deny("source-threw", "resolve-failed");
|
|
1408
|
+
if (resolved.data === null) return deny("invalid-token", "share-link-unknown");
|
|
1409
|
+
if (!isRow(resolved.data)) return deny("schema", "share-link-malformed");
|
|
1410
|
+
row = resolved.data;
|
|
1411
|
+
} catch {
|
|
1412
|
+
return deny("source-threw");
|
|
1413
|
+
}
|
|
1414
|
+
if (row.versionId !== void 0) return deny("invalid-token", "share-link-pinned");
|
|
1415
|
+
const target = ownEntry(options.links, row.subjectType);
|
|
1416
|
+
if (target === void 0) return deny("invalid-token", "subject-type-unknown");
|
|
1417
|
+
const roles = idList(ownEntry(target.roles, row.role));
|
|
1418
|
+
if (roles === void 0 || roles.length === 0) return deny("unknown-role");
|
|
1419
|
+
const now = nowSeconds(options.now);
|
|
1420
|
+
const ttl = Math.min(Math.max(1, options.ttl ?? DEFAULT_TTL), MAX_TTL);
|
|
1421
|
+
const rowExpiry = row.expiresAt === void 0 ? Number.POSITIVE_INFINITY : Math.floor(row.expiresAt.epochMilliseconds / 1e3);
|
|
1422
|
+
const expiresAt = Math.min(now + ttl, rowExpiry);
|
|
1423
|
+
if (expiresAt <= now) return deny("invalid-token", "expired");
|
|
1424
|
+
let capability;
|
|
1425
|
+
try {
|
|
1426
|
+
capability = capabilityOf({
|
|
1427
|
+
id: row.id,
|
|
1428
|
+
on: {
|
|
1429
|
+
resource: target.resource,
|
|
1430
|
+
id: row.subjectId
|
|
1431
|
+
},
|
|
1432
|
+
roles,
|
|
1433
|
+
expiresAt
|
|
1434
|
+
});
|
|
1435
|
+
} catch {
|
|
1436
|
+
return deny("schema", "invalid-claims");
|
|
1437
|
+
}
|
|
1438
|
+
try {
|
|
1439
|
+
const policy = await options.linkPolicy?.(capability, row);
|
|
1440
|
+
if (policy !== void 0 && linkPolicyViolation(capability, policy, void 0) !== void 0) return deny("invalid-token", "link-policy");
|
|
1441
|
+
} catch {
|
|
1442
|
+
return deny("source-threw");
|
|
1443
|
+
}
|
|
1444
|
+
return capabilitySubject(capability);
|
|
1445
|
+
}
|
|
1446
|
+
//#endregion
|
|
1447
|
+
//#region src/better-supabase/workflow.ts
|
|
1448
|
+
/** Whether `permdock` is granted the permission `key` (a catalog key or scope) with no row. */
|
|
1449
|
+
function grants(permdock, key) {
|
|
1450
|
+
try {
|
|
1451
|
+
const permission = findPermission(permdock.permissions, key);
|
|
1452
|
+
return permission !== void 0 && permdock.decide(permission, void 0).outcome === "granted";
|
|
1453
|
+
} catch {
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* The `can` option of better-supabase's `authorizeHook`, for the caller's
|
|
1459
|
+
* `permdock`: `true` only when the caller is a member of the hook's tenant
|
|
1460
|
+
* and `decide` grants the permission its `hookMetadata` names there. An
|
|
1461
|
+
* unknown key, `approval-required` and a throw are `false`.
|
|
1462
|
+
*/
|
|
1463
|
+
function hookCan(permdock) {
|
|
1464
|
+
return (tenant, key) => {
|
|
1465
|
+
try {
|
|
1466
|
+
return permdock.tenants().includes(tenant) && grants(permdock.tenant(tenant), key);
|
|
1467
|
+
} catch {
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* The `can` option of better-supabase's `protectWebHandler`: `undefined`
|
|
1474
|
+
* (401) when the request has no signed-in principal or `getPermDock`
|
|
1475
|
+
* throws, otherwise whether `decide` grants the permission in the request's
|
|
1476
|
+
* active tenant. An unknown key and `approval-required` are `false` (403).
|
|
1477
|
+
*/
|
|
1478
|
+
function handlerCan(source) {
|
|
1479
|
+
return async (request, key) => {
|
|
1480
|
+
let permdock;
|
|
1481
|
+
try {
|
|
1482
|
+
permdock = await source.getPermDock(request);
|
|
1483
|
+
} catch {
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
if (permdock.subject.principal === null) return;
|
|
1487
|
+
return grants(permdock, key);
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
//#endregion
|
|
1491
|
+
export { accessReviewApplier, apiKeyClaimOptions, apiKeyVerifier, authorizationHooks, authorizationProvider, authorizer, bucketPolicy, credentialGuard, handlerCan, hookCan, principalSource, subjectFromBetterSupabase, subjectFromShareLink, subscriberPolicy, supportPolicy, toolPolicy, topicPolicy };
|