permdock 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/{2020-C9VN-x8c.js → 2020-CJbO8Bvz.js} +1 -3
- package/dist/a2a/index.d.ts +7 -7
- package/dist/a2a/index.js +9 -9
- package/dist/action-result-CL-QCITA.d.ts +19 -0
- package/dist/{adapter-context-CRhqyzCk.js → adapter-context-CIQpQxbE.js} +8 -2
- package/dist/{adoption-scan-DCYe60Pi.js → adoption-scan-BLs67P1l.js} +5 -5
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1 -1
- package/dist/ai-sdk/index.d.ts +31 -10
- package/dist/ai-sdk/index.js +22 -12
- package/dist/{app-data-MuyzVqX8.js → app-data-UNNm07jV.js} +10 -7
- package/dist/{approval-header-CJACtHkv.js → approval-header-8QV6MU4H.js} +6 -3
- package/dist/{approval-prompt-Dg5LeAcT.js → approval-prompt-B8hQY69x.js} +5 -6
- package/dist/{approval-prompt-DvSSPQJX.d.ts → approval-prompt-PYagyQUj.d.ts} +13 -11
- package/dist/approvals/index.d.ts +5 -5
- package/dist/approvals/index.js +7 -7
- package/dist/{approvals-p1PGoVjb.js → approvals-DnIBTuXE.js} +128 -78
- package/dist/{approvers-CqPOLD-o.js → approvers-B4r0N_Uz.js} +8 -2
- package/dist/{arazzo-41WuzHou.js → arazzo-3RhGfkAK.js} +2 -2
- package/dist/{arazzo-CaUVBn7D.js → arazzo-B5-ulWif.js} +6 -6
- package/dist/{args-IIolxxKA.js → args-0FrVs5EH.js} +125 -8
- package/dist/astro/index.d.ts +30 -12
- package/dist/astro/index.js +2 -2
- package/dist/{audience-DabClFLQ.js → audience-BAkBDMqn.js} +8 -8
- package/dist/{auth-DRLEizbL.js → auth--yPyZNgr.js} +21 -10
- package/dist/authzen/index.d.ts +171 -25
- package/dist/authzen/index.js +321 -149
- package/dist/{automation-BNM3TtDo.d.ts → automation-DqEoWHBc.d.ts} +42 -32
- package/dist/{automation-DVjOckjV.js → automation-x3KEUusw.js} +22 -21
- package/dist/base-url-BQbddZO5.js +9 -0
- package/dist/better-auth/index.d.ts +2 -2
- package/dist/better-auth/index.js +13 -7
- package/dist/better-supabase/index.d.ts +392 -220
- package/dist/better-supabase/index.js +293 -152
- package/dist/{bind-DOyrv6zi.d.ts → bind-Cp2hBHzP.d.ts} +27 -12
- package/dist/{bind-CAvgUuKs.js → bind-D_sZSVA3.js} +22 -10
- package/dist/{body-CH5of8Ne.js → body-CJQwYVa8.js} +3 -3
- package/dist/{body-text-QcXok_SI.js → body-text-DQhcmqdY.js} +8 -4
- package/dist/{boundary-BM8a_P6E.js → boundary-BV3O1eud.js} +8 -2
- package/dist/{boundary-BmHxVH1J.d.ts → boundary-C9ck-3uK.d.ts} +13 -4
- package/dist/{budget-NbEtbJC_.js → budget-BELja5A9.js} +6 -3
- package/dist/{budget-JqcRqyhK.d.ts → budget-hYsPz_yE.d.ts} +6 -3
- package/dist/{build-instance-Bu_eDqYw.js → build-instance-CNWaYJyr.js} +3 -3
- package/dist/{canonical-json-BC8_nMTw.js → canonical-json-DwilZ1iB.js} +6 -3
- package/dist/{capability-N1K9IpqS.js → capability-BqX94r07.js} +31 -25
- package/dist/{capability-Drg-1YhL.d.ts → capability-ChuiONQU.d.ts} +41 -29
- package/dist/{casbin-Dyu2gnUD.js → casbin-H0rKxBx2.js} +16 -8
- package/dist/{casbin-Cr7RLtA3.js → casbin-cbSbhT_k.js} +2 -2
- package/dist/{casl-64gpT-8x.js → casl-C4OINZHI.js} +2 -2
- package/dist/{casl-DjEDhTCP.js → casl-CHHoCldP.js} +19 -10
- package/dist/catalog/index.d.ts +104 -23
- package/dist/catalog/index.js +117 -8
- package/dist/{catalog-HD2gf5mY.js → catalog-BFpz2EmT.js} +57 -26
- package/dist/{catalog-DZVbrF-z.js → catalog-DCspHDc8.js} +3 -3
- package/dist/{catalog-doc-Cdn7-0pQ.js → catalog-doc-DjMKzJp9.js} +53 -16
- package/dist/{catalog-fingerprint-CCP0-yQS.js → catalog-fingerprint-e6aCWosn.js} +19 -15
- package/dist/catalog-mRQQQhNK.js +1 -0
- package/dist/catalog-operations-Dadrl7Z-.js +60 -0
- package/dist/{create-CQWIpUG-.js → challenge-DImNcR7B.js} +157 -39
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/index.js +13 -10
- package/dist/chat-sdk/index.d.ts +44 -26
- package/dist/chat-sdk/index.js +17 -17
- package/dist/checks-CGkP3Z7A.js +40 -0
- package/dist/{claims-map-7oQ3ue4X.js → claims-map-CKkoAAYF.js} +5 -2
- package/dist/{claims-map-Ca-QX8C6.d.ts → claims-map-D1Pb5oRq.d.ts} +8 -5
- package/dist/claude-agent/index.d.ts +6 -6
- package/dist/claude-agent/index.js +6 -3
- package/dist/clerk/index.d.ts +11 -7
- package/dist/clerk/index.js +6 -6
- package/dist/cli/bin.js +4 -4
- package/dist/cli/index.d.ts +82 -7
- package/dist/cli/index.js +6 -6
- package/dist/{clients-C7zLFuWT.d.ts → clients-C_NOET3z.d.ts} +5 -5
- package/dist/{clients-DjU5ABMy.js → clients-DYr3U86j.js} +3 -3
- package/dist/cloud/index.d.ts +186 -38
- package/dist/cloud/index.js +293 -31
- package/dist/{cloud-ytQ3uviA.js → cloud-BFlGMaak.js} +5 -6
- package/dist/{codemod-5mpSpnUY.js → codemod-DLQ9Zpo4.js} +3 -4
- package/dist/{codemod-ua2hHy7w.js → codemod-DWSN2erG.js} +523 -201
- package/dist/collect-B5BL3klN.js +1 -0
- package/dist/{collect-mLTY4nwb.js → collect-B7ZFAJU3.js} +36 -64
- package/dist/{collect-Ci6akVwH.js → collect-Bzv3le0A.js} +6 -6
- package/dist/{compare-pfyVnBjE.js → compare-DTfyUunF.js} +2 -2
- package/dist/compile/index.d.ts +2 -2
- package/dist/compile/index.js +1 -1
- package/dist/{compile-1bsAezL0.d.ts → compile-BFzysW3o.d.ts} +36 -12
- package/dist/{compile-CMqMx_IN.js → compile-EzEMB-7v.js} +77 -33
- package/dist/{completion-BTFrHZhX.js → completion-saoYiRFO.js} +17 -8
- package/dist/config-BwPBfN0t.js +108 -0
- package/dist/{config-Bj2Ixq3d.js → config-DBeXXmBF.js} +2 -2
- package/dist/{config-report-DSpsLpCE.js → config-report-BipSWA6V.js} +9 -6
- package/dist/config-shape-ykV0Vm2n.js +134 -0
- package/dist/{connection-DjhIJSs2.js → connection-B72dg5Q2.js} +23 -22
- package/dist/connector-schema-CYLQNMYq.js +434 -0
- package/dist/{context-B_u3_bDJ.js → context-BnIzxTNE.js} +4 -3
- package/dist/convex/index.d.ts +13 -7
- package/dist/convex/index.js +9 -6
- package/dist/core-H-mbhpZc.js +341 -0
- package/dist/core-hWIARPDz.d.ts +206 -0
- package/dist/{create-CBMmFie-.d.ts → create-BSNrVuxh.d.ts} +87 -34
- package/dist/{create-B30OMUOJ.d.ts → create-BVq1lNQE.d.ts} +49 -28
- package/dist/{create-D_NmPOdH.d.ts → create-CM1FciYG.d.ts} +37 -29
- package/dist/{create-CGm28gyI.js → create-Cq4Uox0f.js} +13 -9
- package/dist/{create-DCF2SvoT.js → create-_noSrY1T.js} +15 -21
- package/dist/{credential-BcoiejpY.js → credential-CyyyvCsL.js} +42 -39
- package/dist/{credentials-DPyHmYz5.js → credentials-CdDxp7jN.js} +6 -6
- package/dist/{deciding-columns-Sq12mEA6.js → deciding-columns-NB4wcyBf.js} +13 -2
- package/dist/{delegation-j6-rc37o.js → delegation-CDB6_keF.js} +31 -22
- package/dist/delivery/index.d.ts +5 -5
- package/dist/delivery/index.js +6 -5
- package/dist/{delivery-DCAgNyCn.js → delivery-BupfvJIV.js} +81 -20
- package/dist/{delivery-event-xX4obB8F.js → delivery-event-JioK5YbS.js} +6 -6
- package/dist/denied-text-CHNlVInN.d.ts +22 -0
- package/dist/{describe-BybMRY7A.js → describe-Bb4hGK5d.js} +6 -5
- package/dist/{describe-B5qgDvEW.d.ts → describe-ByS9LgHK.d.ts} +5 -5
- package/dist/{device-BARQ8eYG.js → device-BwbJNmb2.js} +11 -8
- package/dist/{diff-CmuSD3vc.js → diff-CN8IYhC_.js} +2 -2
- package/dist/{diff-3jbx6Rm4.js → diff-iZWV65nC.js} +23 -14
- package/dist/{digest-B3kc_PgQ.d.ts → digest-8Yx2CchM.d.ts} +3 -3
- package/dist/{doctor-BdZw53-k.js → doctor-CHD1M9R5.js} +485 -141
- package/dist/{doctor-3ZqNHX_G.js → doctor-D3wo-yuz.js} +4 -4
- package/dist/drizzle/index.d.ts +33 -19
- package/dist/drizzle/index.js +17 -9
- package/dist/elysia/index.d.ts +17 -11
- package/dist/elysia/index.js +5 -5
- package/dist/{errors-BKy0EfhN.js → errors-B6R_lg2a.js} +13 -4
- package/dist/{errors-BWXBMaKK.d.ts → errors-CHwoRRpB.d.ts} +4 -1
- package/dist/{errors-CWTY6uih.js → errors-Vpu6rKHl.js} +14 -10
- package/dist/{errors-DH5QEGXh.d.ts → errors-zi_4T-rx.d.ts} +22 -6
- package/dist/{evaluate-CNClBvcj.js → evaluate-Dd8Ta0Ie.js} +185 -80
- package/dist/{evaluations-beAooV0n.js → evaluations-IBceAeKT.js} +41 -20
- package/dist/{evaluations-C_1vdSZc.d.ts → evaluations-z8dQFDaN.d.ts} +12 -6
- package/dist/eve/index.d.ts +9 -6
- package/dist/eve/index.js +4 -4
- package/dist/{event-time-CsegxXxB.js → event-time-BGy48400.js} +8 -4
- package/dist/{expiry-D4_YKRKD.js → expiry-6XByK0dm.js} +8 -5
- package/dist/express/index.d.ts +9 -6
- package/dist/express/index.js +9 -6
- package/dist/fastify/index.d.ts +16 -10
- package/dist/fastify/index.js +8 -5
- package/dist/{fetch-adapter-Dw0MTS3D.d.ts → fetch-adapter-C1DCvpqV.d.ts} +9 -6
- package/dist/{fetch-adapter-DD_gfmrC.js → fetch-adapter-JL43jD3E.js} +14 -11
- package/dist/fields-D0vUI6dq.js +498 -0
- package/dist/{files-C5ZI27eV.js → files-B3AA05f1.js} +21 -5
- package/dist/{files-D_JIzUrP.js → files-BSjrtIHw.js} +11 -4
- package/dist/{fixtures-B7XrLeY7.js → fixtures-A1_sp_tR.js} +15 -6
- package/dist/{flags-C3DktBDY.js → flags-DXjoTVbS.js} +2 -2
- package/dist/{freeze-CrU8nQeX.js → freeze-jjan0lCv.js} +4 -1
- package/dist/{from-snapshot-CTCMsFZk.js → from-snapshot-DCakWRsr.js} +26 -16
- package/dist/gen-B5kvbAoZ.js +58 -0
- package/dist/gen-DWCYcMQM.js +827 -0
- package/dist/generate-CRVV5V7l.js +165 -0
- package/dist/generate-CezbCaTI.js +275 -0
- package/dist/{grantee-hxrUsbpM.js → grantee-Dke-9rd1.js} +4 -4
- package/dist/{grantee-conditions-CcXf8hMM.js → grantee-conditions-BrYPe-w0.js} +12 -12
- package/dist/{grantee-match-DbENBHLB.js → grantee-match-7RZZvoT_.js} +12 -6
- package/dist/{graph-sql-B-dsAT6j.d.ts → graph-sql-BwFC4Qjs.d.ts} +3 -3
- package/dist/h3/index.d.ts +10 -7
- package/dist/h3/index.js +3 -3
- package/dist/headers-B9jQxjtW.d.ts +10 -0
- package/dist/{helpers-CBYVMjwh.js → helpers-DJicaG6f.js} +40 -28
- package/dist/hono/index.d.ts +16 -10
- package/dist/hono/index.js +5 -5
- package/dist/{hosted-BPk51-kX.d.ts → hosted-Cmtxogd0.d.ts} +44 -20
- package/dist/{hosted-B46-5LYE.js → hosted-D0IzESu_.js} +68 -25
- package/dist/{http-ujKm0JXE.js → http-DH94eh6f.js} +4 -4
- package/dist/{http-BBoKefeS.js → http-DKLXrFGk.js} +13 -7
- package/dist/{http-DXJ37RDA.js → http-DOL33cO0.js} +11 -8
- package/dist/{import-wyqXUyjm.js → import-CST57XLp.js} +64 -51
- package/dist/{import-CNV7I2Dw.js → import-_YqhhwvX.js} +6 -6
- package/dist/{index-B8YjkEZ4.d.ts → index-B8s204GL.d.ts} +64 -52
- package/dist/{index-Ch8LpNDp.d.ts → index-BT5-1VsX.d.ts} +64 -25
- package/dist/{index-DuWxc5dt.d.ts → index-BW_0MZOS.d.ts} +388 -149
- package/dist/index-BhxEUY9J.d.ts +233 -0
- package/dist/index-Bv5iXPqU.d.ts +210 -0
- package/dist/{index-C-6Letxg.d.ts → index-_9UiQIkt.d.ts} +164 -77
- package/dist/index.d.ts +22 -21
- package/dist/index.js +35 -33
- package/dist/{init-GG6Jh8HD.js → init-Dz_PsHdv.js} +5 -5
- package/dist/{init-DeMYsnYJ.js → init-cE3A8aXq.js} +10 -10
- package/dist/{instance-options-BsVpyGIk.d.ts → instance-options-B0KXqTrJ.d.ts} +4 -4
- package/dist/{instance-snapshot-BOEhcqIF.js → instance-snapshot--JWiTrvO.js} +81 -56
- package/dist/{instant-BluqDvyS.js → instant-DK3Iz83e.js} +2 -2
- package/dist/{internals-Dk8A8fX3.js → internals-B3IgHrvo.js} +6 -3
- package/dist/{json-schema-B-vAwlde.js → json-schema-BPGa-RjQ.js} +9 -3
- package/dist/{json-schema-CN_iLFH2.d.ts → json-schema-BfXgnRNT.d.ts} +4 -4
- package/dist/jwt/index.d.ts +125 -17
- package/dist/jwt/index.js +157 -16
- package/dist/{kernel-D0NVb1cK.js → kernel-C-fYydV2.js} +151 -16
- package/dist/kysely/index.d.ts +49 -33
- package/dist/kysely/index.js +9 -7
- package/dist/{live-options-BaAFCIiu.js → live-options-BBIT68le.js} +2 -2
- package/dist/{load-DFS_ry0W.js → load-DjSU5KXO.js} +6 -6
- package/dist/load-Dm0pSYH3.js +1 -0
- package/dist/{login-Dfe5RlYk.js → login-D-8tMqzo.js} +4 -4
- package/dist/{login-imhuwvn3.js → login-DplQ0s84.js} +18 -15
- package/dist/{manifest-dvL2XEe1.js → manifest-Cgiq5oqc.js} +67 -37
- package/dist/{manifest-mx5ghSFM.d.ts → manifest-CzzWtnAb.d.ts} +144 -28
- package/dist/{manifest-schema-C0kK_8V0.js → manifest-schema-2r37XRhD.js} +80 -9
- package/dist/{manifest-C8IzW3Do.js → manifest-wg8WkkpD.js} +11 -7
- package/dist/{map-claims-D3oRmTk0.js → map-claims-BNWCJtmP.js} +27 -18
- package/dist/{map-error-Dr70oA_3.js → map-error-pasIGqMT.js} +7 -7
- package/dist/{markers-C6joDI3o.js → markers-BeHZ4x9Q.js} +20 -5
- package/dist/mcp/index.d.ts +154 -47
- package/dist/mcp/index.js +200 -41
- package/dist/{migrate-CIq4-E9F.js → migrate-B7oP_sQU.js} +102 -31
- package/dist/module-values-BJTBdo6y.js +107 -0
- package/dist/nest/index.d.ts +23 -13
- package/dist/nest/index.js +18 -11
- package/dist/next/client.d.ts +5 -2
- package/dist/next/client.js +1 -1
- package/dist/next/index.d.ts +72 -26
- package/dist/next/index.js +110 -6
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +4 -3
- package/dist/node/index.d.ts +5 -5
- package/dist/node/index.js +3 -3
- package/dist/{ocsf-BFp-PZZA.js → ocsf-TcjRBgIs.js} +15 -5
- package/dist/openai/index.d.ts +13 -7
- package/dist/openai/index.js +3 -3
- package/dist/openapi/index.d.ts +61 -30
- package/dist/openapi/index.js +2 -2
- package/dist/{openapi-B7HZ0f-r.js → openapi-BOsx0xn9.js} +34 -24
- package/dist/{openapi-C4dDTVEd.js → openapi-CBZUhA3l.js} +4 -5
- package/dist/{openapi-DD1SV-5-.js → openapi-VeHHU2l9.js} +23 -14
- package/dist/{operation-findings-DgATDpvh.js → operation-findings-CXtFzlKJ.js} +3 -3
- package/dist/{operations-X0VM7F3m.d.ts → operations-Ci_rT6Uo.d.ts} +10 -7
- package/dist/orpc/index.d.ts +68 -14
- package/dist/orpc/index.js +62 -13
- package/dist/otel/index.d.ts +7 -4
- package/dist/otel/index.js +8 -5
- package/dist/output-files-BHOIlrfD.js +1 -0
- package/dist/{output-files-DhRBBE4n.js → output-files-BIQ_9pIs.js} +29 -17
- package/dist/{output-1im__-08.js → output-tkju6U7R.js} +15 -7
- package/dist/ownership-B4L59T9u.js +1054 -0
- package/dist/parse-1_ZheTW5.js +151 -0
- package/dist/parse-local-manifest-BQhTlLQS.d.ts +10 -0
- package/dist/parse-local-manifest-DEiCF4ah.js +69 -0
- package/dist/{parse-request-D_tUXG6M.js → parse-request-CwCZpyjV.js} +7 -8
- package/dist/{paths-DXSHAMIa.js → paths-CUvURzHp.js} +4 -1
- package/dist/pdp/index.d.ts +9 -6
- package/dist/pdp/index.js +23 -12
- package/dist/{peer-CRvC7Hg1.js → peer-C9LvBCcb.js} +1 -1
- package/dist/peer-Q0fuZ6hx.js +982 -0
- package/dist/{permdock-SV8ZINYT.d.ts → permdock-B6Sf4T_S.d.ts} +270 -117
- package/dist/{permdock-BrAscV28.js → permdock-L-_I_3zP.js} +226 -74
- package/dist/{permission-registry-Br4jSNVi.js → permission-registry-B8tYxPIt.js} +24 -11
- package/dist/{vocabulary-CN8H_9UF.d.ts → permission-types-CAY-GU7e.d.ts} +130 -104
- package/dist/{permissions-u_KHg-sN.d.ts → permissions-Bx6UI8n1.d.ts} +6 -2
- package/dist/{permissions-B-wCP5ZC.js → permissions-CVXmRILq.js} +3 -3
- package/dist/{permissions-BPxlwVhN.js → permissions-mDcTWoBs.js} +8 -5
- package/dist/pg-DQTqRrou.js +60 -0
- package/dist/{pins-8ILTftC9.js → pins-nqarl3oE.js} +4 -1
- package/dist/pkce-CnmmYqUZ.js +21 -0
- package/dist/{policy-77FSAq_V.d.ts → policy-Ba4bDhaj.d.ts} +5 -5
- package/dist/{policy-DbM5YYtV.js → policy-CLQ8__GF.js} +49 -20
- package/dist/{policy-from-catalog-C2gMa2Dj.js → policy-from-catalog-CJgOm6aq.js} +28 -25
- package/dist/{policy-module-CTlwMvLI.js → policy-module-xym9fnSq.js} +12 -6
- package/dist/{policy-types-n0uRTsW8.d.ts → policy-types-CCiDRZBH.d.ts} +802 -334
- package/dist/postgres-store-DcyrPS-r.js +1028 -0
- package/dist/{postgrest-DAjUkF9w.js → postgrest-D4nuPXIQ.js} +88 -36
- package/dist/{postgrest-CEgRbefE.d.ts → postgrest-DicQyqm-.d.ts} +60 -30
- package/dist/powersync-DHNQX06W.js +1117 -0
- package/dist/{powersync-DISt6SUe.js → powersync-DXqGuq9A.js} +3 -3
- package/dist/powersync-Djf-zCpB.js +1 -0
- package/dist/preset/index.d.ts +38 -28
- package/dist/preset/index.js +1 -1
- package/dist/{preset-DZdU0tne.js → preset-BjLzeFpp.js} +58 -34
- package/dist/{principal-source-pHneukrx.js → principal-source-C6N60GtM.js} +10 -10
- package/dist/prisma/index.d.ts +69 -41
- package/dist/prisma/index.js +21 -17
- package/dist/{problem-CTviyH5L.js → problem-BVLHFK33.js} +41 -21
- package/dist/{problem-details-cn5X30gZ.js → problem-details-CLDgX1KV.js} +13 -4
- package/dist/{problem-types-CJ2325SZ.js → problem-types-DSsRfONQ.js} +4 -1
- package/dist/prompt-BYc6drYW.js +938 -0
- package/dist/protected-action-BE7N_Luo.d.ts +58 -0
- package/dist/protected-action-DUYqb-Ma.js +80 -0
- package/dist/{protected-BlF0F6wL.js → protected-action-QEA8arRB.js} +48 -14
- package/dist/{provider-B1al-AZz.js → provider-C55OGXJ7.js} +5 -4
- package/dist/{provider-DILL9oPK.d.ts → provider-DXfq5c2d.d.ts} +1 -1
- package/dist/{provider-store-DM6DNcta.js → provider-store-CaTv00tr.js} +13 -9
- package/dist/pull-B65ndFNZ.js +1 -0
- package/dist/{pull-jEwbf80J.js → pull-CwpGaQSi.js} +12 -11
- package/dist/{push-BD4f15co.js → push-DgssgzkK.js} +62 -16
- package/dist/{pushed-policy-DCW91KQU.d.ts → pushed-policy-D1Eoi6NM.d.ts} +12 -6
- package/dist/{pushed-policy-CqtxkV4G.js → pushed-policy-ITwYKvvH.js} +10 -7
- package/dist/react/index.d.ts +9 -8
- package/dist/react/index.js +7 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -1
- package/dist/react-native/index.d.ts +104 -51
- package/dist/react-native/index.js +394 -67
- package/dist/{refs-K6eWUJ0H.js → refs-C8BKxc_e.js} +17 -3
- package/dist/{relation-walk-3OyDBF0S.js → relation-walk-DK_gkgL7.js} +12 -9
- package/dist/{relations-BnCWnJGI.js → relations-BxvRvDHK.js} +3 -3
- package/dist/{replay-Du1tJjG7.js → replay-BU09ad4L.js} +1 -1
- package/dist/{replay-Bkm-Ne4l.d.ts → replay-DNftIb_R.d.ts} +1 -1
- package/dist/{request-context-C81Pip9n.js → request-context-C25ilqiJ.js} +8 -8
- package/dist/{request-schema-CMreVuFM.js → request-schema-DhX7UbfQ.js} +4 -1
- package/dist/resend/index.d.ts +34 -28
- package/dist/resend/index.js +29 -29
- package/dist/resolve-names-CF7xXLSG.js +201 -0
- package/dist/{resource-relations-D_jw0BFx.js → resource-relations-8-pCScBO.js} +18 -9
- package/dist/{retire-CALjIQ7e.js → retire-B2xffQgY.js} +15 -6
- package/dist/{revocations-R07lFBnX.d.ts → revocations-BaUXHM1N.d.ts} +6 -3
- package/dist/{rls-DTvYd-lI.js → rls-CNEH2_BG.js} +743 -624
- package/dist/{rls-BAqP4q66.js → rls-Ca8Jz9b5.js} +41 -10
- package/dist/{role-builder-uWQZ4v84.js → role-builder-BK2-T6oD.js} +41 -20
- package/dist/rolldown-runtime-C3I60J0A.js +4 -0
- package/dist/{row-conditions-h2HythXQ.js → row-conditions-BAGHOmVq.js} +4 -3
- package/dist/{rpc-VSl5ylaQ.js → rpc-fNqAIqQk.js} +8 -5
- package/dist/{run-CUKuz0A8.js → run-Bz26OTfO.js} +26 -23
- package/dist/run-D-JE8QWb.js +1 -0
- package/dist/{run-ticket-DocwaTKn.d.ts → run-ticket-DbWI9f99.d.ts} +33 -13
- package/dist/{run-ticket-sxFI6LAk.js → run-ticket-Dv7aF7Dk.js} +29 -8
- package/dist/{runtime-D3VTAiCA.js → runtime-BGnrOkVH.js} +5 -2
- package/dist/{saas-BCXRuXca.js → saas-iWnUdwCt.js} +42 -29
- package/dist/{scan-qvOe0hS3.js → scan-DCj1KQfn.js} +1 -1
- package/dist/{schema-B4v0Qrac.js → schema-BOJjqlWL.js} +3 -2
- package/dist/{schema-yLuBWWsh.js → schema-Dst9I9Gs.js} +9 -5
- package/dist/scim/index.d.ts +3 -2
- package/dist/scim/index.js +3 -1
- package/dist/scim-D8YupzX1.js +44 -0
- package/dist/{scim-ClHtpyV3.js → scim-jJBJ7g1Z.js} +35 -551
- package/dist/{scopes-CUPSjo9q.js → scopes-0Up_UA2t.js} +42 -24
- package/dist/server/index.d.ts +12 -12
- package/dist/server/index.js +13 -13
- package/dist/{server-C3WXAHZj.js → server-3cVlTLde.js} +31 -31
- package/dist/{server-types-CKQiCnON.d.ts → server-types-CIlpSA2G.d.ts} +81 -46
- package/dist/{sha256-D9ihqmvd.js → sha256-C-4WXKIu.js} +4 -3
- package/dist/{shared-BAK8t4Uo.js → shared-Afctg7B0.js} +100 -61
- package/dist/{shared-DvvFzYXe.js → shared-l0oGGetg.js} +9 -6
- package/dist/{signals-D3NcTKUS.js → signals-B1vHR3fN.js} +2 -2
- package/dist/{sink-BEZSlMS_.js → sink-D9_vh372.js} +32 -21
- package/dist/{sink-4-eQIRWf.d.ts → sink-Dvi7YZG_.d.ts} +44 -20
- package/dist/{skills-B6lVdD_W.js → skills-Bjj7BNEQ.js} +16 -12
- package/dist/{skills-CTYwkl44.js → skills-OuoMR49L.js} +3 -3
- package/dist/{snapshot-B7DkYg6g.js → snapshot-CXlzi69u.js} +21 -18
- package/dist/{snapshot-cache-PXI-vGpY.d.ts → snapshot-cache-BAXZ0KOp.d.ts} +21 -12
- package/dist/{snapshot-cache-DZHQ0KF6.js → snapshot-cache-Bx_4xC_6.js} +14 -11
- package/dist/solid/index.d.ts +55 -15
- package/dist/solid/index.js +46 -12
- package/dist/solid-start/index.d.ts +30 -12
- package/dist/solid-start/index.js +3 -3
- package/dist/sources-B1Os_YUu.d.ts +312 -0
- package/dist/{sources-vs5dGOsS.js → sources-CMAM-2A3.js} +156 -51
- package/dist/sources-qoIJQrJv.js +11 -0
- package/dist/{sql-B5T1IL83.js → sql-0WjxIp9g.js} +32 -7
- package/dist/sql-B80DFdpG.d.ts +10 -0
- package/dist/{src-Cu2m75uo.js → src-DZ0l9UAg.js} +305 -123
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +665 -224
- package/dist/{standard-webhooks-rbD7LS6h.d.ts → standard-webhooks-C-qX4mJW.d.ts} +7 -7
- package/dist/{standard-webhooks-DgFFD-Ga.js → standard-webhooks-D67a3nzk.js} +5 -5
- package/dist/{store-BDutJkDL.js → store-RNFvaJJv.js} +43 -29
- package/dist/{store-options-u0ycEe8s.js → store-options-Dil3lraY.js} +105 -15
- package/dist/stores-C0Kta5Ft.d.ts +59 -0
- package/dist/{stores-CGhM8BN_.js → stores-DxZWDXCs.js} +42 -5
- package/dist/{stream-CvdUS9V1.js → stream-CDvF0sJm.js} +5 -5
- package/dist/{stream-CeCMJmBB.d.ts → stream-ltuLdtTG.d.ts} +4 -3
- package/dist/{style-DiKWnAvn.js → style-DblOtSga.js} +1 -1
- package/dist/{style-Uao76wO7.js → style-Ds11Hmk4.js} +3 -1
- package/dist/{subject-settings-CkEwrw3W.js → subject-settings-8sH5HlII.js} +8 -4
- package/dist/{row-check-Dt35czLy.d.ts → subject-settings-vNW8qmUH.d.ts} +30 -18
- package/dist/supabase/index.d.ts +48 -37
- package/dist/supabase/index.js +6 -6
- package/dist/supabase/middleware.d.ts +19 -13
- package/dist/supabase/middleware.js +7 -7
- package/dist/{supabase-x5JtR2tc.js → supabase-BL1qJzzY.js} +82 -36
- package/dist/{supabase-By_frk3U.js → supabase-D39cuGPt.js} +3 -3
- package/dist/{supabase-hook-BMoEhZir.js → supabase-hook-B-yXnzUl.js} +1206 -286
- package/dist/supabase-hook-H-MtE9Wd.js +1 -0
- package/dist/supautils-BU-pAEpf.js +808 -0
- package/dist/{support-session-C6xJQYut.js → support-session-BsmPCbz8.js} +22 -22
- package/dist/svelte/PermissionBoundary.svelte +2 -6
- package/dist/svelte/Protected.svelte +2 -8
- package/dist/svelte/index.d.ts +9 -8
- package/dist/svelte/index.js +10 -9
- package/dist/svelte/runtime.d.ts +4 -4
- package/dist/svelte/runtime.js +4 -4
- package/dist/svelte/source.d.ts +9 -8
- package/dist/svelte/source.js +6 -5
- package/dist/sveltekit/index.d.ts +30 -12
- package/dist/sveltekit/index.js +2 -2
- package/dist/{ownership-DqaJNJPn.js → tables-DeMNEy6V.js} +1025 -1085
- package/dist/tanstack-start/index.d.ts +22 -10
- package/dist/tanstack-start/index.js +2 -2
- package/dist/{target-Bw37TFNA.js → target-qdEAnnhU.js} +6 -6
- package/dist/{tenancy-DN2QAx9z.js → tenancy-5d1zVBvK.js} +36 -21
- package/dist/terminal/index.d.ts +23 -14
- package/dist/terminal/index.js +17 -20
- package/dist/testing/core.d.ts +2 -0
- package/dist/testing/core.js +1 -0
- package/dist/testing/fixtures/index.d.ts +15 -0
- package/dist/testing/fixtures/index.js +831 -0
- package/dist/testing/index.d.ts +235 -285
- package/dist/testing/index.js +502 -927
- package/dist/testing/saas/index.d.ts +43 -27
- 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-BtfSmdUo.js +444 -0
- package/dist/{thenable-yD7SabqG.js → thenable-BNSM1U2R.js} +12 -3
- package/dist/{time-DkWjdL98.js → time-nR1JesoH.js} +32 -8
- package/dist/{token-BLZcjQgt.js → token-DC9EQiet.js} +15 -13
- package/dist/{tool-refusal-CMLN5xhn.js → tool-refusal-Qpjs_W6M.js} +5 -2
- package/dist/trpc/index.d.ts +11 -8
- package/dist/trpc/index.js +15 -9
- package/dist/type-helpers-BIPIDa6a.d.ts +50 -0
- package/dist/{types-8RnwUAvE.d.ts → types-AMJ5qoU_.d.ts} +49 -30
- package/dist/{types-C_IiEs64.d.ts → types-B2mlGuhW.d.ts} +14 -11
- package/dist/{types-_WhUP7ud.d.ts → types-B4uqKDnp.d.ts} +25 -12
- package/dist/{types-CX7AvRqP.d.ts → types-BJk0BKRX.d.ts} +24 -14
- package/dist/types-B_d_g7T3.d.ts +262 -0
- package/dist/{types-BdtSDbBO.d.ts → types-B_tiYvk8.d.ts} +59 -32
- package/dist/{types-DqzsnlSA.js → types-CcUXciSj.js} +17 -5
- package/dist/{types-Dfv0aTER.d.ts → types-DC1Loeh3.d.ts} +449 -168
- package/dist/{types-lUlG3TVd.d.ts → types-DHKPcqzr.d.ts} +2 -2
- package/dist/{types-C791HFu9.d.ts → types-DjuMBNF9.d.ts} +68 -32
- package/dist/{types-C863OQiR.d.ts → types-HzqOtvsJ.d.ts} +23 -12
- package/dist/{types-Druu9wLM.d.ts → types-ZzRGoViD.d.ts} +77 -24
- package/dist/{types-BzzaHxt3.d.ts → types-ah9NlU6j.d.ts} +137 -48
- package/dist/{types-CVZSNocA.d.ts → types-djVzLlIV.d.ts} +31 -14
- package/dist/{types-BtHtcKMr.d.ts → types-yUuPR5Hy.d.ts} +47 -24
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CYeXek0p.js +1 -0
- package/dist/{usage-SveTRTyr.js → usage-CxEzS4ey.js} +50 -26
- package/dist/{usage-CRVl9V3t.js → usage-ab0PdusX.js} +2 -2
- package/dist/{validation-error-ZfUHbKvf.js → validation-error-CYg2kJEw.js} +4 -1
- package/dist/{validation-error-DHUrAkku.d.ts → validation-error-YGg2mXyH.d.ts} +5 -2
- package/dist/{validity-CZ7-y0Cj.js → validity-Buc7w8E9.js} +13 -10
- package/dist/{verifier-DHuh-okk.js → verifier-DUV6pLqZ.js} +10 -4
- package/dist/{verify-BaAKx7t6.js → verify-DZeqoWu5.js} +417 -489
- package/dist/version-DMQ0dnN5.js +48 -0
- package/dist/version-DizhmD72.js +1 -0
- package/dist/{views-DTIIO-oQ.js → views-CD8wuL4Z.js} +19 -6
- package/dist/vocabulary-DEJ5j4mR.d.ts +70 -0
- package/dist/vue/index.d.ts +71 -21
- package/dist/vue/index.js +50 -13
- package/dist/{waiter-B986w1oK.d.ts → waiter-Bu9zAlE0.d.ts} +13 -10
- package/dist/{waiter-BGUtGYoU.js → waiter-aBLM63_f.js} +1 -1
- package/dist/{watch-vea54PRw.js → watch-Cx_WciLE.js} +8 -8
- package/dist/watch-DB42U1ib.js +1 -0
- package/dist/webmcp/index.d.ts +11 -11
- package/dist/webmcp/index.js +8 -5
- package/dist/{where-scope-C9vfEphl.js → where-scope-DlCdCM_8.js} +56 -30
- package/dist/{who-can-DWBAcaCQ.js → who-can-rI7dCR9Q.js} +23 -20
- package/dist/workflow/index.d.ts +3 -3
- package/dist/workflow/index.js +13 -13
- package/dist/workflow-sdk/index.d.ts +19 -13
- package/dist/workflow-sdk/index.js +6 -6
- package/dist/{wrap-gX61EtpB.js → wrap-B2wziJI_.js} +11 -5
- package/dist/{wrap-C5jC6sKI.d.ts → wrap-DlJE3gS_.d.ts} +8 -5
- package/package.json +25 -14
- package/schemas/catalog-v1.json +3 -0
- 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 +308 -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-credentials/references/tokens-keys-links.md +8 -6
- package/skills/permdock-data/references/queries-and-rls.md +6 -2
- package/skills/permdock-tenancy/SKILL.md +1 -1
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +2 -1
- package/skills/permdock-wire/references/adapters.md +28 -22
- 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/config-bs_kZeYq.js +0 -162
- package/dist/denied-text-CiFQNuMV.d.ts +0 -19
- package/dist/fields-OYG_JR78.js +0 -203
- package/dist/generate-CNU1pIOo.js +0 -171
- package/dist/headers-BIjfMaoC.js +0 -8
- package/dist/headers-CIbDTUTK.d.ts +0 -7
- package/dist/index-Cp_ilKi1.d.ts +0 -91
- package/dist/index-CueFnxQc.d.ts +0 -110
- package/dist/load-BxEtwlBS.js +0 -1
- package/dist/output-files-B3IZJksC.js +0 -1
- package/dist/package-root-Dk_ETdkp.js +0 -31
- package/dist/parse-Cxq0WxrA.js +0 -53
- package/dist/parse-local-manifest-5ODwqu8b.d.ts +0 -10
- package/dist/parse-local-manifest-CVSs6J9I.js +0 -34
- package/dist/peer-C8swJ2GH.js +0 -50
- package/dist/policies-DP2nqRoF.js +0 -295
- package/dist/powersync-BkWWCS1K.js +0 -538
- package/dist/powersync-MMJ2mI2q.js +0 -1
- package/dist/problem-details-B45V5d2b.d.ts +0 -7
- package/dist/prompt-DfZLJLIn.js +0 -32
- package/dist/protected-RFGpakN9.d.ts +0 -38
- package/dist/pull-CvpINcMK.js +0 -1
- package/dist/run-DDwUaJDU.js +0 -1
- package/dist/sources-Xc4zkszI.d.ts +0 -190
- package/dist/stores-BUGNbJTt.d.ts +0 -30
- 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/types-B3_ooZgZ.d.ts +0 -172
- package/dist/usage-CZpSQP6L.js +0 -1
- package/dist/version-CEbQ5UeM.js +0 -18
- package/dist/version-DXkYUISx.js +0 -1
- package/dist/watch--ovlPOc0.js +0 -1
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
import { a as sole, n as isReadonlyArray, r as isRecord } from "./compact-CAf557ZB.js";
|
|
2
|
+
import { n as isForbiddenKey } from "./paths-CUvURzHp.js";
|
|
3
|
+
import { u as isSqlFunctionField } from "./refs-C8BKxc_e.js";
|
|
4
|
+
import { u as isoAt } from "./time-nR1JesoH.js";
|
|
5
|
+
import { f as scopeList, l as rootScope, s as resolveScope, u as scopeChain } from "./scopes-0Up_UA2t.js";
|
|
6
|
+
import { t as isoInstant } from "./instant-DK3Iz83e.js";
|
|
7
|
+
import { m as findRole, v as listRoles } from "./grantee-Dke-9rd1.js";
|
|
8
|
+
import { r as relationCondition, t as inheritCondition } from "./grantee-conditions-BrYPe-w0.js";
|
|
9
|
+
import { i as configError } from "./errors-Vpu6rKHl.js";
|
|
10
|
+
import { i as qualifySqlTable } from "./sql-0WjxIp9g.js";
|
|
11
|
+
import { a as scopeMembershipTable, i as scopeColumn, s as relatedSql } from "./compile-EzEMB-7v.js";
|
|
12
|
+
import { l as roleColumn } from "./sources-CMAM-2A3.js";
|
|
13
|
+
import { A as quoteQualified, D as qualified, E as helperSchema, L as sqlType, M as textArrayLiteral, O as quoteIdent, R as tenantTypeOf, S as CLAIM, T as declaredArray, _ as subjectClaimJsonSql, b as tenantClaimSql, d as keyTenantSql, f as kindFilterSql, h as roleKindSql, j as quoteTable, k as quoteLiteral, m as memberVia, p as liveSessionSql, s as signedIn, t as CUSTOM_ROLES, u as globalKindFilterSql, v as subjectClaimSql, y as subjectIdSql } from "./shared-Afctg7B0.js";
|
|
14
|
+
import { $ as graphSqlText, A as claimKeptLines, B as hasCustomRoleChecksFor, F as globalRoleRows, I as membershipRows, L as roleRows, M as memberColumn, N as sourceFilters, P as sourcesKeep, U as storedTenant, X as CLOSURE, Z as graphHelper, _t as activeMembershipSql, c as holderSources, ct as memberIdsHelper, d as sourceRowsSql, f as sourceTransferTriggerSql, g as OWNERSHIP, gt as activeInstancesSql, h as ASSIGNMENTS, ht as scopeTable, it as linkHelper, j as disabledColumn, l as pairsSql, m as transferTriggerSql, mt as scopeSources, nt as inheritedRowsHelper, p as holdersTriggerSql, pt as restrictedHelper, st as memberForTable, u as sourceHoldersTriggerSql, ut as memberRoleOf, yt as activeUserSql } from "./tables-DeMNEy6V.js";
|
|
15
|
+
//#region src/cli/sql/conditions.ts
|
|
16
|
+
const MAX_CLAIM_DEPTH = 8;
|
|
17
|
+
/**
|
|
18
|
+
* The claim path of a `principal.claim.*` / `principal.claims.*` ref, one
|
|
19
|
+
* segment per JSON key; `undefined` for any other ref.
|
|
20
|
+
*/
|
|
21
|
+
function claimPath(ref) {
|
|
22
|
+
const prefix = ["principal.claim.", "principal.claims."].find((item) => ref.startsWith(item));
|
|
23
|
+
if (prefix === void 0) return;
|
|
24
|
+
const segments = ref.slice(prefix.length).split(".");
|
|
25
|
+
if (segments.length > MAX_CLAIM_DEPTH) throw configError(`PermDock CLI: claim path '${ref}' is deeper than ${MAX_CLAIM_DEPTH}`);
|
|
26
|
+
for (const segment of segments) if (!CLAIM.test(segment) || isForbiddenKey(segment)) throw configError(`PermDock CLI: unsafe claim name '${segment}'`);
|
|
27
|
+
return segments;
|
|
28
|
+
}
|
|
29
|
+
function jsonPath(base, keys, last) {
|
|
30
|
+
let sql = base;
|
|
31
|
+
for (const [index, key] of keys.entries()) sql += ` ${index === keys.length - 1 ? last : "->"} ${quoteLiteral(key)}`;
|
|
32
|
+
return sql;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The JSON document a claim path starts from and the keys to walk in it. The
|
|
36
|
+
* `guc` dialect keeps each top-level claim in its own setting as JSON text.
|
|
37
|
+
*/
|
|
38
|
+
function claimRoot(ctx, path) {
|
|
39
|
+
const [head, ...rest] = path;
|
|
40
|
+
if (head === void 0) throw configError("PermDock CLI: empty claim path");
|
|
41
|
+
switch (ctx.dialect) {
|
|
42
|
+
case "supabase": return {
|
|
43
|
+
root: "(select auth.jwt())",
|
|
44
|
+
keys: path
|
|
45
|
+
};
|
|
46
|
+
case "neon": return {
|
|
47
|
+
root: "(select auth.session())",
|
|
48
|
+
keys: path
|
|
49
|
+
};
|
|
50
|
+
case "guc": return {
|
|
51
|
+
root: subjectClaimJsonSql(ctx, head),
|
|
52
|
+
keys: rest
|
|
53
|
+
};
|
|
54
|
+
default: return ctx.dialect;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** The claim at `path` as `jsonb`. */
|
|
58
|
+
function claimJsonSql(ctx, path) {
|
|
59
|
+
const { root, keys } = claimRoot(ctx, path);
|
|
60
|
+
return keys.length === 0 ? root : `(${jsonPath(root, keys, "->")})`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The claim at `path` as text: `->>` on the last key, or the plain setting for
|
|
64
|
+
* a one-segment `guc` claim.
|
|
65
|
+
*/
|
|
66
|
+
function claimTextSql(ctx, path) {
|
|
67
|
+
const [head] = path;
|
|
68
|
+
if (path.length === 1 && head !== void 0) return subjectClaimSql(ctx, head);
|
|
69
|
+
const { root, keys } = claimRoot(ctx, path);
|
|
70
|
+
return `(${jsonPath(root, keys, "->>")})`;
|
|
71
|
+
}
|
|
72
|
+
/** The JSON kind a claim must have to compare with a column of `type`. */
|
|
73
|
+
function jsonKindOf(type) {
|
|
74
|
+
if (type === "numeric") return "number";
|
|
75
|
+
return type === "boolean" ? "boolean" : "string";
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A claim compared with a column of `type`. Numbers and booleans must be JSON
|
|
79
|
+
* of that kind (anything else is `null`, so the comparison is false, as in
|
|
80
|
+
* memory); other types cast the text form. A one-segment `guc` claim is text
|
|
81
|
+
* and is cast as is.
|
|
82
|
+
*/
|
|
83
|
+
function typedClaimSql(ctx, path, type) {
|
|
84
|
+
const text = claimTextSql(ctx, path);
|
|
85
|
+
if (type === void 0 || type === "text") return text;
|
|
86
|
+
const cast = `${text}::${sqlType(type)}`;
|
|
87
|
+
const kind = jsonKindOf(type);
|
|
88
|
+
if (kind === "string" || ctx.dialect === "guc" && path.length === 1) return `(${cast})`;
|
|
89
|
+
return `(case when jsonb_typeof(${claimJsonSql(ctx, path)}) = '${kind}' then ${cast} end)`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The elements of an array claim as a Postgres array of the column's type,
|
|
93
|
+
* built once per statement (an uncorrelated `array(select ...)` is an
|
|
94
|
+
* InitPlan). A missing or non-array claim is the empty array; elements of
|
|
95
|
+
* another JSON kind are left out.
|
|
96
|
+
*/
|
|
97
|
+
function claimArraySql(ctx, path, type) {
|
|
98
|
+
const json = claimJsonSql(ctx, path);
|
|
99
|
+
return `array(select ${type === void 0 || type === "text" ? `(e #>> '{}')` : `(e #>> '{}')::${sqlType(type)}`} from jsonb_array_elements(case when jsonb_typeof(${json}) = 'array' then ${json} else '[]'::jsonb end) e where jsonb_typeof(e) = '${jsonKindOf(type)}')`;
|
|
100
|
+
}
|
|
101
|
+
function sqlValue(value, ctx, field) {
|
|
102
|
+
if (value !== null && typeof value === "object" && "ref" in value) return compileRef(value.ref, ctx, field);
|
|
103
|
+
if (value !== null && typeof value === "object" && "date" in value) {
|
|
104
|
+
const instant = isoInstant(value.date);
|
|
105
|
+
return instant === void 0 ? "null" : quoteLiteral(isoAt(instant));
|
|
106
|
+
}
|
|
107
|
+
if (isReadonlyArray(value)) return `array[${value.map((item) => sqlValue(item, ctx)).join(", ")}]`;
|
|
108
|
+
if (value === null) return "null";
|
|
109
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
110
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
111
|
+
if (typeof value === "string") return quoteLiteral(value);
|
|
112
|
+
throw configError("PermDock CLI: non-portable condition value");
|
|
113
|
+
}
|
|
114
|
+
function compileRef(ref, ctx, field) {
|
|
115
|
+
if (ref === "principal.id") return subjectIdSql(ctx);
|
|
116
|
+
if (ref === "now") return "now()";
|
|
117
|
+
const path = claimPath(ref);
|
|
118
|
+
if (ref === "principal.tenant" || path?.length === 1 && path[0] === ctx.tenantClaim) return tenantClaimSql(ctx);
|
|
119
|
+
if (path !== void 0) return typedClaimSql(ctx, path, field === void 0 ? void 0 : ctx.columnTypes?.[field]);
|
|
120
|
+
if (ref === "context" || ref.startsWith("context.")) throw configError(`PermDock CLI: '${ref}' is request context, which is not in the token; RLS cannot read it (permdock doctor PD027)`);
|
|
121
|
+
throw configError(`PermDock CLI: non-portable subject ref '${ref}'`);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `value` with `\`, `%` and `_` escaped for `like ... escape '\'`, as
|
|
125
|
+
* `escapeLike` does in process.
|
|
126
|
+
*/
|
|
127
|
+
function likeLiteralSql(value) {
|
|
128
|
+
return `replace(replace(replace(${value}, '\\', '\\\\'), '%', '\\%'), '_', '\\_')`;
|
|
129
|
+
}
|
|
130
|
+
function compareSql(op, field, value) {
|
|
131
|
+
const left = quoteIdent(field);
|
|
132
|
+
switch (op) {
|
|
133
|
+
case "eq": return `${left} = ${value}`;
|
|
134
|
+
case "ne": return `${left} <> ${value}`;
|
|
135
|
+
case "gt": return `${left} > ${value}`;
|
|
136
|
+
case "gte": return `${left} >= ${value}`;
|
|
137
|
+
case "lt": return `${left} < ${value}`;
|
|
138
|
+
case "lte": return `${left} <= ${value}`;
|
|
139
|
+
case "contains": return `${left}::text like '%' || ${likeLiteralSql(`${value}::text`)} || '%' escape '\\'`;
|
|
140
|
+
default: return op;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether the row's `rowField` is among the ids in `table.rowColumn` the
|
|
145
|
+
* subject holds a matching membership on. The row field stays outside the
|
|
146
|
+
* subquery: inside it, an unqualified name resolves to the membership table
|
|
147
|
+
* first, so `m.org_id = "org_id"` would compare the membership with itself.
|
|
148
|
+
*/
|
|
149
|
+
function membershipSql(table, rowColumn, rowField, roles, ctx, tenantColumn, scope) {
|
|
150
|
+
const parts = [`m.${quoteIdent(table.user)} = ${subjectIdSql(ctx)}`];
|
|
151
|
+
const role = memberRoleOf(table);
|
|
152
|
+
if (roles.length > 0) {
|
|
153
|
+
const held = role.through === void 0 && !role.lateral ? `m.${quoteIdent(role.column)}` : role.lookup;
|
|
154
|
+
parts.push(`${held} = any(${textArrayLiteral(roles)})`);
|
|
155
|
+
const via = memberVia(table);
|
|
156
|
+
const single = sole(roles);
|
|
157
|
+
const kind = single === void 0 ? kindFilterSql(ctx, role.lookup, via) : roleKindSql(ctx, single, via);
|
|
158
|
+
if (kind !== void 0) parts.push(kind);
|
|
159
|
+
}
|
|
160
|
+
if (table.expiresAt !== void 0) parts.push(`(m.${quoteIdent(table.expiresAt)} is null or m.${quoteIdent(table.expiresAt)} > now())`);
|
|
161
|
+
parts.push(...activeMembershipSql(ctx, table.disabledAt === void 0 ? void 0 : `m.${quoteIdent(table.disabledAt)}`));
|
|
162
|
+
if (tenantColumn !== void 0 && ctx.tenants !== "all") parts.push(`m.${quoteIdent(tenantColumn)} = ${tenantClaimSql(ctx)}`);
|
|
163
|
+
parts.push(...activeUserSql(ctx));
|
|
164
|
+
if (scope !== void 0) {
|
|
165
|
+
const tenantOf = scope === rootScope(ctx.scopes) ? rowColumn : tenantColumn;
|
|
166
|
+
const keyed = keyTenantSql(ctx, tenantOf === void 0 ? void 0 : `m.${quoteIdent(tenantOf)}`);
|
|
167
|
+
if (keyed !== void 0) parts.push(keyed);
|
|
168
|
+
parts.push(...activeInstancesSql(ctx, scope, (name) => {
|
|
169
|
+
const held = scopeColumn(table, ctx.scopes, name);
|
|
170
|
+
return held === void 0 ? void 0 : `m.${quoteIdent(held)}`;
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
const expand = roles.length > 0 && role.lateral ? role.join : "";
|
|
174
|
+
return `${quoteIdent(rowField)} in (select m.${quoteIdent(rowColumn)} from ${quoteTable(table.table)} m${expand} where ${parts.join(" and ")})`;
|
|
175
|
+
}
|
|
176
|
+
function compileMemberOf(condition, ctx) {
|
|
177
|
+
const scope = condition.scope === "resource" ? void 0 : resolveScope(ctx.scopes, condition.scope);
|
|
178
|
+
if (condition.scope !== "resource" && scope === void 0) throw configError(`PermDock CLI: memberOf ${condition.scope} names a scope the policy does not declare`);
|
|
179
|
+
const mapping = scope === void 0 ? condition.resource === void 0 ? void 0 : ctx.memberships?.resource?.[condition.resource] : scopeMembershipTable(ctx.memberships, ctx.scopes, scope);
|
|
180
|
+
if (mapping !== void 0) {
|
|
181
|
+
const named = scope === void 0 ? void 0 : scopeTable(ctx, scope);
|
|
182
|
+
const rowColumn = scope === void 0 ? mapping.id : named?.column;
|
|
183
|
+
if (rowColumn === void 0) throw configError(`PermDock CLI: memberships mapping for ${condition.scope} is missing the row column`);
|
|
184
|
+
const tenantColumn = scope === void 0 || scope === rootScope(ctx.scopes) ? void 0 : named?.tenantColumn;
|
|
185
|
+
const primary = membershipSql(mapping, rowColumn, condition.field, condition.roles, ctx, tenantColumn, scope);
|
|
186
|
+
if (condition.scope !== "resource" || condition.parents === void 0) return primary;
|
|
187
|
+
return `(${[primary, ...condition.parents.flatMap((parent) => {
|
|
188
|
+
if (typeof parent === "string") return [membershipSql(mapping, rowColumn, parent, condition.roles, ctx, tenantColumn)];
|
|
189
|
+
const hopTable = ctx.memberships?.resource?.[parent.resource];
|
|
190
|
+
return hopTable?.id === void 0 ? [] : [membershipSql(hopTable, hopTable.id, parent.field, condition.roles, ctx)];
|
|
191
|
+
})].join(" or ")})`;
|
|
192
|
+
}
|
|
193
|
+
if (scope !== void 0 && condition.roles.length === 0 && scopeSources(ctx, scope).length > 0) return `${quoteIdent(condition.field)} in (select ${qualified(ctx, memberIdsHelper(scope))}())`;
|
|
194
|
+
if (scope !== void 0 && scope === rootScope(ctx.scopes)) {
|
|
195
|
+
const parts = [
|
|
196
|
+
ctx.tenants === "all" ? `${quoteIdent(condition.field)} in (select ${qualified(ctx, memberIdsHelper(scope))}())` : `${quoteIdent(condition.field)} = ${tenantClaimSql(ctx)}`,
|
|
197
|
+
...ctx.tenants === "all" ? [] : [keyTenantSql(ctx, quoteIdent(condition.field))].filter((part) => part !== void 0),
|
|
198
|
+
...activeUserSql(ctx),
|
|
199
|
+
...activeInstancesSql(ctx, scope, () => quoteIdent(condition.field))
|
|
200
|
+
];
|
|
201
|
+
return parts.length === 1 ? parts.join("") : `(${parts.join(" and ")})`;
|
|
202
|
+
}
|
|
203
|
+
throw configError(`PermDock CLI: memberOf ${condition.scope} needs a memberships table mapping`);
|
|
204
|
+
}
|
|
205
|
+
function compileConditionSql(condition, ctx) {
|
|
206
|
+
switch (condition.op) {
|
|
207
|
+
case "eq":
|
|
208
|
+
case "ne":
|
|
209
|
+
case "gt":
|
|
210
|
+
case "gte":
|
|
211
|
+
case "lt":
|
|
212
|
+
case "lte": return compareSql(condition.op, condition.field, sqlValue(condition.value, ctx, condition.field));
|
|
213
|
+
case "contains": {
|
|
214
|
+
const element = ctx.arrayColumns?.[condition.field];
|
|
215
|
+
if (element !== void 0) return `${sqlValue(condition.value, {
|
|
216
|
+
...ctx,
|
|
217
|
+
columnTypes: {
|
|
218
|
+
...ctx.columnTypes,
|
|
219
|
+
[condition.field]: element
|
|
220
|
+
}
|
|
221
|
+
}, condition.field)} = any(${quoteIdent(condition.field)})`;
|
|
222
|
+
return compareSql(condition.op, condition.field, sqlValue(condition.value, ctx, condition.field));
|
|
223
|
+
}
|
|
224
|
+
case "in":
|
|
225
|
+
case "notIn": {
|
|
226
|
+
const keyword = condition.op === "in" ? "in" : "not in";
|
|
227
|
+
if (!Array.isArray(condition.value) && typeof condition.value === "object" && "ref" in condition.value) {
|
|
228
|
+
const path = claimPath(condition.value.ref);
|
|
229
|
+
if (path === void 0) {
|
|
230
|
+
compileRef(condition.value.ref, ctx);
|
|
231
|
+
throw configError(`PermDock CLI: non-portable ${condition.op} against '${condition.value.ref}'`);
|
|
232
|
+
}
|
|
233
|
+
const column = quoteIdent(condition.field);
|
|
234
|
+
const list = claimArraySql(ctx, path, ctx.columnTypes?.[condition.field]);
|
|
235
|
+
return condition.op === "in" ? `${column} = any (${list})` : `(${column} is not null and not (${column} = any (${list})))`;
|
|
236
|
+
}
|
|
237
|
+
const values = condition.value.map((item) => sqlValue(item, ctx));
|
|
238
|
+
if (values.length === 0) return condition.op === "in" ? "false" : `${quoteIdent(condition.field)} is not null`;
|
|
239
|
+
return `${quoteIdent(condition.field)} ${keyword} (${values.join(", ")})`;
|
|
240
|
+
}
|
|
241
|
+
case "isNull": return `${quoteIdent(condition.field)} is ${condition.value ? "" : "not "}null`;
|
|
242
|
+
case "and": return `(${condition.conditions.map((item) => compileConditionSql(item, ctx)).join(" and ")})`;
|
|
243
|
+
case "or":
|
|
244
|
+
if (condition.conditions.length === 0) return "false";
|
|
245
|
+
return `(${condition.conditions.map((item) => compileConditionSql(item, ctx)).join(" or ")})`;
|
|
246
|
+
case "not": return `(${compileConditionSql(condition.condition, ctx)}) is not true`;
|
|
247
|
+
case "memberOf": return compileMemberOf(condition, ctx);
|
|
248
|
+
case "related": return compileRelatedSql(condition, ctx);
|
|
249
|
+
case "sqlFunction":
|
|
250
|
+
if (ctx.inlineFunctions === true) return compileConditionSql(condition.twin, ctx);
|
|
251
|
+
return `${quoteTable(condition.name)}(${condition.args.map((arg) => isSqlFunctionField(arg) ? quoteIdent(arg.field) : sqlValue(arg, ctx)).join(", ")})`;
|
|
252
|
+
case "opaque": return condition.sql;
|
|
253
|
+
case "liveSession": return liveSessionSql(ctx);
|
|
254
|
+
default: return condition;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function andConditions(left, right) {
|
|
258
|
+
if (left === void 0) return right;
|
|
259
|
+
if (right === void 0) return left;
|
|
260
|
+
return {
|
|
261
|
+
op: "and",
|
|
262
|
+
conditions: [left, right]
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function valueContextRefs(value) {
|
|
266
|
+
if (Array.isArray(value)) return value.flatMap(valueContextRefs);
|
|
267
|
+
if (isRecord(value) && typeof value["ref"] === "string" && (value["ref"] === "context" || value["ref"].startsWith("context."))) return [value["ref"]];
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The `context.*` refs a condition reads. The request context is not in the
|
|
272
|
+
* token, so RLS cannot evaluate them.
|
|
273
|
+
*/
|
|
274
|
+
function contextRefs(condition) {
|
|
275
|
+
if (condition === void 0) return [];
|
|
276
|
+
switch (condition.op) {
|
|
277
|
+
case "eq":
|
|
278
|
+
case "ne":
|
|
279
|
+
case "gt":
|
|
280
|
+
case "gte":
|
|
281
|
+
case "lt":
|
|
282
|
+
case "lte":
|
|
283
|
+
case "contains":
|
|
284
|
+
case "in":
|
|
285
|
+
case "notIn": return valueContextRefs(condition.value);
|
|
286
|
+
case "and":
|
|
287
|
+
case "or": return condition.conditions.flatMap((child) => contextRefs(child));
|
|
288
|
+
case "not": return contextRefs(condition.condition);
|
|
289
|
+
case "sqlFunction": return [...condition.args.flatMap(valueContextRefs), ...contextRefs(condition.twin)];
|
|
290
|
+
case "isNull":
|
|
291
|
+
case "memberOf":
|
|
292
|
+
case "related":
|
|
293
|
+
case "opaque":
|
|
294
|
+
case "liveSession": return [];
|
|
295
|
+
default: return condition;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function sqlFunctionNames(condition) {
|
|
299
|
+
if (condition === void 0) return [];
|
|
300
|
+
switch (condition.op) {
|
|
301
|
+
case "sqlFunction": return [condition.name, ...sqlFunctionNames(condition.twin)];
|
|
302
|
+
case "and":
|
|
303
|
+
case "or": return condition.conditions.flatMap((child) => sqlFunctionNames(child));
|
|
304
|
+
case "not": return sqlFunctionNames(condition.condition);
|
|
305
|
+
case "eq":
|
|
306
|
+
case "ne":
|
|
307
|
+
case "gt":
|
|
308
|
+
case "gte":
|
|
309
|
+
case "lt":
|
|
310
|
+
case "lte":
|
|
311
|
+
case "contains":
|
|
312
|
+
case "in":
|
|
313
|
+
case "notIn":
|
|
314
|
+
case "isNull":
|
|
315
|
+
case "memberOf":
|
|
316
|
+
case "related":
|
|
317
|
+
case "opaque":
|
|
318
|
+
case "liveSession": return [];
|
|
319
|
+
default: return condition;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* A `related` condition as uncorrelated subqueries, so Postgres runs the helper
|
|
324
|
+
* once per statement: the row's field in the closure's descendants of the ids
|
|
325
|
+
* the subject holds the relation on (or those ids alone at depth 0). The helper
|
|
326
|
+
* sits in `array(...)` so it stays an InitPlan; a plain `in` gets pulled into a
|
|
327
|
+
* join that can rescan it per closure row.
|
|
328
|
+
*/
|
|
329
|
+
function compileRelatedSql(condition, ctx) {
|
|
330
|
+
if (condition.hops !== void 0 && condition.hops.length > 0) return compileHoppedSql(condition, ctx);
|
|
331
|
+
const type = ctx.columnTypes?.[condition.field];
|
|
332
|
+
const cast = type === void 0 || type === "text" ? "" : `::${type}`;
|
|
333
|
+
const column = type === void 0 ? `${quoteIdent(condition.field)}::text` : quoteIdent(condition.field);
|
|
334
|
+
const helper = condition.permission === void 0 ? `${qualified(ctx, graphHelper(condition.resource))}(${quoteLiteral(condition.relation)})` : `${qualified(ctx, inheritedRowsHelper(condition.resource))}(${quoteLiteral(condition.permission)})`;
|
|
335
|
+
const cap = ctx.graph?.closures[condition.resource];
|
|
336
|
+
let inner;
|
|
337
|
+
if (condition.depth > 0 && cap !== void 0) {
|
|
338
|
+
const depth = condition.depth < cap ? ` and depth <= ${String(condition.depth)}` : "";
|
|
339
|
+
inner = `select descendant${cast} from ${qualified(ctx, CLOSURE.table)} where resource = ${quoteLiteral(condition.resource)}${depth} and ancestor = any (array(select ${helper}))`;
|
|
340
|
+
} else inner = cast === "" ? `select ${helper}` : `select p.id${cast} from ${helper} as p(id)`;
|
|
341
|
+
const reach = `${column} in (${inner})`;
|
|
342
|
+
if (condition.parent === true && condition.restricted !== void 0) return `(${reach} and ${quoteIdent(condition.restricted)} is not true)`;
|
|
343
|
+
return reach;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* A `related` condition that crosses link hops: the reached instances of the
|
|
347
|
+
* last resource, carried back over each link by its security definer helper, so
|
|
348
|
+
* the hop reads a table the subject may not see.
|
|
349
|
+
*/
|
|
350
|
+
function compileHoppedSql(condition, ctx) {
|
|
351
|
+
const resources = ctx.graph?.resources;
|
|
352
|
+
if (resources === void 0) throw configError("PermDock CLI: a related condition with link hops needs the policy resources in the RLS context");
|
|
353
|
+
const schema = quoteIdent(helperSchema(ctx));
|
|
354
|
+
return graphSqlText(relatedSql(condition, {
|
|
355
|
+
resources,
|
|
356
|
+
...ctx.graph?.tables === void 0 ? {} : { tables: ctx.graph.tables },
|
|
357
|
+
closure: `${helperSchema(ctx)}.${CLOSURE.table}`,
|
|
358
|
+
closureDepths: ctx.graph?.closures ?? {},
|
|
359
|
+
qualify: qualifySqlTable,
|
|
360
|
+
holders: (resource, relation) => [{ text: `select ${schema}.${graphHelper(resource)}(${quoteLiteral(relation)})` }],
|
|
361
|
+
permitted: (resource, permission) => [{ text: `select ${schema}.${inheritedRowsHelper(resource)}(${quoteLiteral(permission)})` }],
|
|
362
|
+
restrictedRows: (resource) => [{ text: `select ${schema}.${restrictedHelper(resource)}()` }],
|
|
363
|
+
linked: (resource, link, targets) => [
|
|
364
|
+
{ text: `select ${schema}.${linkHelper(resource, link)}(array(` },
|
|
365
|
+
...targets,
|
|
366
|
+
{ text: "))" }
|
|
367
|
+
]
|
|
368
|
+
}), ctx);
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/cli/sql/grants.ts
|
|
372
|
+
/**
|
|
373
|
+
* The `role_permissions` grant key of a break-glass grant. It differs from the
|
|
374
|
+
* permission key, so `authorize()` and the table policies never count it; only
|
|
375
|
+
* the break-glass read asks for it.
|
|
376
|
+
*/
|
|
377
|
+
function breakGlassKey(permission) {
|
|
378
|
+
return `${permission}#break-glass`;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The role and scope holding a break-glass grant. `breakGlass()` sets `to:
|
|
382
|
+
* authenticated`, so inside `role()` the role is on the grant itself.
|
|
383
|
+
*/
|
|
384
|
+
function breakGlassHolder(item) {
|
|
385
|
+
if (item.access.kind === "role") return {
|
|
386
|
+
role: item.access.role,
|
|
387
|
+
scope: item.access.scope
|
|
388
|
+
};
|
|
389
|
+
const { role, scope } = item.grant;
|
|
390
|
+
return role === null || typeof scope !== "string" ? void 0 : {
|
|
391
|
+
role,
|
|
392
|
+
scope
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function granteeItems(to) {
|
|
396
|
+
return Array.isArray(to) ? to : [to];
|
|
397
|
+
}
|
|
398
|
+
function relationWhere(policy, grant, grantee) {
|
|
399
|
+
const node = policy.resources.get(grant.permission.resource);
|
|
400
|
+
const where = relationCondition(grantee, node, scopeList(policy.scopes), {
|
|
401
|
+
resources: policy.resources,
|
|
402
|
+
now: { ref: "now" }
|
|
403
|
+
});
|
|
404
|
+
if (where === void 0) throw configError(`PermDock CLI: grant ${grant.permission.key} names relation '${grantee.relation}', which ${grant.permission.resource} does not declare`);
|
|
405
|
+
return where;
|
|
406
|
+
}
|
|
407
|
+
function inheritWhere(policy, grant, grantee) {
|
|
408
|
+
const where = inheritCondition(grantee, policy.resources.get(grant.permission.resource), policy.resources);
|
|
409
|
+
if (where === void 0) throw configError(`PermDock CLI: grant ${grant.permission.key} inherits ${grantee.permission}, which ${grant.permission.resource} cannot reach`);
|
|
410
|
+
return where;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Only a deny for `oauth-client` actors compiles: Postgres sees `client_id` and
|
|
414
|
+
* `act`, not other actor kinds.
|
|
415
|
+
*/
|
|
416
|
+
function actorAccess(grant, grantee, others) {
|
|
417
|
+
if (grant.effect !== "deny" || grantee.actor !== "oauth-client" || others > 0) throw configError(`PermDock CLI: grant ${grant.permission.key} is limited to an actor grantee; RLS compiles only deny(…, { to: actor('oauth-client') }) on its own, so enforce it in the application`);
|
|
418
|
+
return {
|
|
419
|
+
kind: "actor",
|
|
420
|
+
actor: "oauth-client"
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function accessOf(grant, roles, anyone) {
|
|
424
|
+
const [held, ...more] = roles;
|
|
425
|
+
if (more.length > 0) throw configError(`PermDock CLI: grant ${grant.permission.key} requires several roles at once (${roles.map((item) => item.role).join(", ")}); RLS compiles one role per grant`);
|
|
426
|
+
if (held === void 0) return anyone ? { kind: "anyone" } : { kind: "authenticated" };
|
|
427
|
+
if (typeof held.scope === "object") return {
|
|
428
|
+
kind: "resource",
|
|
429
|
+
role: held.role,
|
|
430
|
+
resource: held.scope.resource
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
kind: "role",
|
|
434
|
+
role: held.role,
|
|
435
|
+
scope: held.scope
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Every grant RLS must enforce: role grants and top-level `definePolicy({
|
|
440
|
+
* grants })` alike (`policy.grants` holds both). A grantee Postgres cannot see
|
|
441
|
+
* (plan, actor, assurance) fails with the permission named.
|
|
442
|
+
*/
|
|
443
|
+
function collectGrants(policy) {
|
|
444
|
+
return policy.grants.map((grant) => {
|
|
445
|
+
const items = granteeItems(grant.to);
|
|
446
|
+
const roles = [];
|
|
447
|
+
let anyone = true;
|
|
448
|
+
let where = grant.where;
|
|
449
|
+
let delegated;
|
|
450
|
+
for (const item of items) switch (item.kind) {
|
|
451
|
+
case "role":
|
|
452
|
+
roles.push(item);
|
|
453
|
+
break;
|
|
454
|
+
case "anyone": break;
|
|
455
|
+
case "authenticated":
|
|
456
|
+
anyone = false;
|
|
457
|
+
break;
|
|
458
|
+
case "relation":
|
|
459
|
+
anyone = false;
|
|
460
|
+
where = andConditions(where, relationWhere(policy, grant, item));
|
|
461
|
+
break;
|
|
462
|
+
case "inherit":
|
|
463
|
+
anyone = false;
|
|
464
|
+
where = andConditions(where, inheritWhere(policy, grant, item));
|
|
465
|
+
break;
|
|
466
|
+
case "actor":
|
|
467
|
+
delegated = actorAccess(grant, item, items.length - 1);
|
|
468
|
+
break;
|
|
469
|
+
case "plan":
|
|
470
|
+
case "assurance": throw configError(`PermDock CLI: grant ${grant.permission.key} is limited to a ${item.kind} grantee, which RLS cannot compile; enforce it in the application`);
|
|
471
|
+
default: return item;
|
|
472
|
+
}
|
|
473
|
+
const access = delegated ?? accessOf(grant, roles, anyone);
|
|
474
|
+
const label = access.kind === "role" || access.kind === "resource" ? access.role : access.kind;
|
|
475
|
+
return where === void 0 ? {
|
|
476
|
+
grant,
|
|
477
|
+
label,
|
|
478
|
+
access
|
|
479
|
+
} : {
|
|
480
|
+
grant,
|
|
481
|
+
label,
|
|
482
|
+
access,
|
|
483
|
+
where
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* The declared roles a custom role may draw grants from: the custom-role
|
|
489
|
+
* ceiling.
|
|
490
|
+
*/
|
|
491
|
+
function assignableRoles(policy) {
|
|
492
|
+
return [...roleNames(policy)].toSorted().filter((name) => policy.rolesByName.get(name)?.assignable ?? listRoles(policy.vocabulary.roles).some((leaf) => leaf.key === name && leaf.assignable));
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Declared role names (bindings and `defineRoles` leaves) plus any role a
|
|
496
|
+
* top-level grant names.
|
|
497
|
+
*/
|
|
498
|
+
function roleNames(policy) {
|
|
499
|
+
const names = new Set(policy.roles.map((item) => item.name));
|
|
500
|
+
for (const leaf of listRoles(policy.vocabulary.roles)) names.add(leaf.key);
|
|
501
|
+
for (const item of collectGrants(policy)) if (item.access.kind === "role" || item.access.kind === "resource") names.add(item.access.role);
|
|
502
|
+
return [...names];
|
|
503
|
+
}
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/cli/rls/assignments.ts
|
|
506
|
+
/**
|
|
507
|
+
* The client roles whose writes the assignment triggers check; any other role
|
|
508
|
+
* is a trusted path.
|
|
509
|
+
*/
|
|
510
|
+
const CLIENT_ROLES = [
|
|
511
|
+
"anon",
|
|
512
|
+
"anonymous",
|
|
513
|
+
"authenticated"
|
|
514
|
+
];
|
|
515
|
+
/**
|
|
516
|
+
* The mapped membership tables, `rls.assignments.tables` and the global-roles
|
|
517
|
+
* table `rls.roles`.
|
|
518
|
+
*/
|
|
519
|
+
function assignedTables(ctx) {
|
|
520
|
+
const out = [];
|
|
521
|
+
for (const { name } of ctx.scopes) {
|
|
522
|
+
const mapped = scopeTable(ctx, name);
|
|
523
|
+
if (mapped === void 0) continue;
|
|
524
|
+
out.push({
|
|
525
|
+
table: mapped.table.table,
|
|
526
|
+
scope: name,
|
|
527
|
+
id: mapped.column,
|
|
528
|
+
tenant: mapped.tenantColumn ?? mapped.column,
|
|
529
|
+
role: mapped.table.role,
|
|
530
|
+
user: mapped.table.user
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (ctx.roles !== void 0) out.push({
|
|
534
|
+
table: ctx.roles.table,
|
|
535
|
+
scope: "global",
|
|
536
|
+
role: ctx.roles.role ?? "role",
|
|
537
|
+
user: ctx.roles.user ?? "user_id"
|
|
538
|
+
});
|
|
539
|
+
for (const extra of ctx.assignments?.tables ?? []) {
|
|
540
|
+
const scope = resolveScope(ctx.scopes, extra.scope);
|
|
541
|
+
if (scope === void 0) throw configError(`PermDock CLI: rls.assignments.tables names scope '${extra.scope}', which the policy does not declare`);
|
|
542
|
+
if (scope !== ctx.scopes[0]?.name && extra.tenant === void 0) throw configError(`PermDock CLI: rls.assignments.tables entry ${extra.table} assigns at ${scope}, below the first scope: name its tenant column`);
|
|
543
|
+
out.push({
|
|
544
|
+
table: extra.table,
|
|
545
|
+
scope,
|
|
546
|
+
id: extra.id,
|
|
547
|
+
tenant: extra.tenant ?? extra.id,
|
|
548
|
+
role: extra.role,
|
|
549
|
+
...extra.user === void 0 ? {} : { user: extra.user }
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* The tables `rls.assignments.ownRole` refuses a caller's own rows on, by table
|
|
556
|
+
* key.
|
|
557
|
+
*/
|
|
558
|
+
function ownRoleTables(ctx, tables) {
|
|
559
|
+
const own = ctx.assignments?.ownRole;
|
|
560
|
+
if (own === void 0) return /* @__PURE__ */ new Set();
|
|
561
|
+
if (own === "refuse") return new Set(tables.filter((entry) => entry.user !== void 0).map((entry) => qualifySqlTable(entry.table)));
|
|
562
|
+
const out = /* @__PURE__ */ new Set();
|
|
563
|
+
for (const [name, value] of Object.entries(own)) {
|
|
564
|
+
const entry = tables.find((table) => qualifySqlTable(table.table) === qualifySqlTable(name));
|
|
565
|
+
if (entry === void 0) throw configError(`PermDock CLI: rls.assignments.ownRole names ${name}, which no assignment trigger guards`);
|
|
566
|
+
if (value !== "refuse") throw configError(`PermDock CLI: rls.assignments.ownRole.${name} must be 'refuse'`);
|
|
567
|
+
if (entry.user === void 0) throw configError(`PermDock CLI: rls.assignments.ownRole names ${name}, which has no user column: set its user in rls.assignments.tables`);
|
|
568
|
+
out.add(qualifySqlTable(entry.table));
|
|
569
|
+
}
|
|
570
|
+
return out;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* The tables `rls.assignments` guards, schema-qualified: each scope's mapped
|
|
574
|
+
* membership table, the global-roles table (`rls.roles`, else
|
|
575
|
+
* `supabase.hook.roles`) and `rls.assignments.tables`. Empty without
|
|
576
|
+
* `rls.assignments`.
|
|
577
|
+
*/
|
|
578
|
+
function guardedTables(config, scopes) {
|
|
579
|
+
const rls = config.rls;
|
|
580
|
+
if (rls?.assignments === void 0) return [];
|
|
581
|
+
const hook = config.supabase?.hook?.roles;
|
|
582
|
+
const roles = rls.roles ?? (hook === false ? void 0 : hook);
|
|
583
|
+
const tables = [
|
|
584
|
+
...scopes.flatMap((scope) => {
|
|
585
|
+
const mapped = memberForTable({
|
|
586
|
+
scopes,
|
|
587
|
+
...rls.memberships === void 0 ? {} : { memberships: rls.memberships }
|
|
588
|
+
}, scope.name);
|
|
589
|
+
return mapped === void 0 ? [] : [mapped.table];
|
|
590
|
+
}),
|
|
591
|
+
...roles === void 0 ? [] : [roles.table],
|
|
592
|
+
...(rls.assignments === true ? [] : rls.assignments.tables ?? []).map((entry) => entry.table)
|
|
593
|
+
];
|
|
594
|
+
return [...new Set(tables.map(qualifySqlTable))];
|
|
595
|
+
}
|
|
596
|
+
function isRoleList(role) {
|
|
597
|
+
return Array.isArray(role);
|
|
598
|
+
}
|
|
599
|
+
/** The role keys a row variable holds, one scalar per role source. */
|
|
600
|
+
function rowRoles(entry, row) {
|
|
601
|
+
return (isRoleList(entry.role) ? entry.role : [entry.role]).map((part) => roleColumn(part, qualifySqlTable(entry.table), row, {
|
|
602
|
+
label: `rls.assignments ${entry.table} role`,
|
|
603
|
+
prefix: "PermDock CLI"
|
|
604
|
+
}).lookup);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* `permdock_can_assign_custom_role`: whether the signed-in caller may assign a
|
|
608
|
+
* custom role of the tenant at the instance, by the checks the custom-role
|
|
609
|
+
* write functions run on its stored definition.
|
|
610
|
+
*/
|
|
611
|
+
function canAssignCustomSql(ctx, forUser = false) {
|
|
612
|
+
const tenantType = tenantTypeOf(ctx);
|
|
613
|
+
const fn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
|
|
614
|
+
const perms = qualified(ctx, CUSTOM_ROLES.permissions);
|
|
615
|
+
const includes = qualified(ctx, CUSTOM_ROLES.includes);
|
|
616
|
+
const guard = qualified(ctx, forUser ? CUSTOM_ROLES.guardFor : CUSTOM_ROLES.guard);
|
|
617
|
+
const userParam = forUser ? "p_user uuid, " : "";
|
|
618
|
+
const userArg = forUser ? "uuid, " : "";
|
|
619
|
+
const stored = `${storedTenant(ctx)} and c.scope = p_scope and c.role = p_role and (c.scope_id is null or c.scope_id = p_scope_id)`;
|
|
620
|
+
const grants = forUser ? `revoke execute on function ${fn}(${userArg}${tenantType}, text, text, text) from public, anon, authenticated;` : `revoke execute on function ${fn}(${tenantType}, text, text, text) from public, anon;
|
|
621
|
+
grant execute on function ${fn}(${tenantType}, text, text, text) to authenticated;`;
|
|
622
|
+
return `-- whether ${forUser ? "user p_user" : "the caller"} may assign custom role p_role at instance p_scope_id (a platform role: null tenant, scope global): the custom-role write checks on its stored definition${forUser ? "; no client role may execute it" : ""}
|
|
623
|
+
create or replace function ${fn}(${userParam}p_tenant ${tenantType}, p_scope text, p_scope_id text, p_role text)
|
|
624
|
+
returns boolean
|
|
625
|
+
language plpgsql
|
|
626
|
+
stable
|
|
627
|
+
security definer
|
|
628
|
+
set search_path = ''
|
|
629
|
+
as $$
|
|
630
|
+
declare
|
|
631
|
+
v_scope_id text;
|
|
632
|
+
v_found boolean := false;
|
|
633
|
+
begin
|
|
634
|
+
select true, c.scope_id into v_found, v_scope_id
|
|
635
|
+
from (
|
|
636
|
+
select c.scope_id from ${perms} c where ${stored}
|
|
637
|
+
union all
|
|
638
|
+
select c.scope_id from ${includes} c where ${stored}
|
|
639
|
+
) c
|
|
640
|
+
order by c.scope_id nulls first
|
|
641
|
+
limit 1;
|
|
642
|
+
if not coalesce(v_found, false) then
|
|
643
|
+
return false;
|
|
644
|
+
end if;
|
|
645
|
+
perform ${guard}(${forUser ? "p_user, " : ""}p_tenant, p_scope, v_scope_id, p_role);
|
|
646
|
+
return true;
|
|
647
|
+
exception
|
|
648
|
+
when insufficient_privilege or invalid_parameter_value then
|
|
649
|
+
return false;
|
|
650
|
+
end;
|
|
651
|
+
$$;
|
|
652
|
+
${grants}`;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* The assignment triggers (\`rls.assignments\`): before a client role inserts,
|
|
656
|
+
* updates or deletes a row that assigns a role, every role the old and new row
|
|
657
|
+
* hold must be one the caller may assign at that instance, by
|
|
658
|
+
* \`permdock_can_assign\` for a declared role and
|
|
659
|
+
* \`permdock_can_assign_custom_role\` for a custom one. The functions run as
|
|
660
|
+
* the invoker, so \`current_user\` tells a client write from a trusted one.
|
|
661
|
+
*/
|
|
662
|
+
function assignmentSql(ctx) {
|
|
663
|
+
const own = ctx.ownership;
|
|
664
|
+
if (ctx.assignments === void 0 || own === void 0 || own.assigns.length === 0) return "";
|
|
665
|
+
const canAssign = qualified(ctx, OWNERSHIP.canAssign);
|
|
666
|
+
const custom = ctx.customRoles !== void 0 && ctx.authorize === "database";
|
|
667
|
+
const declared = ctx.customRoles?.declared;
|
|
668
|
+
const tenantType = tenantTypeOf(ctx);
|
|
669
|
+
const chunks = [];
|
|
670
|
+
const clients = `current_user::text = any(array[${CLIENT_ROLES.map(quoteLiteral).join(", ")}])`;
|
|
671
|
+
const tables = assignedTables(ctx);
|
|
672
|
+
const ownRole = ownRoleTables(ctx, tables);
|
|
673
|
+
for (const entry of tables) {
|
|
674
|
+
const table = quoteQualified(entry.table);
|
|
675
|
+
const suffix = entry.table.replaceAll(/[^a-z0-9_]/gu, "_");
|
|
676
|
+
const fn = qualified(ctx, quoteIdent(`${ASSIGNMENTS.trigger}_${suffix}`));
|
|
677
|
+
const ownUser = entry.user !== void 0 && ownRole.has(qualifySqlTable(entry.table)) ? entry.user : void 0;
|
|
678
|
+
const ownCheck = (row, reason) => ownUser === void 0 ? "" : ` if ${row}.${quoteIdent(ownUser)}::text = (${subjectIdSql(ctx)})::text then
|
|
679
|
+
raise exception using
|
|
680
|
+
errcode = '42501',
|
|
681
|
+
message = 'permdock: the caller may not change their own roles',
|
|
682
|
+
hint = '${reason}';
|
|
683
|
+
end if;
|
|
684
|
+
`;
|
|
685
|
+
const check = (row) => {
|
|
686
|
+
const id = entry.id === void 0 ? "null::text" : `${row}.${quoteIdent(entry.id)}::text`;
|
|
687
|
+
const customFn = qualified(ctx, ASSIGNMENTS.custom);
|
|
688
|
+
let allowed = `${canAssign}(v_role, ${id})`;
|
|
689
|
+
if (custom && declared !== void 0) {
|
|
690
|
+
const platform = `${customFn}(null, 'global', null, v_role)`;
|
|
691
|
+
allowed = entry.id === void 0 || entry.tenant === void 0 ? `case when v_role = any(${declaredArray(declared)}) then ${canAssign}(v_role, ${id}) else ${platform} end` : `case when v_role = any(${declaredArray(declared)}) then ${canAssign}(v_role, ${id}) when ${id} is null then ${platform} else ${customFn}(${row}.${quoteIdent(entry.tenant)}::${tenantType}, ${quoteLiteral(entry.scope)}, ${id}, v_role) end`;
|
|
692
|
+
}
|
|
693
|
+
return ` foreach v_role in array array[${rowRoles(entry, row).join(", ")}]::text[] loop
|
|
694
|
+
if v_role is not null and not coalesce(${allowed}, false) then
|
|
695
|
+
raise exception using
|
|
696
|
+
errcode = '42501',
|
|
697
|
+
message = 'permdock: the caller may not assign ' || v_role || ' in ' || coalesce(${id}, 'null'),
|
|
698
|
+
hint = 'not-assignable-by';
|
|
699
|
+
end if;
|
|
700
|
+
end loop;`;
|
|
701
|
+
};
|
|
702
|
+
const what = entry.id === void 0 ? "global roles" : `${entry.scope} roles`;
|
|
703
|
+
chunks.push(`-- ${entry.table}: a client role may write only the ${what} it may assign${ownUser === void 0 ? "" : ", and none on its own rows"}
|
|
704
|
+
create or replace function ${fn}()
|
|
705
|
+
returns trigger
|
|
706
|
+
language plpgsql
|
|
707
|
+
volatile
|
|
708
|
+
set search_path = ''
|
|
709
|
+
as $$
|
|
710
|
+
declare
|
|
711
|
+
v_role text;
|
|
712
|
+
begin
|
|
713
|
+
if not (${clients}) then
|
|
714
|
+
return coalesce(new, old);
|
|
715
|
+
end if;
|
|
716
|
+
if tg_op in ('UPDATE', 'DELETE') then
|
|
717
|
+
${ownCheck("old", "self-demotion")}${check("old")}
|
|
718
|
+
end if;
|
|
719
|
+
if tg_op in ('INSERT', 'UPDATE') then
|
|
720
|
+
${ownCheck("new", "not-assignable-by")}${check("new")}
|
|
721
|
+
end if;
|
|
722
|
+
return coalesce(new, old);
|
|
723
|
+
end;
|
|
724
|
+
$$;
|
|
725
|
+
revoke execute on function ${fn}() from public, anon, authenticated;
|
|
726
|
+
drop trigger if exists "permdock_assignment" on ${table};
|
|
727
|
+
create trigger "permdock_assignment"
|
|
728
|
+
before insert or update or delete on ${table}
|
|
729
|
+
for each row execute function ${fn}();`);
|
|
730
|
+
}
|
|
731
|
+
return `${chunks.join("\n\n")}\n`;
|
|
732
|
+
}
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/cli/rls/can-assign.ts
|
|
735
|
+
/**
|
|
736
|
+
* Whether the signed-in user (or `p_user` in the `_for` form) holds, in
|
|
737
|
+
* instance `p_scope_id`, a role whose `assigns` lists `p_role`.
|
|
738
|
+
*/
|
|
739
|
+
function canAssignSql(base, own, forUser = false) {
|
|
740
|
+
const ctx = forUser ? {
|
|
741
|
+
...base,
|
|
742
|
+
subjectId: "p_user",
|
|
743
|
+
tenants: "all"
|
|
744
|
+
} : base;
|
|
745
|
+
const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
|
|
746
|
+
const parts = [];
|
|
747
|
+
const used = [];
|
|
748
|
+
const userOf = (source) => {
|
|
749
|
+
const index = used.includes(source) ? used.indexOf(source) : used.push(source) - 1;
|
|
750
|
+
return `v_user_${String(index)}`;
|
|
751
|
+
};
|
|
752
|
+
const global = own.assigns.filter((pair) => pair.scope === "global");
|
|
753
|
+
for (const [instance, pairs] of [["p_scope_id is not null", global.filter((pair) => pair.at !== "global")], ["p_scope_id is null", global.filter((pair) => pair.at === "global")]]) {
|
|
754
|
+
if (pairs.length === 0) continue;
|
|
755
|
+
if (ctx.authorize === "database") {
|
|
756
|
+
const ur = globalRoleRows(ctx);
|
|
757
|
+
const kind = globalKindFilterSql(ctx, ur.roleSql);
|
|
758
|
+
parts.push(`${instance} and exists (
|
|
759
|
+
select 1 from ${ur.from}
|
|
760
|
+
where ${ur.userSql} = ${subjectIdSql(ctx)}
|
|
761
|
+
and (${ur.roleSql}, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}
|
|
762
|
+
)`);
|
|
763
|
+
} else {
|
|
764
|
+
const kind = globalKindFilterSql(ctx, "r.role");
|
|
765
|
+
parts.push(`${instance} and exists (
|
|
766
|
+
select 1 from ${roleRows(ctx)}
|
|
767
|
+
where (r.role, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}
|
|
768
|
+
)`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
for (const { name } of ctx.scopes) {
|
|
772
|
+
const pairs = own.assigns.filter((pair) => pair.scope === name);
|
|
773
|
+
if (pairs.length === 0) continue;
|
|
774
|
+
const sources = scopeSources(ctx, name);
|
|
775
|
+
if (sources.length > 0) {
|
|
776
|
+
const kind = kindFilterSql(ctx, "r.role", "ms.via");
|
|
777
|
+
parts.push(`exists (
|
|
778
|
+
select 1
|
|
779
|
+
from (
|
|
780
|
+
${sourceRowsSql(sources, userOf)}
|
|
781
|
+
) ms
|
|
782
|
+
cross join lateral jsonb_array_elements_text(
|
|
783
|
+
case jsonb_typeof(ms.roles) when 'array' then ms.roles else '[]'::jsonb end
|
|
784
|
+
) r(role)
|
|
785
|
+
where ms.scope = ${quoteLiteral(name)}
|
|
786
|
+
and ms.id = p_scope_id
|
|
787
|
+
and (r.role, p_role) in (${pairsSql(pairs)})${kind === void 0 ? "" : `\n and ${kind}`}${sourceFilters(ctx, name, void 0, void 0, sourcesKeep(sources)).replaceAll("\n and ", "\n and ")}
|
|
788
|
+
)`);
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (ctx.authorize === "database") {
|
|
792
|
+
const mapped = scopeTable(ctx, name);
|
|
793
|
+
if (mapped === void 0) continue;
|
|
794
|
+
const { table, column } = mapped;
|
|
795
|
+
const role = memberRoleOf(table, "m", " ");
|
|
796
|
+
const filters = [
|
|
797
|
+
`${memberColumn(table.user)} = ${subjectIdSql(ctx)}`,
|
|
798
|
+
`${memberColumn(column)}::text = p_scope_id`,
|
|
799
|
+
`(${role.sql}, p_role) in (${pairsSql(pairs)})`
|
|
800
|
+
];
|
|
801
|
+
if (table.expiresAt !== void 0) {
|
|
802
|
+
const expires = memberColumn(table.expiresAt);
|
|
803
|
+
filters.push(`(${expires} is null or ${expires} > now())`);
|
|
804
|
+
}
|
|
805
|
+
const disabled = disabledColumn(table);
|
|
806
|
+
if (disabled !== void 0) filters.push(`${disabled} is null`);
|
|
807
|
+
const kind = kindFilterSql(ctx, role.sql, memberVia(table));
|
|
808
|
+
if (kind !== void 0) filters.push(kind);
|
|
809
|
+
parts.push(`exists (
|
|
810
|
+
select 1 from ${quoteQualified(table.table)} m${role.join}
|
|
811
|
+
where ${filters.join("\n and ")}
|
|
812
|
+
)`);
|
|
813
|
+
} else {
|
|
814
|
+
const kind = kindFilterSql(ctx, "r.role", "m ->> 'via'");
|
|
815
|
+
parts.push(`exists (
|
|
816
|
+
select 1 from ${membershipRows(ctx)}
|
|
817
|
+
where m ->> 'scope' = ${quoteLiteral(name)}
|
|
818
|
+
and m ->> 'id' = p_scope_id
|
|
819
|
+
and (r.role, p_role) in (${pairsSql(pairs)})
|
|
820
|
+
and case jsonb_typeof(m -> 'expires_at')
|
|
821
|
+
when 'number' then (m ->> 'expires_at')::numeric > extract(epoch from now())
|
|
822
|
+
else true
|
|
823
|
+
end${kind === void 0 ? "" : `\n and ${kind}`}${claimKeptLines(ctx, " ").map((line) => `\n${line}`).join("")}
|
|
824
|
+
)`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const body = parts.length === 0 ? "false" : parts.join("\n or ");
|
|
828
|
+
const language = used.length === 0 ? `language sql
|
|
829
|
+
stable
|
|
830
|
+
security definer
|
|
831
|
+
set search_path = ''
|
|
832
|
+
as $$
|
|
833
|
+
select ${signedIn(ctx)} and (
|
|
834
|
+
${body}
|
|
835
|
+
)
|
|
836
|
+
$$;` : `language plpgsql
|
|
837
|
+
stable
|
|
838
|
+
security definer
|
|
839
|
+
set search_path = ''
|
|
840
|
+
as $$
|
|
841
|
+
declare
|
|
842
|
+
${used.map((source, index) => ` v_user_${String(index)} ${source.sql.userType} := ${subjectIdSql(ctx)};`).join("\n")}
|
|
843
|
+
begin
|
|
844
|
+
return ${signedIn(ctx)} and (
|
|
845
|
+
${body}
|
|
846
|
+
);
|
|
847
|
+
end;
|
|
848
|
+
$$;`;
|
|
849
|
+
if (forUser) {
|
|
850
|
+
const user = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
851
|
+
return `-- who may assign, for a user the caller names: trusted SQL acting later for a stored user; no client role may execute it
|
|
852
|
+
create or replace function ${fn}(p_user ${user}, p_role text, p_scope_id text)
|
|
853
|
+
returns boolean
|
|
854
|
+
${language}
|
|
855
|
+
revoke execute on function ${fn}(${user}, text, text) from public, anon, authenticated;`;
|
|
856
|
+
}
|
|
857
|
+
return `-- who may assign: a held role whose assigns lists p_role, in instance p_scope_id (its scope or an ancestor's); a null p_scope_id assigns a global role, which only global assigners may
|
|
858
|
+
create or replace function ${fn}(p_role text, p_scope_id text)
|
|
859
|
+
returns boolean
|
|
860
|
+
${language}
|
|
861
|
+
revoke execute on function ${fn}(text, text) from public, anon;
|
|
862
|
+
grant execute on function ${fn}(text, text) to authenticated;`;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Whether the helpers keep custom roles in tables, so assigning one is checked
|
|
866
|
+
* against its stored definition.
|
|
867
|
+
*/
|
|
868
|
+
function assignsCustomRoles(ctx) {
|
|
869
|
+
return ctx.customRoles !== void 0 && ctx.authorize === "database";
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Whether `permdock_can_assign_any_for` is written: every function it calls has
|
|
873
|
+
* a `_for` form.
|
|
874
|
+
*/
|
|
875
|
+
function hasCanAssignAnyFor(ctx) {
|
|
876
|
+
return ctx.authorize === "database" && (!assignsCustomRoles(ctx) || hasCustomRoleChecksFor(ctx));
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* `permdock_can_assign_any`: one check for a declared or a custom role, so SQL
|
|
880
|
+
* that assigns roles calls one function: `permdock_can_assign` for a declared
|
|
881
|
+
* role, `permdock_can_assign_custom_role` for a custom one. `p_scope`
|
|
882
|
+
* `'global'` (no tenant, no instance) assigns a platform role.
|
|
883
|
+
*/
|
|
884
|
+
function canAssignAnySql(ctx, custom, forUser = false) {
|
|
885
|
+
const tenantType = tenantTypeOf(ctx);
|
|
886
|
+
const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignAnyFor : OWNERSHIP.canAssignAny);
|
|
887
|
+
const declaredFn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
|
|
888
|
+
const customFn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
|
|
889
|
+
const userType = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
890
|
+
const userParam = forUser ? `p_user ${userType}, ` : "";
|
|
891
|
+
const userArg = forUser ? `${userType}, ` : "";
|
|
892
|
+
const user = forUser ? "p_user, " : "";
|
|
893
|
+
const declared = `${declaredFn}(${user}p_role, case when p_scope = 'global' then null else p_scope_id end)`;
|
|
894
|
+
const body = custom && ctx.customRoles !== void 0 ? `case
|
|
895
|
+
when p_role = any(${declaredArray(ctx.customRoles.declared)}) then ${declared}
|
|
896
|
+
when p_scope = 'global' then ${customFn}(${user}null, 'global', null, p_role)
|
|
897
|
+
else ${customFn}(${user}p_tenant, p_scope, p_scope_id, p_role)
|
|
898
|
+
end` : declared;
|
|
899
|
+
const grants = forUser ? `revoke execute on function ${fn}(${userArg}text, ${tenantType}, text, text) from public, anon, authenticated;` : `revoke execute on function ${fn}(text, ${tenantType}, text, text) from public, anon;
|
|
900
|
+
grant execute on function ${fn}(text, ${tenantType}, text, text) to authenticated;`;
|
|
901
|
+
return `-- whether ${forUser ? "user p_user" : "the caller"} may assign p_role at instance p_scope_id of p_scope in tenant p_tenant, declared or custom (p_scope 'global' for a platform role)${forUser ? "; no client role may execute it" : ""}
|
|
902
|
+
create or replace function ${fn}(${userParam}p_role text, p_tenant ${tenantType}, p_scope text, p_scope_id text)
|
|
903
|
+
returns boolean
|
|
904
|
+
language sql
|
|
905
|
+
stable
|
|
906
|
+
security definer
|
|
907
|
+
set search_path = ''
|
|
908
|
+
as $$
|
|
909
|
+
select coalesce(${body}, false)
|
|
910
|
+
$$;
|
|
911
|
+
${grants}`;
|
|
912
|
+
}
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/cli/rls/ownership.ts
|
|
915
|
+
function roleScope(policy, scopes, name) {
|
|
916
|
+
const on = policy.rolesByName.get(name)?.on ?? findRole(policy.vocabulary.roles, name)?.on;
|
|
917
|
+
return typeof on === "string" ? resolveScope(scopes, on) : void 0;
|
|
918
|
+
}
|
|
919
|
+
function isGlobalRole(policy, name) {
|
|
920
|
+
const binding = policy.rolesByName.get(name);
|
|
921
|
+
if (binding !== void 0) return binding.on === void 0;
|
|
922
|
+
const leaf = findRole(policy.vocabulary.roles, name);
|
|
923
|
+
return leaf !== void 0 && leaf.on === void 0;
|
|
924
|
+
}
|
|
925
|
+
/** The rules `rls generate` enforces; `undefined` when no role declares one. */
|
|
926
|
+
function ownershipRules(policy, scopes) {
|
|
927
|
+
const kinds = {};
|
|
928
|
+
const assigns = [];
|
|
929
|
+
const counted = [];
|
|
930
|
+
for (const binding of policy.roles) {
|
|
931
|
+
const scope = roleScope(policy, scopes, binding.name);
|
|
932
|
+
if (binding.for !== void 0) kinds[binding.name] = binding.for;
|
|
933
|
+
const assigner = scope ?? (binding.on === void 0 ? "global" : void 0);
|
|
934
|
+
for (const target of binding.assigns ?? []) {
|
|
935
|
+
const at = roleScope(policy, scopes, target);
|
|
936
|
+
if (assigner !== void 0 && at !== void 0 && (assigner === "global" || scopeChain(scopes, at).includes(assigner))) assigns.push({
|
|
937
|
+
assigner: binding.name,
|
|
938
|
+
scope: assigner,
|
|
939
|
+
role: target,
|
|
940
|
+
at
|
|
941
|
+
});
|
|
942
|
+
else if (assigner === "global" && isGlobalRole(policy, target)) assigns.push({
|
|
943
|
+
assigner: binding.name,
|
|
944
|
+
scope: "global",
|
|
945
|
+
role: target,
|
|
946
|
+
at: "global"
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
const min = binding.min ?? 0;
|
|
950
|
+
const transferOnly = binding.transferOnly === true;
|
|
951
|
+
if (scope !== void 0 && (min > 0 || binding.max !== void 0 || transferOnly)) counted.push(binding.max === void 0 ? {
|
|
952
|
+
role: binding.name,
|
|
953
|
+
scope,
|
|
954
|
+
min,
|
|
955
|
+
transferOnly
|
|
956
|
+
} : {
|
|
957
|
+
role: binding.name,
|
|
958
|
+
scope,
|
|
959
|
+
min,
|
|
960
|
+
max: binding.max,
|
|
961
|
+
transferOnly
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
if (Object.keys(kinds).length === 0 && assigns.length === 0 && counted.length === 0) return;
|
|
965
|
+
return {
|
|
966
|
+
kinds,
|
|
967
|
+
assigns,
|
|
968
|
+
counted
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* The roles whose `for` lists kinds that no membership source of the role's
|
|
973
|
+
* scope can hold: such a role resolves to no one. A `via` read from a column
|
|
974
|
+
* can hold any kind, and so does a source with no `via` (it is not compared)
|
|
975
|
+
* and a scope with no table or source.
|
|
976
|
+
*/
|
|
977
|
+
function unsatisfiedKinds(ctx, policy) {
|
|
978
|
+
const out = [];
|
|
979
|
+
for (const [role, kinds] of Object.entries(ctx.ownership?.kinds ?? {})) {
|
|
980
|
+
const scope = roleScope(policy, ctx.scopes, role);
|
|
981
|
+
if (scope === void 0) continue;
|
|
982
|
+
const held = [];
|
|
983
|
+
const mapped = scopeTable(ctx, scope)?.table;
|
|
984
|
+
if (mapped === void 0) for (const source of ctx.memberSources ?? ctx.sources ?? []) {
|
|
985
|
+
if (source.sql.scope !== void 0 && source.sql.scope !== scope) continue;
|
|
986
|
+
const via = source.sql.manifest.via;
|
|
987
|
+
held.push({
|
|
988
|
+
label: source.sql.table,
|
|
989
|
+
via: via === void 0 ? void 0 : "value" in via && typeof via.value === "string" ? via.value : void 0
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
const via = memberVia(mapped);
|
|
994
|
+
held.push({
|
|
995
|
+
label: mapped.table,
|
|
996
|
+
via: typeof via === "string" ? void 0 : via.value ?? void 0
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
if (held.length === 0 || held.some((entry) => entry.via === void 0 || kinds.includes(entry.via ?? ""))) continue;
|
|
1000
|
+
out.push({
|
|
1001
|
+
role,
|
|
1002
|
+
for: kinds,
|
|
1003
|
+
sources: held.map((entry) => `${entry.label} (via ${entry.via ?? ""})`)
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
return out;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* The ownership objects: holder-count and transfer-only triggers on each
|
|
1010
|
+
* scope's membership table, and `permdock_can_assign` for the application's own
|
|
1011
|
+
* policies on those tables. Empty when no role declares a rule.
|
|
1012
|
+
*/
|
|
1013
|
+
function ownershipSql(ctx) {
|
|
1014
|
+
const own = ctx.ownership;
|
|
1015
|
+
if (own === void 0) return "";
|
|
1016
|
+
const chunks = [];
|
|
1017
|
+
const skipped = ctx.skipOwnershipTriggers;
|
|
1018
|
+
for (const { name } of ctx.scopes) {
|
|
1019
|
+
const counted = own.counted.filter((rule) => rule.scope === name);
|
|
1020
|
+
if (counted.length === 0) continue;
|
|
1021
|
+
if (skipped === "all" || skipped?.includes(name) === true) {
|
|
1022
|
+
chunks.push(`-- ${name}: rls.ownershipTriggers leaves out the triggers, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
const transfer = counted.filter((rule) => rule.transferOnly).map((rule) => rule.role);
|
|
1026
|
+
const mapped = scopeTable(ctx, name);
|
|
1027
|
+
if (mapped === void 0) {
|
|
1028
|
+
const sources = holderSources(ctx, name);
|
|
1029
|
+
if (sources === void 0) chunks.push(`-- ${name}: no memberships table configured, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
1030
|
+
else if (sources === "not-tables") chunks.push(`-- ${name}: a membership source is not a table, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
1031
|
+
else {
|
|
1032
|
+
chunks.push(sourceHoldersTriggerSql(ctx, name, sources, counted));
|
|
1033
|
+
if (transfer.length > 0) chunks.push(sourceTransferTriggerSql(ctx, name, sources, transfer));
|
|
1034
|
+
}
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
chunks.push(holdersTriggerSql(ctx, name, mapped.table, mapped.column, counted));
|
|
1038
|
+
if (transfer.length > 0) chunks.push(transferTriggerSql(ctx, name, mapped.table, mapped.column, transfer));
|
|
1039
|
+
}
|
|
1040
|
+
if (own.assigns.length > 0) {
|
|
1041
|
+
chunks.push(canAssignSql(ctx, own));
|
|
1042
|
+
if (ctx.authorize === "database") chunks.push(canAssignSql(ctx, own, true));
|
|
1043
|
+
const custom = assignsCustomRoles(ctx);
|
|
1044
|
+
if (custom) {
|
|
1045
|
+
chunks.push(canAssignCustomSql(ctx));
|
|
1046
|
+
if (hasCustomRoleChecksFor(ctx)) chunks.push(canAssignCustomSql(ctx, true));
|
|
1047
|
+
}
|
|
1048
|
+
chunks.push(canAssignAnySql(ctx, custom));
|
|
1049
|
+
if (hasCanAssignAnyFor(ctx)) chunks.push(canAssignAnySql(ctx, custom, true));
|
|
1050
|
+
}
|
|
1051
|
+
return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
|
|
1052
|
+
}
|
|
1053
|
+
//#endregion
|
|
1054
|
+
export { guardedTables as a, breakGlassKey as c, andConditions as d, compileConditionSql as f, assignmentSql as i, collectGrants as l, sqlFunctionNames as m, ownershipSql as n, assignableRoles as o, contextRefs as p, unsatisfiedKinds as r, breakGlassHolder as s, ownershipRules as t, roleNames as u };
|