permdock 0.6.0 → 0.7.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 +1 -0
- package/dist/{2020-C9VN-x8c.js → 2020-CJbO8Bvz.js} +1 -3
- package/dist/a2a/index.d.ts +5 -5
- package/dist/a2a/index.js +4 -4
- package/dist/{adoption-scan-DCYe60Pi.js → adoption-scan-D-feE7BE.js} +1 -1
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1 -1
- package/dist/ai-sdk/index.d.ts +21 -6
- package/dist/ai-sdk/index.js +19 -9
- package/dist/{approval-prompt-DvSSPQJX.d.ts → approval-prompt-K0faOLFp.d.ts} +2 -2
- package/dist/approvals/index.d.ts +4 -4
- package/dist/approvals/index.js +4 -4
- package/dist/{approvals-p1PGoVjb.js → approvals-CMRfsbBh.js} +74 -48
- package/dist/{arazzo-41WuzHou.js → arazzo-ClrF3PVb.js} +2 -2
- package/dist/{arazzo-CaUVBn7D.js → arazzo-rdeJ8n1J.js} +4 -4
- package/dist/{args-IIolxxKA.js → args-CC3OwyHW.js} +112 -2
- package/dist/astro/index.d.ts +6 -6
- package/dist/astro/index.js +2 -2
- package/dist/{audience-DabClFLQ.js → audience-BrMs8c4o.js} +1 -1
- package/dist/{auth-DRLEizbL.js → auth-CscUX8Sy.js} +3 -4
- package/dist/authzen/index.d.ts +162 -22
- package/dist/authzen/index.js +315 -146
- package/dist/{automation-BNM3TtDo.d.ts → automation-DOtAFWdy.d.ts} +3 -3
- package/dist/{automation-DVjOckjV.js → automation-ySsxp_Yp.js} +1 -1
- package/dist/base-url-BQbddZO5.js +9 -0
- package/dist/better-auth/index.d.ts +2 -2
- package/dist/better-auth/index.js +2 -2
- package/dist/better-supabase/index.d.ts +50 -8
- package/dist/better-supabase/index.js +94 -32
- package/dist/{bind-CAvgUuKs.js → bind-D1nO6fiC.js} +5 -5
- package/dist/{bind-DOyrv6zi.d.ts → bind-JDuvxjuW.d.ts} +7 -7
- package/dist/{build-instance-Bu_eDqYw.js → build-instance-DxE7czo9.js} +1 -1
- package/dist/{canonical-json-BC8_nMTw.js → canonical-json-DwilZ1iB.js} +6 -3
- package/dist/{capability-Drg-1YhL.d.ts → capability-D4UC9TTW.d.ts} +2 -2
- package/dist/{capability-N1K9IpqS.js → capability-L7NnfhiE.js} +1 -1
- package/dist/{casbin-Cr7RLtA3.js → casbin-BHijLuHY.js} +2 -2
- package/dist/{casbin-Dyu2gnUD.js → casbin-BW9pS47J.js} +1 -1
- package/dist/{casl-64gpT-8x.js → casl-CmJlgziS.js} +2 -2
- package/dist/{casl-DjEDhTCP.js → casl-UbI8v-Wq.js} +1 -1
- package/dist/catalog/index.d.ts +70 -10
- package/dist/catalog/index.js +112 -6
- package/dist/catalog-BCCjSAdH.js +1 -0
- package/dist/{catalog-DZVbrF-z.js → catalog-BaMQfQ6Y.js} +3 -3
- package/dist/{catalog-HD2gf5mY.js → catalog-D69WhFii.js} +43 -13
- package/dist/{catalog-doc-Cdn7-0pQ.js → catalog-doc-CV9s6B1S.js} +28 -5
- package/dist/{catalog-fingerprint-CCP0-yQS.js → catalog-fingerprint-C144oW4M.js} +1 -1
- package/dist/{create-CQWIpUG-.js → challenge-nGWma08S.js} +122 -25
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/index.js +2 -2
- package/dist/chat-sdk/index.d.ts +3 -3
- package/dist/checks-BC_0mPIq.js +37 -0
- package/dist/claude-agent/index.d.ts +3 -3
- package/dist/claude-agent/index.js +2 -2
- package/dist/clerk/index.d.ts +3 -3
- package/dist/clerk/index.js +1 -1
- package/dist/cli/bin.js +2 -2
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cloud/index.d.ts +127 -11
- package/dist/cloud/index.js +251 -3
- package/dist/{cloud-ytQ3uviA.js → cloud-CO8pYtwr.js} +5 -5
- package/dist/{codemod-ua2hHy7w.js → codemod-BP6n8N9L.js} +3 -3
- package/dist/{codemod-5mpSpnUY.js → codemod-i4N2BhDc.js} +3 -3
- package/dist/{collect-mLTY4nwb.js → collect-BaWeMjwV.js} +17 -8
- package/dist/{collect-Ci6akVwH.js → collect-DNm3oKOk.js} +6 -5
- package/dist/collect-k_Dulxr5.js +1 -0
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -1
- package/dist/{compile-CMqMx_IN.js → compile-BAEO5bOk.js} +2 -2
- package/dist/{compile-1bsAezL0.d.ts → compile-COM2VKuN.d.ts} +3 -3
- package/dist/{completion-BTFrHZhX.js → completion-BNdVf7X2.js} +3 -3
- package/dist/{config-bs_kZeYq.js → config-CoQHDrDi.js} +7 -3
- package/dist/{config-Bj2Ixq3d.js → config-DGZzMV1B.js} +2 -2
- package/dist/{config-report-DSpsLpCE.js → config-report-Cm7UITNA.js} +3 -3
- package/dist/connector-schema-_VCSsdtD.js +433 -0
- package/dist/{context-B_u3_bDJ.js → context--uM9V3Ui.js} +2 -2
- package/dist/convex/index.d.ts +5 -5
- package/dist/convex/index.js +2 -2
- package/dist/core-B5-w6Kw9.js +335 -0
- package/dist/core-CQkE8Esr.d.ts +179 -0
- package/dist/{create-B30OMUOJ.d.ts → create-B2-CQqCI.d.ts} +5 -5
- package/dist/{create-CGm28gyI.js → create-Cb86Z8Im.js} +9 -8
- package/dist/{create-DCF2SvoT.js → create-DJrlhEp9.js} +5 -5
- package/dist/{create-D_NmPOdH.d.ts → create-QHuuPw3q.d.ts} +7 -7
- package/dist/{create-CBMmFie-.d.ts → create-UTnUVETj.d.ts} +30 -10
- package/dist/{credential-BcoiejpY.js → credential-CIM7wHwA.js} +4 -4
- package/dist/{credentials-DPyHmYz5.js → credentials-BpuTwd7a.js} +1 -1
- package/dist/{deciding-columns-Sq12mEA6.js → deciding-columns-DRo2QM6E.js} +13 -2
- package/dist/{delegation-j6-rc37o.js → delegation-C5rOdTp5.js} +5 -5
- package/dist/delivery/index.d.ts +5 -5
- package/dist/delivery/index.js +4 -3
- package/dist/{delivery-DCAgNyCn.js → delivery-rwOKLWRm.js} +51 -2
- package/dist/{denied-text-CiFQNuMV.d.ts → denied-text-D4qkEtFz.d.ts} +2 -2
- package/dist/{describe-BybMRY7A.js → describe-DZlnEtLy.js} +1 -0
- package/dist/{describe-B5qgDvEW.d.ts → describe-O0SwYIK8.d.ts} +2 -2
- package/dist/{diff-CmuSD3vc.js → diff-Btr1c1Ow.js} +2 -2
- package/dist/{diff-3jbx6Rm4.js → diff-DzT-xD13.js} +10 -10
- package/dist/{doctor-BdZw53-k.js → doctor-BU1szn63.js} +241 -49
- package/dist/{doctor-3ZqNHX_G.js → doctor-BoK_y0KO.js} +4 -4
- package/dist/drizzle/index.d.ts +3 -3
- package/dist/drizzle/index.js +1 -1
- package/dist/elysia/index.d.ts +5 -5
- package/dist/elysia/index.js +3 -3
- package/dist/{errors-DH5QEGXh.d.ts → errors-B_Qg9D3e.d.ts} +2 -2
- package/dist/{errors-CWTY6uih.js → errors-DpP_U6Bf.js} +1 -0
- package/dist/{evaluate-CNClBvcj.js → evaluate-B_oqFbWC.js} +55 -12
- package/dist/{evaluations-C_1vdSZc.d.ts → evaluations-BCddEOfO.d.ts} +4 -4
- package/dist/{evaluations-beAooV0n.js → evaluations-DReoiVBw.js} +4 -4
- package/dist/eve/index.d.ts +5 -5
- package/dist/eve/index.js +3 -3
- package/dist/express/index.d.ts +5 -5
- package/dist/express/index.js +3 -3
- package/dist/fastify/index.d.ts +5 -5
- package/dist/fastify/index.js +3 -3
- package/dist/{fetch-adapter-Dw0MTS3D.d.ts → fetch-adapter-CuhyYwC1.d.ts} +4 -4
- package/dist/{fetch-adapter-DD_gfmrC.js → fetch-adapter-De-u6vhJ.js} +3 -3
- package/dist/{fields-OYG_JR78.js → fields-ClcQLrVc.js} +48 -13
- package/dist/{files-C5ZI27eV.js → files-acUbNwWt.js} +15 -2
- package/dist/{fixtures-B7XrLeY7.js → fixtures-BTMW6zgU.js} +2 -2
- package/dist/{flags-C3DktBDY.js → flags-aaeRAdiD.js} +2 -2
- package/dist/{from-snapshot-CTCMsFZk.js → from-snapshot-Bz469dIh.js} +17 -10
- package/dist/generate-3FNO5P5Q.js +159 -0
- package/dist/{grantee-hxrUsbpM.js → grantee-BrRANrpT.js} +1 -1
- package/dist/{grantee-conditions-CcXf8hMM.js → grantee-conditions-C6P1Tvol.js} +1 -1
- package/dist/{grantee-match-DbENBHLB.js → grantee-match-0RtHWQDx.js} +3 -3
- package/dist/h3/index.d.ts +4 -4
- package/dist/h3/index.js +3 -3
- package/dist/{helpers-CBYVMjwh.js → helpers-DY3vXuGs.js} +3 -3
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +3 -3
- package/dist/{hosted-BPk51-kX.d.ts → hosted-BmjzxQhp.d.ts} +14 -2
- package/dist/{hosted-B46-5LYE.js → hosted-XBgU6RGT.js} +34 -12
- package/dist/{http-DXJ37RDA.js → http-Ca3Sr5x1.js} +1 -1
- package/dist/{import-wyqXUyjm.js → import-Bz0GefWg.js} +5 -5
- package/dist/{import-CNV7I2Dw.js → import-DB2EG_iy.js} +3 -3
- package/dist/{index-C-6Letxg.d.ts → index-BE8y3moU.d.ts} +50 -8
- package/dist/{index-B8YjkEZ4.d.ts → index-BSfRl0hZ.d.ts} +6 -6
- package/dist/{index-DuWxc5dt.d.ts → index-BTkwrK7v.d.ts} +143 -14
- package/dist/index-CMiPH2Tv.d.ts +206 -0
- package/dist/{index-Ch8LpNDp.d.ts → index-DH9DUDEM.d.ts} +18 -3
- package/dist/{index-CueFnxQc.d.ts → index-DLwcuCN_.d.ts} +86 -7
- package/dist/index.d.ts +18 -17
- package/dist/index.js +27 -25
- package/dist/{init-GG6Jh8HD.js → init-5TXNji88.js} +5 -5
- package/dist/{init-DeMYsnYJ.js → init-B-4t1NzC.js} +4 -4
- package/dist/{instance-options-BsVpyGIk.d.ts → instance-options-CU0YQCLH.d.ts} +1 -1
- package/dist/{instance-snapshot-BOEhcqIF.js → instance-snapshot-CjcwnEiV.js} +16 -11
- package/dist/jwt/index.d.ts +83 -5
- package/dist/jwt/index.js +143 -5
- package/dist/{kernel-D0NVb1cK.js → kernel-DVExUwkp.js} +138 -12
- package/dist/kysely/index.d.ts +3 -3
- package/dist/kysely/index.js +1 -1
- package/dist/load-DrMHXzgN.js +1 -0
- package/dist/{load-DFS_ry0W.js → load-DxroEzH1.js} +2 -2
- package/dist/{login-Dfe5RlYk.js → login-ClSJh5t5.js} +4 -4
- package/dist/{login-imhuwvn3.js → login-WC9HPJpU.js} +6 -10
- package/dist/{manifest-C8IzW3Do.js → manifest-B79YuF2T.js} +6 -2
- package/dist/{manifest-mx5ghSFM.d.ts → manifest-BVhExYhw.d.ts} +23 -2
- package/dist/{manifest-dvL2XEe1.js → manifest-Dv6q4i5s.js} +54 -35
- package/dist/{manifest-schema-C0kK_8V0.js → manifest-schema-Cv492Qev.js} +37 -7
- package/dist/{map-claims-D3oRmTk0.js → map-claims-etGIS_Dv.js} +14 -11
- package/dist/{map-error-Dr70oA_3.js → map-error-BQGyDm_M.js} +1 -1
- package/dist/mcp/index.d.ts +64 -10
- package/dist/mcp/index.js +151 -22
- package/dist/{migrate-CIq4-E9F.js → migrate-Dodel0IJ.js} +77 -24
- package/dist/nest/index.d.ts +5 -5
- package/dist/nest/index.js +3 -3
- package/dist/next/index.d.ts +9 -9
- package/dist/next/index.js +5 -5
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +2 -2
- package/dist/node/index.d.ts +5 -5
- package/dist/node/index.js +2 -2
- package/dist/openai/index.d.ts +5 -5
- package/dist/openai/index.js +3 -3
- package/dist/openapi/index.d.ts +14 -4
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-C4dDTVEd.js → openapi-DMocl1bh.js} +4 -4
- package/dist/{openapi-B7HZ0f-r.js → openapi-Ddb4628T.js} +2 -7
- package/dist/{openapi-DD1SV-5-.js → openapi-ztVaux6j.js} +8 -8
- package/dist/{operation-findings-DgATDpvh.js → operation-findings-kRcEp8lE.js} +1 -1
- package/dist/{operations-X0VM7F3m.d.ts → operations-BNLes61x.d.ts} +1 -1
- package/dist/orpc/index.d.ts +6 -6
- package/dist/orpc/index.js +4 -4
- package/dist/otel/index.d.ts +3 -3
- package/dist/otel/index.js +1 -1
- package/dist/{output-1im__-08.js → output-DgSDLrta.js} +7 -3
- package/dist/{output-files-DhRBBE4n.js → output-files-CCMdcFWb.js} +2 -2
- package/dist/output-files-DPAY7-GX.js +1 -0
- package/dist/{ownership-DqaJNJPn.js → ownership-Z2LpxsGa.js} +443 -40
- package/dist/parse-Ddhz2xf4.js +148 -0
- package/dist/{parse-local-manifest-CVSs6J9I.js → parse-local-manifest-T1Q8X3A2.js} +24 -1
- package/dist/{parse-local-manifest-5ODwqu8b.d.ts → parse-local-manifest-e4mdwAKd.d.ts} +1 -1
- package/dist/pdp/index.d.ts +3 -3
- package/dist/pdp/index.js +7 -3
- package/dist/peer-12Q5I_RH.js +976 -0
- package/dist/{peer-CRvC7Hg1.js → peer-DOzzMfZL.js} +1 -1
- package/dist/{permdock-BrAscV28.js → permdock-BfWEeMqk.js} +177 -49
- package/dist/{permdock-SV8ZINYT.d.ts → permdock-Dco6dyCK.d.ts} +34 -5
- package/dist/{permission-registry-Br4jSNVi.js → permission-registry-DpPtSKNZ.js} +1 -1
- package/dist/{vocabulary-CN8H_9UF.d.ts → permission-types-DhNEmoRG.d.ts} +4 -64
- package/dist/{permissions-u_KHg-sN.d.ts → permissions-CoqCOfJ3.d.ts} +2 -1
- package/dist/{permissions-B-wCP5ZC.js → permissions-CyH2MV_B.js} +1 -1
- package/dist/{permissions-BPxlwVhN.js → permissions-DArULThq.js} +4 -4
- package/dist/pkce-5h8NgMQJ.js +21 -0
- package/dist/{policies-DP2nqRoF.js → policies-B2lZIurj.js} +3 -3
- package/dist/{policy-77FSAq_V.d.ts → policy-B7dg_yzu.d.ts} +2 -2
- package/dist/{policy-DbM5YYtV.js → policy-DaJy4nK4.js} +17 -8
- package/dist/{policy-from-catalog-C2gMa2Dj.js → policy-from-catalog-DFWYkNdu.js} +9 -9
- package/dist/{policy-types-n0uRTsW8.d.ts → policy-types-B-F-qAZk.d.ts} +83 -4
- package/dist/postgres-store-BL9M_CA1.js +1001 -0
- package/dist/{postgrest-CEgRbefE.d.ts → postgrest-CYacM_u7.d.ts} +12 -1
- package/dist/{postgrest-DAjUkF9w.js → postgrest-r5faDIzD.js} +37 -4
- package/dist/{powersync-DISt6SUe.js → powersync-13odBwiM.js} +3 -3
- package/dist/powersync-53ryOZip.js +1059 -0
- package/dist/powersync-CExtTLyA.js +1 -0
- package/dist/preset/index.js +1 -1
- package/dist/{preset-DZdU0tne.js → preset-CHTbd-DQ.js} +2 -2
- package/dist/prisma/index.d.ts +2 -2
- package/dist/prisma/index.js +1 -1
- package/dist/{problem-details-B45V5d2b.d.ts → problem-details-Tcfmj3bV.d.ts} +1 -1
- package/dist/prompt-DwtVemER.js +935 -0
- package/dist/{protected-RFGpakN9.d.ts → protected-BwZYL1X_.d.ts} +6 -5
- package/dist/{protected-BlF0F6wL.js → protected-DXE3cQYg.js} +2 -2
- package/dist/{provider-B1al-AZz.js → provider-BQsTYYy0.js} +2 -2
- package/dist/{provider-DILL9oPK.d.ts → provider-Csk-THAn.d.ts} +1 -1
- package/dist/{provider-store-DM6DNcta.js → provider-store-IgqQipCO.js} +2 -2
- package/dist/{pull-jEwbf80J.js → pull-CN5OLxrF.js} +7 -7
- package/dist/pull-Cty6Ovyu.js +1 -0
- package/dist/{push-BD4f15co.js → push-tVdG8dUL.js} +54 -14
- package/dist/{pushed-policy-DCW91KQU.d.ts → pushed-policy-BbcY9r_4.d.ts} +1 -1
- package/dist/{pushed-policy-CqtxkV4G.js → pushed-policy-CmYeSQqq.js} +1 -1
- package/dist/react/index.d.ts +6 -6
- package/dist/react/index.js +3 -3
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -1
- package/dist/react-native/index.d.ts +25 -11
- package/dist/react-native/index.js +209 -21
- package/dist/{refs-K6eWUJ0H.js → refs-CNJBiTFo.js} +15 -1
- package/dist/{replay-Bkm-Ne4l.d.ts → replay-DCDD_ZfL.d.ts} +1 -1
- package/dist/{request-context-C81Pip9n.js → request-context-BykKLmyE.js} +2 -2
- package/dist/resend/index.d.ts +4 -4
- package/dist/{retire-CALjIQ7e.js → retire-Wj6dkyDE.js} +2 -2
- package/dist/{rls-BAqP4q66.js → rls-Ck_PocSp.js} +32 -8
- package/dist/{rls-DTvYd-lI.js → rls-CxBZb4rZ.js} +478 -534
- package/dist/{role-builder-uWQZ4v84.js → role-builder-C1VJzvcr.js} +3 -3
- package/dist/rolldown-runtime-C3I60J0A.js +4 -0
- package/dist/{rpc-VSl5ylaQ.js → rpc-Bk1bE-ch.js} +1 -1
- package/dist/run-Bz7DXGEv.js +1 -0
- package/dist/{run-ticket-sxFI6LAk.js → run-ticket-B5d_63Ma.js} +22 -1
- package/dist/{run-ticket-DocwaTKn.d.ts → run-ticket-BpNi7BCi.d.ts} +8 -3
- package/dist/{run-CUKuz0A8.js → run-uLD2aWuO.js} +7 -7
- package/dist/{runtime-D3VTAiCA.js → runtime-Vif3_-zf.js} +1 -1
- package/dist/{saas-BCXRuXca.js → saas-Az_Kz-4D.js} +7 -7
- package/dist/{scan-qvOe0hS3.js → scan-DSljDE1Y.js} +1 -1
- package/dist/{schema-yLuBWWsh.js → schema-gIcCx7zM.js} +1 -1
- package/dist/scim/index.d.ts +3 -2
- package/dist/scim/index.js +3 -1
- package/dist/scim-C7JYhySg.js +44 -0
- package/dist/{scim-ClHtpyV3.js → scim-YNieBO1U.js} +3 -537
- package/dist/{scopes-CUPSjo9q.js → scopes-BHsc-wp-.js} +1 -1
- package/dist/server/index.d.ts +11 -11
- package/dist/server/index.js +7 -7
- package/dist/{server-C3WXAHZj.js → server-Bnga6I50.js} +3 -3
- package/dist/{server-types-CKQiCnON.d.ts → server-types-C4gZUEZy.d.ts} +9 -9
- package/dist/{shared-DvvFzYXe.js → shared-C8LjUdVU.js} +1 -1
- package/dist/{shared-BAK8t4Uo.js → shared-fMKOv8hP.js} +44 -38
- package/dist/{sink-4-eQIRWf.d.ts → sink-Cp_Eot9W.d.ts} +4 -4
- package/dist/{skills-B6lVdD_W.js → skills-Dcjw9_1C.js} +5 -5
- package/dist/{skills-CTYwkl44.js → skills-g_3BS9NC.js} +3 -3
- package/dist/{snapshot-B7DkYg6g.js → snapshot-CbGmYiJx.js} +4 -4
- package/dist/{snapshot-cache-DZHQ0KF6.js → snapshot-cache-DT2Kx1F8.js} +1 -1
- package/dist/{snapshot-cache-PXI-vGpY.d.ts → snapshot-cache-Pahd3IYJ.d.ts} +1 -1
- package/dist/solid/index.d.ts +6 -5
- package/dist/solid/index.js +4 -4
- package/dist/solid-start/index.d.ts +6 -6
- package/dist/solid-start/index.js +3 -3
- package/dist/{sources-vs5dGOsS.js → sources-B-zYB4GS.js} +111 -25
- package/dist/{sources-Xc4zkszI.d.ts → sources-DEEuGb8S.d.ts} +63 -14
- package/dist/sources-qoIJQrJv.js +11 -0
- package/dist/{sql-B5T1IL83.js → sql-B96movIM.js} +5 -1
- package/dist/sql-CDEPCA0g.d.ts +10 -0
- package/dist/{src-Cu2m75uo.js → src-DLFrtrrC.js} +140 -48
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +640 -211
- package/dist/{standard-webhooks-rbD7LS6h.d.ts → standard-webhooks-B_qRg0ws.d.ts} +2 -2
- package/dist/{store-BDutJkDL.js → store-DSP9oGvh.js} +15 -7
- package/dist/{store-options-u0ycEe8s.js → store-options-B_poyYvg.js} +6 -6
- package/dist/{stores-BUGNbJTt.d.ts → stores-BAZwSYZA.d.ts} +6 -5
- package/dist/{stores-CGhM8BN_.js → stores-BqEuHC05.js} +2 -2
- package/dist/{stream-CeCMJmBB.d.ts → stream-2Vwhl7Er.d.ts} +2 -2
- package/dist/{style-Uao76wO7.js → style-Cqo7-73x.js} +1 -0
- package/dist/{style-DiKWnAvn.js → style-Ds1gNm77.js} +1 -1
- package/dist/supabase/index.d.ts +8 -7
- package/dist/supabase/index.js +5 -5
- package/dist/supabase/middleware.d.ts +6 -6
- package/dist/supabase/middleware.js +6 -6
- package/dist/{supabase-x5JtR2tc.js → supabase--0aJdLEd.js} +8 -6
- package/dist/{supabase-By_frk3U.js → supabase-Bqp7iIY2.js} +3 -3
- package/dist/supabase-hook-DJxC4PoP.js +1 -0
- package/dist/{supabase-hook-BMoEhZir.js → supabase-hook-Dg0SrQWR.js} +1060 -119
- package/dist/supautils-MESd81bF.js +689 -0
- package/dist/{support-session-C6xJQYut.js → support-session-Cna49-3f.js} +2 -2
- package/dist/svelte/index.d.ts +5 -5
- package/dist/svelte/index.js +5 -5
- package/dist/svelte/runtime.d.ts +3 -3
- package/dist/svelte/runtime.js +3 -3
- package/dist/svelte/source.d.ts +5 -5
- package/dist/svelte/source.js +2 -2
- package/dist/sveltekit/index.d.ts +6 -6
- package/dist/sveltekit/index.js +2 -2
- package/dist/tanstack-start/index.d.ts +6 -6
- package/dist/tanstack-start/index.js +2 -2
- package/dist/{tenancy-DN2QAx9z.js → tenancy-CmeBQ6Nv.js} +12 -3
- package/dist/terminal/index.d.ts +6 -6
- package/dist/terminal/index.js +3 -3
- package/dist/testing/core.d.ts +2 -0
- package/dist/testing/core.js +1 -0
- package/dist/testing/fixtures/index.d.ts +12 -0
- package/dist/testing/fixtures/index.js +770 -0
- package/dist/testing/index.d.ts +41 -174
- package/dist/testing/index.js +241 -731
- package/dist/testing/saas/index.d.ts +5 -5
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -1
- package/dist/text-diff-DhITNCnD.js +444 -0
- package/dist/{token-BLZcjQgt.js → token-2qYDvXz2.js} +1 -1
- package/dist/{tool-refusal-CMLN5xhn.js → tool-refusal-reAhIzdl.js} +1 -1
- package/dist/trpc/index.d.ts +6 -6
- package/dist/trpc/index.js +3 -3
- package/dist/{types-Dfv0aTER.d.ts → types-BWZybqs9.d.ts} +110 -8
- package/dist/{types-Druu9wLM.d.ts → types-BXMYB7BY.d.ts} +2 -2
- package/dist/{types-BzzaHxt3.d.ts → types-BfVmNJO5.d.ts} +14 -3
- package/dist/{types-lUlG3TVd.d.ts → types-CIeZcG8v.d.ts} +2 -2
- package/dist/{types-B3_ooZgZ.d.ts → types-CKV71kKh.d.ts} +4 -4
- package/dist/{types-CX7AvRqP.d.ts → types-CQVEQawv.d.ts} +4 -4
- package/dist/{types-C863OQiR.d.ts → types-CeIscIKp.d.ts} +4 -4
- package/dist/{types-C_IiEs64.d.ts → types-CfiKC5aN.d.ts} +1 -1
- package/dist/{types-_WhUP7ud.d.ts → types-D3A19w4A.d.ts} +8 -4
- package/dist/{types-8RnwUAvE.d.ts → types-DBHftjgS.d.ts} +15 -8
- package/dist/{types-BdtSDbBO.d.ts → types-DC44-hqb.d.ts} +2 -2
- package/dist/{types-CVZSNocA.d.ts → types-DXmwu78Z.d.ts} +4 -4
- package/dist/{types-C791HFu9.d.ts → types-cTos-H7p.d.ts} +2 -2
- package/dist/{types-BtHtcKMr.d.ts → types-g7Oy6Zg4.d.ts} +3 -3
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/{usage-SveTRTyr.js → usage-Bybmck7f.js} +7 -7
- package/dist/{usage-CRVl9V3t.js → usage-CDaLXCfL.js} +2 -2
- package/dist/usage-y-kBNdqs.js +1 -0
- package/dist/{validation-error-DHUrAkku.d.ts → validation-error-CQpoquwj.d.ts} +1 -1
- package/dist/{verify-BaAKx7t6.js → verify-pe8rL_Qw.js} +350 -83
- package/dist/{views-DTIIO-oQ.js → views-BB8E9dTP.js} +1 -1
- package/dist/vocabulary-Bg3eeT2H.d.ts +67 -0
- package/dist/vue/index.d.ts +6 -5
- package/dist/vue/index.js +4 -4
- package/dist/{waiter-B986w1oK.d.ts → waiter-DwEyouan.d.ts} +2 -2
- package/dist/watch-Bd9mx3rd.js +1 -0
- package/dist/{watch-vea54PRw.js → watch-C-4VmFlL.js} +3 -3
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +2 -2
- package/dist/{where-scope-C9vfEphl.js → where-scope-wq2coopv.js} +41 -21
- package/dist/{who-can-DWBAcaCQ.js → who-can-BwNsI_Js.js} +6 -6
- package/dist/workflow/index.d.ts +3 -3
- package/dist/workflow/index.js +5 -5
- package/dist/workflow-sdk/index.d.ts +5 -5
- package/dist/{wrap-C5jC6sKI.d.ts → wrap-B6R0cMRL.d.ts} +2 -2
- package/dist/{wrap-gX61EtpB.js → wrap-CiSc_KCq.js} +8 -2
- package/package.json +22 -11
- package/schemas/cli-result-v1.json +1 -0
- package/schemas/cloud-api-v1.json +152 -7
- package/schemas/connector-v1.json +8 -0
- package/schemas/supabase-claims-v1.json +5 -0
- package/schemas/supabase-manifest-v1.json +276 -75
- package/skills/permdock/SKILL.md +1 -0
- package/skills/permdock-agents/references/runtimes.md +3 -1
- package/skills/permdock-approvals/references/stores-and-resume.md +2 -1
- package/skills/permdock-data/references/queries-and-rls.md +4 -1
- package/skills/permdock-tenancy/SKILL.md +1 -1
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +1 -0
- package/skills/permdock-wire/references/adapters.md +14 -0
- package/skills/permdock-wire/references/policy-options.md +1 -0
- package/dist/catalog-BoWglDPI.js +0 -1
- package/dist/collect-D7eQDffE.js +0 -1
- package/dist/index-Cp_ilKi1.d.ts +0 -91
- package/dist/load-BxEtwlBS.js +0 -1
- package/dist/output-files-B3IZJksC.js +0 -1
- package/dist/parse-Cxq0WxrA.js +0 -53
- package/dist/peer-C8swJ2GH.js +0 -50
- package/dist/powersync-BkWWCS1K.js +0 -538
- package/dist/powersync-MMJ2mI2q.js +0 -1
- package/dist/prompt-DfZLJLIn.js +0 -32
- package/dist/pull-CvpINcMK.js +0 -1
- package/dist/run-DDwUaJDU.js +0 -1
- package/dist/supabase-hook-CCw5m35C.js +0 -1
- package/dist/supautils-B6HYL9eL.js +0 -91
- package/dist/text-diff-BQcUapOb.js +0 -15
- package/dist/usage-CZpSQP6L.js +0 -1
- package/dist/watch--ovlPOc0.js +0 -1
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import { c as listPermissions, u as renamedKeys } from "./permission-registry-DpPtSKNZ.js";
|
|
2
|
+
import { f as scopeList, s as resolveScope } from "./scopes-BHsc-wp-.js";
|
|
3
|
+
import { o as flattenGrantee } from "./grantee-BrRANrpT.js";
|
|
4
|
+
import { c as policyLevels } from "./policy-DaJy4nK4.js";
|
|
5
|
+
import { a as describeError, i as configError } from "./errors-DpP_U6Bf.js";
|
|
6
|
+
import { d as sqlTextArray, i as qualifySqlTable } from "./sql-B96movIM.js";
|
|
7
|
+
import "./budget-NbEtbJC_.js";
|
|
8
|
+
import { l as roleColumn } from "./sources-B-zYB4GS.js";
|
|
9
|
+
import { t as beforeFirst } from "./text-BMFRfBqc.js";
|
|
10
|
+
import { c as parseSplit } from "./output-files-CCMdcFWb.js";
|
|
11
|
+
import { x as resolveAuthorize } from "./supabase-hook-Dg0SrQWR.js";
|
|
12
|
+
import { A as quoteQualified, C as parseMembershipsFlag, D as qualified, L as tenantTypeOf, O as quoteIdent, a as customRolesOn, j as quoteTable, k as quoteLiteral, t as CUSTOM_ROLES } from "./shared-fMKOv8hP.js";
|
|
13
|
+
import { D as byKey, E as byCodeUnit, I as roleNames, M as assignableRoles, T as rolesTableScope, U as graphSqlName, b as tokenScopesPlan, j as apiKeysPlan, ot as checkSuspension, r as unsatisfiedKinds, t as ownershipRules, w as currentKeyExpr } from "./ownership-Z2LpxsGa.js";
|
|
14
|
+
import { t as membershipSourcesOf } from "./sources-qoIJQrJv.js";
|
|
15
|
+
import { i as group, o as sqlStatements } from "./deciding-columns-DRo2QM6E.js";
|
|
16
|
+
import { c as branchClauses, i as orSql } from "./policies-B2lZIurj.js";
|
|
17
|
+
import { d as tableNames, f as tablelessResources, i as graphPlan, l as resolveVias, r as closureDepths, x as tableFor } from "./verify-pe8rL_Qw.js";
|
|
18
|
+
//#region src/cli/rls/custom-role-rebuild.ts
|
|
19
|
+
const PREFIX = "PermDock CLI";
|
|
20
|
+
/** A column of the roles-table row `r`. */
|
|
21
|
+
function col(name) {
|
|
22
|
+
return `r.${quoteIdent(name)}`;
|
|
23
|
+
}
|
|
24
|
+
function schemaOf(table) {
|
|
25
|
+
return table.includes(".") ? beforeFirst(table, ".") : "public";
|
|
26
|
+
}
|
|
27
|
+
/** The roles-table column a reference joins on, after checking it points at `roles`. */
|
|
28
|
+
function roleRef(role, table, roles, label) {
|
|
29
|
+
const pairs = Object.entries(role.on);
|
|
30
|
+
const [pair] = pairs;
|
|
31
|
+
if (pair === void 0 || pairs.length !== 1) throw configError(`${PREFIX}: ${label}.on must map exactly one column of ${table} to ${role.through}, for example { role_id: 'id' }`);
|
|
32
|
+
const through = role.through.includes(".") ? role.through : `${schemaOf(table)}.${role.through}`;
|
|
33
|
+
if (qualifySqlTable(through) !== qualifySqlTable(roles.table)) throw configError(`${PREFIX}: ${label}.through must be rls.customRoleWrites.roles.table (${roles.table}), got ${role.through}`);
|
|
34
|
+
if (role.column !== roles.key) throw configError(`${PREFIX}: ${label}.column must be rls.customRoleWrites.roles.key (${roles.key}), got ${role.column}`);
|
|
35
|
+
const [ref, id] = pair;
|
|
36
|
+
return {
|
|
37
|
+
ref,
|
|
38
|
+
id
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A legacy key as SQL the way `mapKey` maps it: `rls.migrate.keys`, then
|
|
43
|
+
* `renamed`, then the longest `rls.migrate.prefixes` match, and a former key
|
|
44
|
+
* read as its current key.
|
|
45
|
+
*/
|
|
46
|
+
function legacyKeySql(expr, migrate, renamed) {
|
|
47
|
+
const exact = new Map(Object.entries(renamed));
|
|
48
|
+
for (const [from, to] of Object.entries(migrate?.keys ?? {})) exact.set(from, to);
|
|
49
|
+
const prefixes = Object.entries(migrate?.prefixes ?? {}).toSorted(([a], [b]) => b.length - a.length || byCodeUnit(a, b));
|
|
50
|
+
const prefixed = prefixes.length === 0 ? expr : `(case ${prefixes.map(([from, to]) => `when left(${expr}, ${String(from.length)}) = ${quoteLiteral(from)} then ${quoteLiteral(to)} || substr(${expr}, ${String(from.length + 1)})`).join(" ")} else ${expr} end)`;
|
|
51
|
+
const mapped = exact.size === 0 ? prefixed : `(case ${expr} ${[...exact.entries()].toSorted(byKey).map(([from, to]) => `when ${quoteLiteral(from)} then ${quoteLiteral(to)}`).join(" ")} else ${prefixed} end)`;
|
|
52
|
+
const renames = Object.entries(renamed).toSorted(byKey);
|
|
53
|
+
return currentKeyExpr(renames, mapped);
|
|
54
|
+
}
|
|
55
|
+
function sourceSql(source, table, label) {
|
|
56
|
+
return roleColumn(source, table, "x", {
|
|
57
|
+
label,
|
|
58
|
+
prefix: PREFIX
|
|
59
|
+
}).lookup;
|
|
60
|
+
}
|
|
61
|
+
function rebuildPlan(ctx, from, migrate) {
|
|
62
|
+
const custom = ctx.customRoles;
|
|
63
|
+
const root = ctx.scopes[0]?.name;
|
|
64
|
+
if (custom === void 0 || root === void 0) throw configError(`${PREFIX}: rls.customRoles.from needs custom roles on`);
|
|
65
|
+
if (ctx.authorize !== "database") throw configError(`${PREFIX}: rls.customRoles.from needs database mode; in jwt mode custom roles ride the token, so build the grants claim with customRoleClaim`);
|
|
66
|
+
const roles = custom.table;
|
|
67
|
+
if (roles === void 0) throw configError(`${PREFIX}: rls.customRoles.from needs rls.customRoleWrites.roles, the roles table each row belongs to`);
|
|
68
|
+
const leveled = custom.levels === true;
|
|
69
|
+
if (from.permissions.level !== void 0 && !leveled) throw configError(`${PREFIX}: rls.customRoles.from.permissions.level is set, but no resource declares levels`);
|
|
70
|
+
const tenantType = tenantTypeOf(ctx);
|
|
71
|
+
const rolesTable = quoteQualified(roles.table);
|
|
72
|
+
const tenant = roles.tenant === void 0 ? `null::${tenantType}` : `${col(roles.tenant)}::${tenantType}`;
|
|
73
|
+
const scope = rolesTableScope(roles, tenant, root, col);
|
|
74
|
+
const scopeId = roles.id === void 0 || roles.tenant === void 0 ? "null::text" : `case when ${tenant} is null then null else ${col(roles.id)}::text end`;
|
|
75
|
+
const skipped = roles.skip === void 0 || roles.tenant === void 0 ? "false" : `(${tenant} is not null and coalesce(${col(roles.skip)}, false))`;
|
|
76
|
+
const renamed = custom.renamed ?? {};
|
|
77
|
+
const renames = Object.entries(renamed).toSorted(byKey);
|
|
78
|
+
const current = (expr) => currentKeyExpr(renames, expr);
|
|
79
|
+
const known = sqlTextArray([...custom.permissions ?? [], ...renames.map(([former]) => former)]);
|
|
80
|
+
const declared = sqlTextArray(custom.declared);
|
|
81
|
+
const scopes = sqlTextArray(ctx.scopes.map((item) => item.name));
|
|
82
|
+
const ceiling = qualified(ctx, CUSTOM_ROLES.ceiling);
|
|
83
|
+
const rp = qualified(ctx, "role_permissions");
|
|
84
|
+
const perms = qualified(ctx, CUSTOM_ROLES.permissions);
|
|
85
|
+
const includes = qualified(ctx, CUSTOM_ROLES.includes);
|
|
86
|
+
const replace = qualified(ctx, CUSTOM_ROLES.trustedReplace);
|
|
87
|
+
const p = from.permissions;
|
|
88
|
+
const pTable = qualifySqlTable(p.table);
|
|
89
|
+
const pRef = roleRef(p.role, p.table, roles, "rls.customRoles.from.permissions.role");
|
|
90
|
+
const legacy = sourceSql(p.permission, pTable, "rls.customRoles.from.permissions.permission");
|
|
91
|
+
const effect = p.effect === void 0 ? "'allow'" : `coalesce(lower(x.${quoteIdent(p.effect)}::text), 'allow')`;
|
|
92
|
+
const level = p.level === void 0 ? "null::text" : `nullif(x.${quoteIdent(p.level)}::text, '')`;
|
|
93
|
+
const inCeiling = (key) => `exists (select 1 from ${ceiling} c where c.scope = v.scope and c.effect = 'allow' and ${current("c.permission")} = ${key})`;
|
|
94
|
+
const hasLevels = (key) => `exists (select 1 from ${ceiling} c where c.scope = v.scope and c.effect = 'allow' and ${current("c.permission")} = ${key} and position('@' in c.grant_key) > 0)`;
|
|
95
|
+
const levelCheck = leveled ? `
|
|
96
|
+
when m.effect = 'allow' and m.level is not null and ${hasLevels("m.key")} and not exists (
|
|
97
|
+
select 1 from ${ceiling} c
|
|
98
|
+
where c.scope = v.scope and c.effect = 'allow' and ${current("c.permission")} = m.key
|
|
99
|
+
and split_part(c.grant_key, '@', 2) = m.level
|
|
100
|
+
) then 'level ' || m.level || ' is not declared'` : "";
|
|
101
|
+
const allowEntry = leveled ? "j.key || coalesce('@' || j.level, '')" : "j.key";
|
|
102
|
+
const i = from.includes;
|
|
103
|
+
const iTable = i === void 0 ? void 0 : qualifySqlTable(i.table);
|
|
104
|
+
const iRef = i === void 0 || iTable === void 0 ? void 0 : roleRef(i.role, i.table, roles, "rls.customRoles.from.includes.role");
|
|
105
|
+
const included = i === void 0 || iTable === void 0 ? void 0 : sourceSql(i.include, iTable, "rls.customRoles.from.includes.include");
|
|
106
|
+
const refs = [`${col(pRef.id)} as permissions_ref`, ...iRef === void 0 ? [] : [`${col(iRef.id)} as includes_ref`]].join(", ");
|
|
107
|
+
const includeBlock = i === void 0 || iTable === void 0 || iRef === void 0 || included === void 0 ? " v_include := null;" : ` with raw as (
|
|
108
|
+
select ${included} as role
|
|
109
|
+
from ${quoteTable(iTable)} x
|
|
110
|
+
where x.${quoteIdent(iRef.ref)} = v.includes_ref
|
|
111
|
+
),
|
|
112
|
+
judged as (
|
|
113
|
+
select n.role, case
|
|
114
|
+
when n.role is null or not (n.role = any(${declared})) then 'not a declared role'
|
|
115
|
+
when exists (
|
|
116
|
+
select 1 from ${rp} rp
|
|
117
|
+
where rp.role = n.role and rp.effect = 'allow'
|
|
118
|
+
and not ${inCeiling(current("rp.permission"))}
|
|
119
|
+
) then 'allows permissions outside the ceiling of ' || v.scope
|
|
120
|
+
end as dropped
|
|
121
|
+
from raw n
|
|
122
|
+
)
|
|
123
|
+
select array_agg(distinct j.role) filter (where j.dropped is null),
|
|
124
|
+
array_agg(v.role || ': includes ' || coalesce(j.role, 'null') || ', ' || j.dropped) filter (where j.dropped is not null)
|
|
125
|
+
into v_include, v_lost
|
|
126
|
+
from judged j;
|
|
127
|
+
v_dropped := v_dropped || coalesce(v_lost, '{}'::text[]);`;
|
|
128
|
+
const collect = ` with raw as (
|
|
129
|
+
select ${legacy} as legacy, ${effect} as effect, ${level} as level
|
|
130
|
+
from ${quoteTable(pTable)} x
|
|
131
|
+
where x.${quoteIdent(pRef.ref)} = v.permissions_ref
|
|
132
|
+
),
|
|
133
|
+
mapped as (
|
|
134
|
+
select n.legacy, ${legacyKeySql("n.legacy", migrate, renamed)} as key, n.effect, n.level
|
|
135
|
+
from raw n
|
|
136
|
+
),
|
|
137
|
+
judged as (
|
|
138
|
+
select m.legacy, m.key, m.effect, ${leveled ? `case when ${hasLevels("m.key")} then m.level end` : "m.level"} as level, case
|
|
139
|
+
when m.effect not in ('allow', 'deny') then 'effect ' || m.effect || ' is not allow or deny'
|
|
140
|
+
when m.key is null or not (m.key = any(${known})) then 'not a declared permission'
|
|
141
|
+
when m.effect = 'allow' and not ${inCeiling("m.key")} then 'outside the ceiling of ' || v.scope${levelCheck}
|
|
142
|
+
end as dropped
|
|
143
|
+
from mapped m
|
|
144
|
+
)
|
|
145
|
+
select array_agg(distinct ${allowEntry}) filter (where j.dropped is null and j.effect = 'allow'),
|
|
146
|
+
array_agg(distinct j.key) filter (where j.dropped is null and j.effect = 'deny'),
|
|
147
|
+
array_agg(v.role || ': ' || coalesce(j.legacy, 'null') || ', ' || j.dropped) filter (where j.dropped is not null)
|
|
148
|
+
into v_allow, v_deny, v_lost
|
|
149
|
+
from judged j;
|
|
150
|
+
v_dropped := v_dropped || coalesce(v_lost, '{}'::text[]);
|
|
151
|
+
${includeBlock}`;
|
|
152
|
+
return {
|
|
153
|
+
rolesTable,
|
|
154
|
+
identity: `select ${tenant} as tenant_id, ${scope} as scope, ${scopeId} as scope_id, ${col(roles.key)}::text as role, ${refs}`,
|
|
155
|
+
eligible: `not ${skipped} and not (${col(roles.key)}::text = any(${declared}))`,
|
|
156
|
+
permsRef: pRef,
|
|
157
|
+
includesRef: iRef,
|
|
158
|
+
scopes,
|
|
159
|
+
collect,
|
|
160
|
+
replace,
|
|
161
|
+
perms,
|
|
162
|
+
includes,
|
|
163
|
+
declared,
|
|
164
|
+
roles
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** `v_dropped` grouped per legacy role (`role: a, b; other: c`), capped at 50 entries. */
|
|
168
|
+
const DROPPED_LIST = `(select string_agg(g.role || ': ' || g.items, '; ' order by g.role)
|
|
169
|
+
from (
|
|
170
|
+
select split_part(l.d, ': ', 1) as role, string_agg(substr(l.d, strpos(l.d, ': ') + 2), ', ' order by l.d) as items
|
|
171
|
+
from (select u.d from unnest(v_dropped) as u(d) order by u.d limit 50) l
|
|
172
|
+
group by 1
|
|
173
|
+
) g)`;
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/cli/rls/custom-role-sync.ts
|
|
176
|
+
/**
|
|
177
|
+
* `rls.customRoles.from.sync: 'trigger'`: after-row triggers on the legacy
|
|
178
|
+
* permissions table, and on the includes table when mapped, rebuild each
|
|
179
|
+
* affected role through `permdock_trusted_replace_custom_role_grants`, with
|
|
180
|
+
* the same reads the backfill uses. An entry outside the ceiling is dropped
|
|
181
|
+
* with a warning rather than aborting the legacy write, and a tenant role
|
|
182
|
+
* whose name a shared role holds is skipped with a warning (`name-taken`).
|
|
183
|
+
*/
|
|
184
|
+
function customRoleSyncSql(ctx, from, migrate) {
|
|
185
|
+
const plan = rebuildPlan(ctx, from, migrate);
|
|
186
|
+
const shared = qualified(ctx, CUSTOM_ROLES.shared);
|
|
187
|
+
const keepIncludes = plan.includesRef === void 0 ? `
|
|
188
|
+
v_include := array(select c.include_role from ${plan.includes} c where c.tenant_id is not distinct from v.tenant_id and c.scope = v.scope and c.scope_id is not distinct from v.scope_id and c.role = v.role);` : "";
|
|
189
|
+
const one = (name, table, ref) => {
|
|
190
|
+
const fn = qualified(ctx, `${CUSTOM_ROLES.sync}_${name}`);
|
|
191
|
+
const target = quoteTable(qualifySqlTable(table));
|
|
192
|
+
const trigger = `${CUSTOM_ROLES.sync}_${name}`;
|
|
193
|
+
return `create or replace function ${fn}()
|
|
194
|
+
returns trigger
|
|
195
|
+
language plpgsql
|
|
196
|
+
volatile
|
|
197
|
+
security definer
|
|
198
|
+
set search_path = ''
|
|
199
|
+
as $$
|
|
200
|
+
declare
|
|
201
|
+
v record;
|
|
202
|
+
v_allow text[];
|
|
203
|
+
v_deny text[];
|
|
204
|
+
v_include text[];
|
|
205
|
+
v_lost text[];
|
|
206
|
+
v_dropped text[];
|
|
207
|
+
v_refs text[] := array[
|
|
208
|
+
case when tg_op <> 'INSERT' then old.${quoteIdent(ref.ref)}::text end,
|
|
209
|
+
case when tg_op <> 'DELETE' then new.${quoteIdent(ref.ref)}::text end
|
|
210
|
+
];
|
|
211
|
+
begin
|
|
212
|
+
for v in
|
|
213
|
+
${plan.identity}
|
|
214
|
+
from ${plan.rolesTable} r
|
|
215
|
+
where ${plan.eligible} and r.${quoteIdent(ref.id)}::text = any(v_refs)
|
|
216
|
+
loop
|
|
217
|
+
if v.role is null or not (v.scope = 'global' or v.scope = any(${plan.scopes})) then
|
|
218
|
+
raise warning 'permdock sync: % has scope %, which is not declared', coalesce(v.role, 'null'), coalesce(v.scope, 'null');
|
|
219
|
+
continue;
|
|
220
|
+
end if;
|
|
221
|
+
if v.tenant_id is not null and ${shared}(v.scope, v.role) then
|
|
222
|
+
raise warning 'permdock sync: % is the name of a shared custom role (name-taken); the legacy write is not copied', v.role;
|
|
223
|
+
continue;
|
|
224
|
+
end if;
|
|
225
|
+
v_dropped := '{}'::text[];
|
|
226
|
+
${plan.collect}${keepIncludes}
|
|
227
|
+
perform ${plan.replace}(v.tenant_id, v.scope, v.scope_id, v.role, v_allow, v_deny, v_include);
|
|
228
|
+
if cardinality(v_dropped) > 0 then
|
|
229
|
+
raise warning 'permdock sync: dropped % entr(ies) PermDock cannot hold: %', cardinality(v_dropped), ${DROPPED_LIST};
|
|
230
|
+
end if;
|
|
231
|
+
end loop;
|
|
232
|
+
return null;
|
|
233
|
+
end;
|
|
234
|
+
$$;
|
|
235
|
+
revoke execute on function ${fn}() from public, anon, authenticated;
|
|
236
|
+
drop trigger if exists ${quoteIdent(trigger)} on ${target};
|
|
237
|
+
create trigger ${quoteIdent(trigger)}
|
|
238
|
+
after insert or update or delete on ${target}
|
|
239
|
+
for each row execute function ${fn}();`;
|
|
240
|
+
};
|
|
241
|
+
const chunks = [`-- rls.customRoles.from.sync: a write to the application's role tables rebuilds the role's PermDock grants`, one("permissions", from.permissions.table, plan.permsRef)];
|
|
242
|
+
if (from.includes !== void 0 && plan.includesRef !== void 0) chunks.push(one("includes", from.includes.table, plan.includesRef));
|
|
243
|
+
return `${chunks.join("\n\n")}\n`;
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/cli/rls/generate-context.ts
|
|
247
|
+
/** The scopes an `rls.migrate` `role` helper names, so `rls generate` writes their role-filtered member helper. */
|
|
248
|
+
function roleMemberScopesOf(migrate) {
|
|
249
|
+
const scopes = [...new Set(Object.values(migrate?.helpers ?? {}).flatMap((helper) => helper.form === "role" ? [helper.scope] : []))].toSorted();
|
|
250
|
+
return scopes.length === 0 ? {} : { roleMemberScopes: scopes };
|
|
251
|
+
}
|
|
252
|
+
/** `rls.customRoleWrites.requires` as permission keys, or why it names none. */
|
|
253
|
+
function writeRequirement(policy, requires) {
|
|
254
|
+
if (requires === void 0) return {};
|
|
255
|
+
const leaves = listPermissions(policy.permissions);
|
|
256
|
+
const keys = requires === "manageRoles" ? leaves.filter((leaf) => leaf.meta.manageRoles === true).map((leaf) => leaf.key) : [...requires];
|
|
257
|
+
if (keys.length === 0) return { error: requires === "manageRoles" ? "rls.customRoleWrites.requires is 'manageRoles', but no permission declares meta.manageRoles" : "rls.customRoleWrites.requires names no permission" };
|
|
258
|
+
const known = new Set(leaves.map((leaf) => leaf.key));
|
|
259
|
+
const unknown = keys.filter((key) => !known.has(key));
|
|
260
|
+
if (unknown.length > 0) return { error: `rls.customRoleWrites.requires names undeclared permissions: ${unknown.join(", ")}` };
|
|
261
|
+
return { keys: [...new Set(keys)].toSorted() };
|
|
262
|
+
}
|
|
263
|
+
/** Declared role names, and those a tenant admin may compose into custom roles. */
|
|
264
|
+
function customRoleNames(policy, requires) {
|
|
265
|
+
const declared = [...roleNames(policy)].toSorted();
|
|
266
|
+
const assignable = assignableRoles(policy);
|
|
267
|
+
const renamed = renamedKeys(policy.vocabulary.permissions);
|
|
268
|
+
const leaves = listPermissions(policy.permissions);
|
|
269
|
+
const manage = leaves.filter((leaf) => leaf.meta.manageRoles === true).map((leaf) => leaf.key);
|
|
270
|
+
return {
|
|
271
|
+
declared,
|
|
272
|
+
assignable,
|
|
273
|
+
...renamed.size === 0 ? {} : { renamed: Object.fromEntries(renamed) },
|
|
274
|
+
permissions: leaves.map((leaf) => leaf.key).toSorted(),
|
|
275
|
+
...manage.length === 0 ? {} : { manage: manage.toSorted() },
|
|
276
|
+
...requires === void 0 ? {} : { requires },
|
|
277
|
+
...policy.levels === void 0 ? {} : { levels: true }
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function skippedOwnershipTriggers(triggers, scopes) {
|
|
281
|
+
if (triggers === false) return "all";
|
|
282
|
+
return Object.keys(triggers).map((key) => {
|
|
283
|
+
const name = resolveScope(scopes, key);
|
|
284
|
+
if (name === void 0) throw configError(`PermDock CLI: rls.ownershipTriggers.${key} names a scope the policy does not declare`);
|
|
285
|
+
return name;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/** The SQL context every emitter reads, from the flags, `rls` config and the policy. */
|
|
289
|
+
function buildContext(input, policy, options) {
|
|
290
|
+
const rls = input.config.rls;
|
|
291
|
+
const memberships = parseMembershipsFlag(input.memberships) ?? rls?.memberships;
|
|
292
|
+
if (input.rbac && input.dialect !== "supabase") return { refusal: `rls generate --rbac supabase needs --dialect supabase (got ${input.dialect})` };
|
|
293
|
+
const schema = input.rbacSchema ?? rls?.schema ?? rls?.rbac?.schema ?? "permdock";
|
|
294
|
+
const authorize = resolveAuthorize(input.config, {
|
|
295
|
+
authorize: input.authorize,
|
|
296
|
+
rbac: input.rbac,
|
|
297
|
+
memberships
|
|
298
|
+
});
|
|
299
|
+
const writeTable = rls?.customRoleWrites?.roles;
|
|
300
|
+
const guard = writeTable?.tenant === void 0 ? void 0 : {
|
|
301
|
+
table: writeTable.table,
|
|
302
|
+
tenant: writeTable.tenant,
|
|
303
|
+
skip: writeTable.skip,
|
|
304
|
+
declared: [...roleNames(policy)].toSorted()
|
|
305
|
+
};
|
|
306
|
+
const memberSources = membershipSourcesOf(input.config).map((source) => guard === void 0 ? source : source.guarded?.(guard) ?? source);
|
|
307
|
+
const sources = authorize === "database" ? memberSources : void 0;
|
|
308
|
+
const scopes = scopeList(policy.scopes);
|
|
309
|
+
const suspension = checkSuspension(rls?.suspension, scopes);
|
|
310
|
+
const hookRoles = input.config.supabase?.hook?.roles;
|
|
311
|
+
const roles = rls?.roles ?? (hookRoles === false ? void 0 : hookRoles);
|
|
312
|
+
if (roles !== void 0 && input.rbac) return { refusal: "rls generate --rbac supabase creates its own user_roles (user_id, role app_role); drop rls.roles (or supabase.hook.roles) or the --rbac flag" };
|
|
313
|
+
const requires = writeRequirement(policy, rls?.customRoleWrites?.requires);
|
|
314
|
+
if (requires.error !== void 0) return { refusal: requires.error };
|
|
315
|
+
if (rls?.customRoleWrites?.audit !== void 0 && rls.customRoleWrites.roles?.primaryKey === void 0) return { refusal: "rls.customRoleWrites.audit needs rls.customRoleWrites.roles.primaryKey: only the by-id writers call it" };
|
|
316
|
+
const readers = rls?.trustedReaders;
|
|
317
|
+
if (readers !== void 0 && (!Array.isArray(readers) || !readers.every((name) => typeof name === "string" && name !== ""))) return { refusal: "rls.trustedReaders must list Postgres role names, such as ['service_role']" };
|
|
318
|
+
const ownership = ownershipRules(policy, scopes);
|
|
319
|
+
const graph = graphPlan(policy);
|
|
320
|
+
const apiKeys = apiKeysPlan(rls?.apiKeys, new Set(roleNames(policy)), Object.fromEntries(renamedKeys(policy.vocabulary.permissions)));
|
|
321
|
+
const tokenScopesSetting = rls?.tokenScopes;
|
|
322
|
+
if (tokenScopesSetting !== void 0 && typeof tokenScopesSetting !== "boolean") return { refusal: "rls.tokenScopes must be true or false" };
|
|
323
|
+
const tokenScopes = tokenScopesPlan(tokenScopesSetting, policy);
|
|
324
|
+
let vias;
|
|
325
|
+
try {
|
|
326
|
+
vias = resolveVias(rls?.tables, scopes, input.dialect);
|
|
327
|
+
} catch (cause) {
|
|
328
|
+
return { refusal: describeError(cause) };
|
|
329
|
+
}
|
|
330
|
+
const tableless = tablelessResources(rls?.tables);
|
|
331
|
+
let ctx = {
|
|
332
|
+
dialect: input.dialect,
|
|
333
|
+
scopes,
|
|
334
|
+
tenantClaim: rls?.tenantClaim ?? "tenant_id",
|
|
335
|
+
...roleMemberScopesOf(rls?.migrate),
|
|
336
|
+
...rls?.roleValues === void 0 ? {} : { roleValues: rls.roleValues },
|
|
337
|
+
...rls?.tenants === "all" ? { tenants: "all" } : {},
|
|
338
|
+
gucPrefix: input.gucPrefix ?? rls?.gucPrefix ?? "app",
|
|
339
|
+
...rls?.actions === void 0 ? {} : { actions: rls.actions },
|
|
340
|
+
...tableless.size === 0 ? {} : { tableless },
|
|
341
|
+
inlineFunctions: input.inlineFunctions || rls?.inlineFunctions === true,
|
|
342
|
+
schema,
|
|
343
|
+
authorize,
|
|
344
|
+
roleClaim: rls?.roleClaim ?? "user_role",
|
|
345
|
+
tenantType: input.tenantType ?? rls?.tenantType ?? "uuid",
|
|
346
|
+
...rls?.teamType === void 0 ? {} : { teamType: rls.teamType },
|
|
347
|
+
...rls?.scopeTypes === void 0 ? {} : { scopeTypes: rls.scopeTypes },
|
|
348
|
+
...memberships === void 0 ? {} : { memberships },
|
|
349
|
+
...sources === void 0 || sources.length === 0 ? {} : { sources },
|
|
350
|
+
...memberSources === void 0 || memberSources.length === 0 ? {} : { memberSources },
|
|
351
|
+
...suspension === void 0 ? {} : { suspension },
|
|
352
|
+
...roles === void 0 ? {} : { roles },
|
|
353
|
+
...input.customRoles === true || customRolesOn(rls) ? { customRoles: {
|
|
354
|
+
...customRoleNames(policy, requires.keys),
|
|
355
|
+
...rls?.customRoleWrites?.roles === void 0 ? {} : { table: rls.customRoleWrites.roles },
|
|
356
|
+
...rls?.customRoleWrites?.audit === void 0 ? {} : { audit: rls.customRoleWrites.audit }
|
|
357
|
+
} } : {},
|
|
358
|
+
...input.capabilities === true || rls?.capabilities === true ? { capabilities: true } : {},
|
|
359
|
+
...apiKeys === void 0 ? {} : { apiKeys },
|
|
360
|
+
...tokenScopes === void 0 ? {} : { tokenScopes },
|
|
361
|
+
...rls?.anonymousSignIns === "deny" ? { anonymousSignIns: "deny" } : {},
|
|
362
|
+
...ownership === void 0 ? {} : { ownership },
|
|
363
|
+
...rls?.seeds === void 0 ? {} : { seeds: rls.seeds },
|
|
364
|
+
...rls?.ownershipTriggers === void 0 ? {} : { skipOwnershipTriggers: skippedOwnershipTriggers(rls.ownershipTriggers, scopes) },
|
|
365
|
+
...rls?.assignments === void 0 ? {} : { assignments: rls.assignments === true ? { tables: [] } : {
|
|
366
|
+
tables: rls.assignments.tables ?? [],
|
|
367
|
+
...rls.assignments.ownRole === void 0 ? {} : { ownRole: rls.assignments.ownRole }
|
|
368
|
+
} },
|
|
369
|
+
...options.fieldsMode === void 0 ? {} : { fields: options.fieldsMode },
|
|
370
|
+
...vias === void 0 ? {} : { vias },
|
|
371
|
+
...graph.size === 0 ? {} : { graph: {
|
|
372
|
+
closures: closureDepths(graph),
|
|
373
|
+
resources: policy.resources,
|
|
374
|
+
...rls?.tables === void 0 ? {} : { tables: tableNames(rls.tables) ?? {} }
|
|
375
|
+
} }
|
|
376
|
+
};
|
|
377
|
+
const from = typeof rls?.customRoles === "object" ? rls.customRoles.from : void 0;
|
|
378
|
+
if (from?.sync === "trigger" && ctx.customRoles !== void 0) try {
|
|
379
|
+
ctx = {
|
|
380
|
+
...ctx,
|
|
381
|
+
customRoles: {
|
|
382
|
+
...ctx.customRoles,
|
|
383
|
+
sync: customRoleSyncSql(ctx, from, rls?.migrate)
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
} catch (cause) {
|
|
387
|
+
return { refusal: describeError(cause) };
|
|
388
|
+
}
|
|
389
|
+
const [unsatisfied] = unsatisfiedKinds(ctx, policy);
|
|
390
|
+
if (unsatisfied !== void 0) return { refusal: `role ${unsatisfied.role} is for ${unsatisfied.for.join(", ")}, but its membership sources hold ${unsatisfied.sources.join(", ")}: no membership can hold it; set via on a source to one of those kinds` };
|
|
391
|
+
return {
|
|
392
|
+
ctx,
|
|
393
|
+
graph,
|
|
394
|
+
ownership,
|
|
395
|
+
schema,
|
|
396
|
+
authorize
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/cli/rls/generate-options.ts
|
|
401
|
+
function isRefusal(value) {
|
|
402
|
+
return "refusal" in value;
|
|
403
|
+
}
|
|
404
|
+
/** The checks that need no policy: `--fields` and `--revoke-columns`. */
|
|
405
|
+
function generateOptions(input) {
|
|
406
|
+
const rls = input.config.rls;
|
|
407
|
+
const fieldsMode = input.fields ?? rls?.fields;
|
|
408
|
+
if (fieldsMode !== void 0 && fieldsMode !== "views" && fieldsMode !== "mask") return { refusal: `rls generate --fields must be views or mask (got ${fieldsMode})` };
|
|
409
|
+
const revokeColumns = input.revokeColumns === true || rls?.revokeColumns === true;
|
|
410
|
+
if (revokeColumns && fieldsMode === void 0) return { refusal: "rls generate --revoke-columns needs --fields views or mask" };
|
|
411
|
+
return {
|
|
412
|
+
fieldsMode,
|
|
413
|
+
revokeColumns
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/** Whether `--split` names `part`, which then leaves the helpers or the warnings. */
|
|
417
|
+
function splitsPart(raw, part) {
|
|
418
|
+
const split = parseSplit(raw);
|
|
419
|
+
return typeof split !== "string" && split?.includes(part) === true;
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/cli/rls/rows.ts
|
|
423
|
+
/** `permitted_<resource>_rows(p_permission)`: the resource's row ids the subject may act on with one permission. */
|
|
424
|
+
function rowsHelper(resource) {
|
|
425
|
+
const name = graphSqlName(resource);
|
|
426
|
+
const helper = `permitted_${name ?? resource}_rows`;
|
|
427
|
+
if (name === void 0 || new TextEncoder().encode(`${helper}_for`).length > 63) throw configError(`PermDock CLI: rls.rowHelpers cannot name a helper for resource '${resource}'; use a SQL name of at most 45 bytes`);
|
|
428
|
+
return helper;
|
|
429
|
+
}
|
|
430
|
+
/** Every declared `<permission>@<level>` of an instance permission on a resource with levels. */
|
|
431
|
+
function levelKeys(policy) {
|
|
432
|
+
const out = [];
|
|
433
|
+
for (const leaf of listPermissions(policy.vocabulary.permissions)) {
|
|
434
|
+
if (leaf.kind !== "instance") continue;
|
|
435
|
+
for (const [level, condition] of policyLevels(policy, leaf.resource)) out.push({
|
|
436
|
+
key: `${leaf.key}@${level}`,
|
|
437
|
+
permission: leaf.key,
|
|
438
|
+
conditioned: !(condition.op === "and" && condition.conditions.length === 0),
|
|
439
|
+
rowHelper: `permitted_${graphSqlName(leaf.resource) ?? leaf.resource}_row`
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
444
|
+
/** The branches a row check of a permission reads: its allows and denies, inserts included, for signed-in callers. */
|
|
445
|
+
function rowBranchesOf(branches, resource) {
|
|
446
|
+
const out = /* @__PURE__ */ new Map();
|
|
447
|
+
for (const branch of branches) {
|
|
448
|
+
if (branch.resource !== resource || branch.coverage === true || !branch.roles.includes("authenticated")) continue;
|
|
449
|
+
out.set(branch.permissionKey, [...out.get(branch.permissionKey) ?? [], branch]);
|
|
450
|
+
}
|
|
451
|
+
return out;
|
|
452
|
+
}
|
|
453
|
+
/** What a branch asks of a row: the `with check` of an insert, which sees the new row, the `using` of every other command. */
|
|
454
|
+
function clauseOf(branch) {
|
|
455
|
+
const clauses = branchClauses(branch);
|
|
456
|
+
return (branch.command === "insert" ? clauses.check : clauses.using) ?? "true";
|
|
457
|
+
}
|
|
458
|
+
function usingOf(branches) {
|
|
459
|
+
const parts = branches.map(clauseOf);
|
|
460
|
+
return parts.length === 0 ? void 0 : orSql(parts);
|
|
461
|
+
}
|
|
462
|
+
function permissionCase(key, branches) {
|
|
463
|
+
const allow = usingOf(branches.filter((branch) => branch.effect === "allow"));
|
|
464
|
+
if (allow === void 0) return;
|
|
465
|
+
const deny = usingOf(branches.filter((branch) => branch.effect === "deny"));
|
|
466
|
+
const granted = `coalesce((${allow}), false)`;
|
|
467
|
+
return ` when ${quoteLiteral(key)} then ${deny === void 0 ? granted : `${granted} and (${deny}) is not true`}`;
|
|
468
|
+
}
|
|
469
|
+
function forUserSql(ctx, fn, helper) {
|
|
470
|
+
const user = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
471
|
+
const signature = `${user}, text, jsonb`;
|
|
472
|
+
const head = `create or replace function ${fn}(p_user ${user}, p_permission text, p_claims jsonb default '{}')
|
|
473
|
+
returns setof text
|
|
474
|
+
language plpgsql
|
|
475
|
+
volatile
|
|
476
|
+
security definer
|
|
477
|
+
set search_path = ''
|
|
478
|
+
as $$`;
|
|
479
|
+
const tail = `revoke execute on function ${fn}(${signature}) from public, anon, authenticated;`;
|
|
480
|
+
if (ctx.dialect === "supabase") return `${head}
|
|
481
|
+
declare
|
|
482
|
+
v_claims text := pg_catalog.current_setting('request.jwt.claims', true);
|
|
483
|
+
begin
|
|
484
|
+
perform pg_catalog.set_config(
|
|
485
|
+
'request.jwt.claims',
|
|
486
|
+
(coalesce(p_claims, '{}'::jsonb) || pg_catalog.jsonb_build_object('sub', p_user::text, 'role', 'authenticated'))::text,
|
|
487
|
+
true
|
|
488
|
+
);
|
|
489
|
+
return query select r.id from ${helper}(p_permission) r(id);
|
|
490
|
+
perform pg_catalog.set_config('request.jwt.claims', coalesce(v_claims, ''), true);
|
|
491
|
+
end
|
|
492
|
+
$$;
|
|
493
|
+
${tail}`;
|
|
494
|
+
const prefix = ctx.gucPrefix;
|
|
495
|
+
return `${head}
|
|
496
|
+
declare
|
|
497
|
+
v_saved jsonb := '{}'::jsonb;
|
|
498
|
+
v_key text;
|
|
499
|
+
v_value text;
|
|
500
|
+
begin
|
|
501
|
+
for v_key, v_value in
|
|
502
|
+
select 'user_id', p_user
|
|
503
|
+
union all
|
|
504
|
+
select c.key, c.value from pg_catalog.jsonb_each_text(coalesce(p_claims, '{}'::jsonb)) c where c.key <> 'user_id'
|
|
505
|
+
loop
|
|
506
|
+
v_saved := v_saved || pg_catalog.jsonb_build_object(v_key, coalesce(pg_catalog.current_setting(${quoteLiteral(`${prefix}.`)} || v_key, true), ''));
|
|
507
|
+
perform pg_catalog.set_config(${quoteLiteral(`${prefix}.`)} || v_key, v_value, true);
|
|
508
|
+
end loop;
|
|
509
|
+
return query select r.id from ${helper}(p_permission) r(id);
|
|
510
|
+
for v_key, v_value in select s.key, s.value from pg_catalog.jsonb_each_text(v_saved) s loop
|
|
511
|
+
perform pg_catalog.set_config(${quoteLiteral(`${prefix}.`)} || v_key, v_value, true);
|
|
512
|
+
end loop;
|
|
513
|
+
end
|
|
514
|
+
$$;
|
|
515
|
+
${tail}`;
|
|
516
|
+
}
|
|
517
|
+
function inheritTargets(policy) {
|
|
518
|
+
const out = /* @__PURE__ */ new Set();
|
|
519
|
+
for (const grant of policy.grants) for (const item of flattenGrantee(grant.to)) if (item.kind === "inherit") out.add(item.resource);
|
|
520
|
+
return [...out].toSorted();
|
|
521
|
+
}
|
|
522
|
+
function stubSql(ctx, resource) {
|
|
523
|
+
return `create or replace function ${qualified(ctx, rowsHelper(resource))}(p_permission text)
|
|
524
|
+
returns setof text
|
|
525
|
+
language sql
|
|
526
|
+
stable
|
|
527
|
+
security definer
|
|
528
|
+
set search_path = ''
|
|
529
|
+
as $$
|
|
530
|
+
select null::text where false
|
|
531
|
+
$$;`;
|
|
532
|
+
}
|
|
533
|
+
function rowSql(ctx, rows, table, where) {
|
|
534
|
+
const fn = qualified(ctx, rows.replace(/_rows$/u, "_row"));
|
|
535
|
+
return `create or replace function ${fn}(p_row ${table}, p_permission text)
|
|
536
|
+
returns boolean
|
|
537
|
+
language sql
|
|
538
|
+
stable
|
|
539
|
+
security definer
|
|
540
|
+
set search_path = ''
|
|
541
|
+
as $$
|
|
542
|
+
select exists (select 1 from (select (p_row).*) r where ${where})
|
|
543
|
+
$$;
|
|
544
|
+
revoke execute on function ${fn}(${table}, text) from public, anon;
|
|
545
|
+
grant execute on function ${fn}(${table}, text) to authenticated;`;
|
|
546
|
+
}
|
|
547
|
+
function resourceSql(ctx, node, branches, tables) {
|
|
548
|
+
const name = rowsHelper(node.name);
|
|
549
|
+
const fn = qualified(ctx, name);
|
|
550
|
+
const table = quoteQualified(tableFor(node.name, tables));
|
|
551
|
+
const cases = [...branches.keys()].toSorted().flatMap((key) => {
|
|
552
|
+
const sql = permissionCase(key, branches.get(key) ?? []);
|
|
553
|
+
return sql === void 0 ? [] : [sql];
|
|
554
|
+
});
|
|
555
|
+
const where = cases.length === 0 ? "false" : `case p_permission\n${cases.join("\n")}\n else false\n end`;
|
|
556
|
+
const rows = `-- ${node.name}: ids of the rows the caller may act on with p_permission, as the table's policies decide it
|
|
557
|
+
create or replace function ${fn}(p_permission text)
|
|
558
|
+
returns setof text
|
|
559
|
+
language sql
|
|
560
|
+
stable
|
|
561
|
+
security definer
|
|
562
|
+
set search_path = ''
|
|
563
|
+
as $$
|
|
564
|
+
select ${quoteIdent(node.id)}::text from ${table}
|
|
565
|
+
where ${where}
|
|
566
|
+
$$;
|
|
567
|
+
revoke execute on function ${fn}(text) from public, anon;
|
|
568
|
+
grant execute on function ${fn}(text) to authenticated;
|
|
569
|
+
${rowSql(ctx, name, table, where)}`;
|
|
570
|
+
if (ctx.dialect === "neon") return rows;
|
|
571
|
+
return `${rows}
|
|
572
|
+
-- the same rows for a user the caller names, with claims it supplies; no client role may execute it
|
|
573
|
+
${forUserSql(ctx, qualified(ctx, `${name}_for`), fn)}`;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* `permitted_<resource>_rows(p_permission)` for each resource `select`
|
|
577
|
+
* names (or every resource a grant reaches with `true`): the ids of its rows
|
|
578
|
+
* the caller may read, update or delete with that permission, from the same
|
|
579
|
+
* compiled allows and denies the table policies use, so closure walks, link
|
|
580
|
+
* hops, the restricted stop, `includes`, groups and `requires` all apply. A
|
|
581
|
+
* `security definer` helper, so hand-written policies and functions call it
|
|
582
|
+
* without restating the graph. Outside the `neon` dialect a
|
|
583
|
+
* `permitted_<resource>_rows_for(p_user, p_permission, p_claims)` form runs it
|
|
584
|
+
* as the named user, with the claims the caller supplies, for trusted SQL.
|
|
585
|
+
*/
|
|
586
|
+
function rowHelpersSql(ctx, policy, branches, select, tables, warnings) {
|
|
587
|
+
const names = select === true ? [...new Set(branches.flatMap((branch) => branch.resource === void 0 ? [] : [branch.resource]))].toSorted() : [...new Set(select)];
|
|
588
|
+
const called = inheritTargets(policy);
|
|
589
|
+
for (const name of called) if (!names.includes(name)) names.push(name);
|
|
590
|
+
const chunks = called.map((name) => stubSql(ctx, name));
|
|
591
|
+
for (const name of names) {
|
|
592
|
+
const node = policy.resources.get(name);
|
|
593
|
+
if (node === void 0) throw configError(`PermDock CLI: rls.rowHelpers names '${name}', which the policy does not declare`);
|
|
594
|
+
chunks.push(resourceSql(ctx, node, rowBranchesOf(branches, name), tables));
|
|
595
|
+
}
|
|
596
|
+
if (chunks.length > 0 && ctx.dialect === "neon") warnings.push("rls.rowHelpers: the neon dialect reads the subject from auth.session(), which SQL cannot set, so no permitted_<resource>_rows_for is written");
|
|
597
|
+
return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/cli/sql/supautils.ts
|
|
601
|
+
/** `supautils.reserved_roles` of supabase/postgres 17.11.0.002 (supautils 3.4.4). */
|
|
602
|
+
const RESERVED_ROLES = /* @__PURE__ */ new Set([
|
|
603
|
+
"supabase_admin",
|
|
604
|
+
"supabase_auth_admin",
|
|
605
|
+
"supabase_storage_admin",
|
|
606
|
+
"supabase_read_only_user",
|
|
607
|
+
"supabase_realtime_admin",
|
|
608
|
+
"supabase_replication_admin",
|
|
609
|
+
"supabase_etl_admin",
|
|
610
|
+
"supabase_privileged_role",
|
|
611
|
+
"dashboard_user",
|
|
612
|
+
"pgbouncer",
|
|
613
|
+
"service_role",
|
|
614
|
+
"authenticator",
|
|
615
|
+
"authenticated",
|
|
616
|
+
"anon"
|
|
617
|
+
]);
|
|
618
|
+
/** The `*` entries of `reserved_roles`: `postgres` may still `set` or `reset` their settings. */
|
|
619
|
+
const CONFIGURABLE_ROLES = /* @__PURE__ */ new Set([
|
|
620
|
+
"service_role",
|
|
621
|
+
"authenticator",
|
|
622
|
+
"authenticated",
|
|
623
|
+
"anon"
|
|
624
|
+
]);
|
|
625
|
+
/** `supautils.reserved_memberships` of the same image. */
|
|
626
|
+
const RESERVED_MEMBERSHIPS = /* @__PURE__ */ new Set([
|
|
627
|
+
"pg_read_server_files",
|
|
628
|
+
"pg_write_server_files",
|
|
629
|
+
"pg_execute_server_program",
|
|
630
|
+
"supabase_admin",
|
|
631
|
+
"supabase_auth_admin",
|
|
632
|
+
"supabase_storage_admin",
|
|
633
|
+
"supabase_read_only_user",
|
|
634
|
+
"supabase_realtime_admin",
|
|
635
|
+
"supabase_replication_admin",
|
|
636
|
+
"supabase_etl_admin",
|
|
637
|
+
"dashboard_user",
|
|
638
|
+
"pgbouncer",
|
|
639
|
+
"authenticator"
|
|
640
|
+
]);
|
|
641
|
+
const ROLE = String.raw`(?:"[^"]+"|\w+)`;
|
|
642
|
+
const ROLES = String.raw`${ROLE}(?:\s*,\s*${ROLE})*`;
|
|
643
|
+
const ALTER_ROLE = new RegExp(String.raw`^alter\s+(role|user|group)\s+(${ROLE})\s+([\s\S]*)$`, "iu");
|
|
644
|
+
const DROP_ROLE = new RegExp(String.raw`^drop\s+(?:role|user|group)\s+(?:if\s+exists\s+)?(${ROLES})\s*$`, "iu");
|
|
645
|
+
const GRANT_ROLE = /^grant\s+([\s\S]+?)\s+to\s/iu;
|
|
646
|
+
const CREATE_IN_ROLE = new RegExp(String.raw`^create\s+(?:role|user|group)\s+${ROLE}\b[\s\S]*?\bin\s+(?:role|group)\s+(${ROLES})`, "iu");
|
|
647
|
+
const CONFIG_CHANGE = /^(?:in\s+database\s+\S+\s+)?(?:set|reset)\s/iu;
|
|
648
|
+
function roleName(name) {
|
|
649
|
+
return name.replaceAll("\"", "").toLowerCase();
|
|
650
|
+
}
|
|
651
|
+
function roleList(list) {
|
|
652
|
+
return list.split(",").map((name) => roleName(name.trim()));
|
|
653
|
+
}
|
|
654
|
+
function rejection(text) {
|
|
655
|
+
const alter = ALTER_ROLE.exec(text);
|
|
656
|
+
if (alter !== null) {
|
|
657
|
+
const role = roleName(group(alter, 2));
|
|
658
|
+
const rest = group(alter, 3);
|
|
659
|
+
if (group(alter, 1).toLowerCase() === "group" && /^add\s/iu.test(rest)) return RESERVED_MEMBERSHIPS.has(role) ? `grants membership in the reserved role ${role}` : void 0;
|
|
660
|
+
if (!RESERVED_ROLES.has(role) || CONFIGURABLE_ROLES.has(role) && CONFIG_CHANGE.test(rest)) return;
|
|
661
|
+
return `changes the reserved role ${role}`;
|
|
662
|
+
}
|
|
663
|
+
const drop = DROP_ROLE.exec(text);
|
|
664
|
+
if (drop !== null) {
|
|
665
|
+
const role = roleList(group(drop, 1)).find((name) => RESERVED_ROLES.has(name));
|
|
666
|
+
return role === void 0 ? void 0 : `drops the reserved role ${role}`;
|
|
667
|
+
}
|
|
668
|
+
const grant = GRANT_ROLE.exec(text);
|
|
669
|
+
const granted = CREATE_IN_ROLE.exec(text)?.[1] ?? (grant === null || /\bon\b/iu.test(group(grant, 1)) ? void 0 : group(grant, 1));
|
|
670
|
+
if (granted === void 0) return;
|
|
671
|
+
const role = roleList(granted).find((name) => RESERVED_MEMBERSHIPS.has(name));
|
|
672
|
+
return role === void 0 ? void 0 : `grants membership in the reserved role ${role}`;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* The statements of `sql` that supautils rejects for a non-superuser:
|
|
676
|
+
* changing or dropping a reserved role, other than `set` or `reset` on the
|
|
677
|
+
* four API roles, and granting a reserved membership.
|
|
678
|
+
*/
|
|
679
|
+
function supautilsRejections(sql) {
|
|
680
|
+
return sqlStatements(sql).flatMap(({ line, text }) => {
|
|
681
|
+
const reason = rejection(text);
|
|
682
|
+
return reason === void 0 ? [] : [{
|
|
683
|
+
line,
|
|
684
|
+
reason
|
|
685
|
+
}];
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
//#endregion
|
|
689
|
+
export { generateOptions as a, buildContext as c, rowHelpersSql as i, DROPPED_LIST as l, inheritTargets as n, isRefusal as o, levelKeys as r, splitsPart as s, supautilsRejections as t, rebuildPlan as u };
|