permdock 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/index.d.ts +7 -5
- package/dist/a2a/index.js +14 -21
- package/dist/ai-sdk/index.d.ts +18 -6
- package/dist/ai-sdk/index.js +8 -5
- package/dist/app-data-CV8pqqMi.js +59 -0
- package/dist/approvals/index.d.ts +2 -2
- package/dist/approvals/index.js +3 -3
- package/dist/{approvals-DbYAuNkG.js → approvals-DZL76lKW.js} +35 -19
- package/dist/{approvers-DT4AVyIg.js → approvers-CqPOLD-o.js} +1 -1
- package/dist/{arazzo-BAgw9ap6.js → arazzo-C4BcskbX.js} +6 -5
- package/dist/{arazzo-CKwwqFWz.js → arazzo-CD8d0WLb.js} +2 -2
- package/dist/authzen/index.d.ts +3 -3
- package/dist/authzen/index.js +161 -23
- package/dist/better-auth/index.d.ts +2 -2
- package/dist/better-auth/index.js +6 -19
- package/dist/better-supabase/index.d.ts +112 -87
- package/dist/better-supabase/index.js +257 -156
- package/dist/{bind-C_ob3LKa.d.ts → bind-BzqG8SMb.d.ts} +16 -5
- package/dist/{bind-CuzGytRf.js → bind-kk-uyCwH.js} +14 -6
- package/dist/build-instance-DIIV9DbC.js +51 -0
- package/dist/{canonical-json-DemiK4Vp.js → canonical-json-CFKyFOke.js} +1 -3
- package/dist/{capability-2kYTMWDv.d.ts → capability-CksyCWDH.d.ts} +2 -2
- package/dist/{capability-4qPdO9gk.js → capability-txATtKV4.js} +3 -2
- package/dist/catalog/index.d.ts +1 -1
- package/dist/catalog/index.js +1 -1
- package/dist/catalog-6yWAX6Ps.js +2 -0
- package/dist/{catalog-dFZdWCGN.js → catalog-WHG4kBq7.js} +2 -2
- package/dist/{catalog-doc-DdGm82XT.js → catalog-doc-DWtg0gGR.js} +17 -10
- package/dist/{catalog-DWF5cYqB.js → catalog-tZUyOfOv.js} +4 -4
- package/dist/claude-agent/index.d.ts +4 -4
- package/dist/claude-agent/index.js +4 -5
- package/dist/clerk/index.d.ts +3 -3
- package/dist/clerk/index.js +8 -33
- package/dist/cli/bin.js +1 -1
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cloud/index.d.ts +4 -4
- package/dist/cloud/index.js +9 -15
- package/dist/{cloud-CB_ELYuX.js → cloud-C7bSDgX4.js} +2 -2
- package/dist/{cloud-DWLXO2af.js → cloud-PeBN3IEn.js} +5 -5
- package/dist/{collect-DSTE3ml9.js → collect-2SrB7lfk.js} +6 -8
- package/dist/collect-Bw2Jdq1q.js +2 -0
- package/dist/{collect-DClJsheJ.js → collect-CQx4hX4N.js} +3 -3
- package/dist/{commands-DSxCqG9A.js → commands-Cpip4ULx.js} +13 -13
- package/dist/{compact-y6vfu2_f.js → compact-CAf557ZB.js} +11 -1
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -1
- package/dist/{compile-Ba4blROR.js → compile-CcEu2ZQ1.js} +6 -5
- package/dist/{compile-BkRyBDBw.d.ts → compile-CtTP7eur.d.ts} +2 -2
- package/dist/{config-BJiM-xAm.js → config-BIjREpyE.js} +4 -4
- package/dist/{config-Dnszbv-6.js → config-DLt6BvV7.js} +2 -2
- package/dist/{config-report-DMvWKUxL.js → config-report--Xb9APJS.js} +5 -6
- package/dist/{connection-CU8MQxRi.js → connection-DUsczpB5.js} +3 -2
- package/dist/constants-DcJABbLW.js +20 -0
- package/dist/{context-Bcy85rGi.js → context-DieZVJXu.js} +16 -5
- package/dist/convex/index.d.ts +4 -4
- package/dist/convex/index.js +14 -16
- package/dist/create-BBO24vj6.js +324 -0
- package/dist/{credential-8P81ff4u.js → credential-kue0qpbm.js} +4 -4
- package/dist/deciding-columns-Sq12mEA6.js +123 -0
- package/dist/delegation-input-FTxMK3ji.js +19 -0
- package/dist/denied-text-DfRk15uw.d.ts +19 -0
- package/dist/{describe-DMYiCjJV.d.ts → describe-C3ldA9os.d.ts} +11 -2
- package/dist/{describe-B_gVZEVi.js → describe-Cgkp7eP0.js} +5 -4
- package/dist/{diff-DBIhz_f2.js → diff-Cnghh_Ip.js} +2 -2
- package/dist/{diff-2VaeChzX.js → diff-rpqoe7Ag.js} +9 -9
- package/dist/{doctor-BwCAMLx_.js → doctor-CuVJvhV_.js} +2 -2
- package/dist/doctor-owlv6ddh.js +2166 -0
- package/dist/drizzle/index.d.ts +2 -2
- package/dist/drizzle/index.js +6 -14
- package/dist/elysia/index.d.ts +4 -4
- package/dist/elysia/index.js +7 -7
- package/dist/{errors-DPdqI50v.d.ts → errors-OD_4c__H.d.ts} +2 -2
- package/dist/{errors-Cz5zn0mW.js → errors-drnfxYtm.js} +3 -3
- package/dist/{evaluations-tz6D9A2G.js → evaluations-C9YJsv3H.js} +45 -7
- package/dist/eve/index.d.ts +5 -5
- package/dist/eve/index.js +5 -5
- package/dist/express/index.d.ts +4 -4
- package/dist/express/index.js +8 -7
- package/dist/fastify/index.d.ts +15 -5
- package/dist/fastify/index.js +9 -29
- package/dist/fields-fwfXN01n.js +1016 -0
- package/dist/{files-DWUN9PzI.js → files-D_JIzUrP.js} +12 -3
- package/dist/{fixtures-ht28Cplr.js → fixtures-h2eG2OZV.js} +5 -6
- package/dist/{from-snapshot-CTLijvdm.js → from-snapshot-7REQZe0H.js} +32 -22
- package/dist/{generate-CH7ThJ14.js → generate-CNU1pIOo.js} +1 -3
- package/dist/grantee-CXY-0C87.js +254 -0
- package/dist/grantee-conditions-MQs5NIuX.js +270 -0
- package/dist/grantee-match-fxYp9Nmt.js +141 -0
- package/dist/{helpers-H_KARDVC.js → helpers-C9YmyBIa.js} +7 -6
- package/dist/hono/index.d.ts +4 -4
- package/dist/hono/index.js +8 -8
- package/dist/hosted-CBHgkzDl.js +655 -0
- package/dist/{http-SO7qGttf.js → http-BBoKefeS.js} +35 -2
- package/dist/{http-BIhmLfCc.js → http-BEbdMZ05.js} +2 -24
- package/dist/{rls-import-BTFVk3jH.js → import-BFIlhetD.js} +86 -80
- package/dist/{openapi-import-cmwetbzw.js → import-UHk1N7vH.js} +6 -7
- package/dist/{index-C8z4RjQV.d.ts → index-BwMe94Xq.d.ts} +1 -1
- package/dist/{index-0nNqHspG.d.ts → index-DgM7w96K.d.ts} +2 -2
- package/dist/{index-C7Tw7MaW.d.ts → index-Dz7didpC.d.ts} +3 -3
- package/dist/{index-BNc7Wer_.d.ts → index-UdVQgq48.d.ts} +105 -38
- package/dist/index.d.ts +15 -13
- package/dist/index.js +22 -22
- package/dist/{instance-options-jARdwfPN.d.ts → instance-options-plkrsAog.d.ts} +1 -1
- package/dist/{json-schema-BsAflUGW.js → json-schema-DBkdUuCO.js} +1 -3
- package/dist/jwt/index.d.ts +4 -4
- package/dist/jwt/index.js +13 -13
- package/dist/{kernel-_RRHmnMY.js → kernel-CB3LrIJM.js} +81 -32
- package/dist/kysely/index.d.ts +2 -2
- package/dist/kysely/index.js +6 -14
- package/dist/{live-options-H2eG8dfJ.js → live-options-BaAFCIiu.js} +16 -3
- package/dist/load-B_shHrmd.js +2 -0
- package/dist/{load-DhWEB9P1.js → load-C3_oTHyO.js} +11 -3
- package/dist/{manifest-iaoBXHKc.js → manifest-BngWKryL.js} +5 -18
- package/dist/{map-claims-Z4QZOiFF.js → map-claims-AVlBaJoa.js} +7 -16
- package/dist/{map-error-pgUWNNtG.js → map-error-BE27PYkE.js} +5 -5
- package/dist/{markers-B0hYLvMD.js → markers-CiwAOZtO.js} +6 -2
- package/dist/{may-use-DndQ5tQ-.js → may-use-BOn_dR_w.js} +4 -3
- package/dist/mcp/index.d.ts +26 -9
- package/dist/mcp/index.js +456 -446
- package/dist/{rls-migrate-CN4TlJTO.js → migrate-BwlB5-Wk.js} +75 -15
- package/dist/nest/index.d.ts +4 -4
- package/dist/nest/index.js +11 -12
- package/dist/next/index.d.ts +31 -8
- package/dist/next/index.js +29 -33
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +1 -1
- package/dist/node/index.d.ts +4 -4
- package/dist/node/index.js +4 -4
- package/dist/ocsf-uYFXGVw2.js +115 -0
- package/dist/openai/index.d.ts +5 -5
- package/dist/openai/index.js +4 -4
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-CqqEbHsL.js → openapi-BJdBmYDG.js} +19 -23
- package/dist/{openapi-BqRBvAng.js → openapi-DBzHQbov.js} +2 -5
- package/dist/{openapi-l0xYHkcv.js → openapi-DcQ1Kg40.js} +3 -3
- package/dist/orpc/index.d.ts +5 -5
- package/dist/orpc/index.js +9 -11
- package/dist/otel/index.d.ts +2 -2
- package/dist/otel/index.js +26 -23
- package/dist/output-files-CgIsvdSa.js +140 -0
- package/dist/{rls-ownership-RuFqaqGz.js → ownership-BgnbOJFT.js} +387 -379
- package/dist/{package-root-zGkxO3oN.js → package-root-DtNnpmUd.js} +3 -2
- package/dist/{parse-BuyjxhZV.js → parse-CVFQHk6c.js} +3 -3
- package/dist/{parse-local-manifest-BrMozJCo.js → parse-local-manifest-C-q02Pal.js} +2 -4
- package/dist/{parse-local-manifest-JLnaBH0S.d.ts → parse-local-manifest-DvYUR0QL.d.ts} +1 -1
- package/dist/pdp/index.d.ts +2 -2
- package/dist/pdp/index.js +8 -9
- package/dist/{permdock-CbgvLnuo.js → permdock-L26udHSk.js} +1387 -1220
- package/dist/{rls-fields-74oZPXWL.js → permission-keys-B26jeyUv.js} +1289 -2066
- package/dist/permission-registry-BsblpB-z.js +385 -0
- package/dist/{permissions-DttnqqxC.d.ts → permission-types-Dg39z9Kv.d.ts} +59 -31
- package/dist/{permissions-RJrovw98.js → permissions-CCJ6lmxB.js} +3 -3
- package/dist/{permissions-CDgvKG5L.d.ts → permissions-COH-KRgT.d.ts} +2 -2
- package/dist/{policy-IB6yxpC3.d.ts → policy-types-sp9P1iIV.d.ts} +661 -582
- package/dist/{policy-D2W7uv19.d.ts → policy-ur0BBK_e.d.ts} +2 -2
- package/dist/{powersync-DliZK_-y.js → powersync-BFSbSElQ.js} +2 -2
- package/dist/powersync-DCDgZgfZ.js +2 -0
- package/dist/{powersync-BylawD_U.js → powersync-cZe4kzhL.js} +26 -27
- package/dist/prisma/index.d.ts +1 -1
- package/dist/prisma/index.js +6 -12
- package/dist/{problem-B43ztKIm.js → problem-CnWraVhC.js} +8 -7
- package/dist/{problem-details-_AMOYz-w.js → problem-details-DECUnXAn.js} +2 -2
- package/dist/{problem-details-DX075l9V.d.ts → problem-details-rIIo8B2C.d.ts} +1 -1
- package/dist/{protected-BdnIEFkY.d.ts → protected-B3_P7xuw.d.ts} +9 -6
- package/dist/{protected-utU-SLuy.js → protected-BN7BUdY1.js} +20 -8
- package/dist/{provider-CSQd7m92.js → provider-Bi-0HNL3.js} +23 -10
- package/dist/{provider-DZY281jO.d.ts → provider-kwHC9CZD.d.ts} +1 -1
- package/dist/{provider-store-Cfbvo4DE.js → provider-store-Ctt-81Ni.js} +6 -3
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +5 -5
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -1
- package/dist/react-native/index.d.ts +20 -11
- package/dist/react-native/index.js +33 -17
- package/dist/{refs-KYb4ntaz.js → refs-f3imMjNW.js} +1 -1
- package/dist/relation-walk-BXnlLtg2.js +186 -0
- package/dist/relations-CvmSpG67.js +163 -0
- package/dist/{replay-CYyiLiwO.d.ts → replay-COpmZUfx.d.ts} +1 -1
- package/dist/request-context-BVM7aWNs.js +71 -0
- package/dist/request-context-C2_1lchh.d.ts +5 -0
- package/dist/resource-relations-D1Un3pcz.js +243 -0
- package/dist/retire-BFcxL6un.js +240 -0
- package/dist/{rls-B4ur8nKQ.js → rls-55DQTuf5.js} +14 -2
- package/dist/{rls-DQtdgTHB.js → rls-BEktUaqg.js} +884 -484
- package/dist/{policy-xyWg7QdR.js → role-builder-B4GbKM-i.js} +235 -541
- package/dist/{rpc-DL9Sqvb1.js → rpc-nYC3cR_u.js} +15 -5
- package/dist/{run-DKDbM4Du.js → run-DMcNW_8T.js} +3 -3
- package/dist/{runtime-CB8gQFvS.js → runtime-D_tMXnsq.js} +1 -1
- package/dist/{saas-DhpJjnBA.js → saas-DtaA4JuW.js} +9 -8
- package/dist/{openapi-schema--ARjyfQr.js → schema-Bol6gqXA.js} +3 -5
- package/dist/{schema-D3MnDMxa.js → schema-DjQBcAP9.js} +4 -2
- package/dist/scim/index.d.ts +1 -1
- package/dist/scim/index.js +1 -1
- package/dist/{scim-BFGBBAqR.js → scim-vFR1O5oG.js} +6 -12
- package/dist/{scopes-CVdPrWN-.js → scopes-tvpa34ep.js} +10 -2
- package/dist/server/index.d.ts +18 -8
- package/dist/server/index.js +8 -8
- package/dist/{server-DlZ28Oz5.js → server-ZYEHtNHS.js} +4 -4
- package/dist/{create-B1d519XK.d.ts → server-types-DdqBwZN_.d.ts} +69 -33
- package/dist/shared-1uhI-e-b.js +294 -0
- package/dist/{shared-DRu8VFVM.js → shared-JhMeOsDF.js} +1 -1
- package/dist/{sink-Bd9PSyfq.js → sink-BTtmfPnE.js} +1 -1
- package/dist/{sink-B1fZzIMf.d.ts → sink-CPN4zLfs.d.ts} +1 -1
- package/dist/{skills-Biq5uup3.js → skills-BZdRQkxs.js} +4 -3
- package/dist/{skills-Bx-U0irG.js → skills-Dle4ZoLF.js} +2 -2
- package/dist/{snapshot-Enxd0n-s.js → snapshot-Dc9CHPME.js} +116 -13
- package/dist/{snapshot-cache-D6jLelln.js → snapshot-cache-Cod40D-X.js} +1 -1
- package/dist/{snapshot-cache-B-LxUahj.d.ts → snapshot-cache-J5T7t_vD.d.ts} +1 -1
- package/dist/solid/index.d.ts +22 -22
- package/dist/solid/index.js +40 -10
- package/dist/{sources-4p0-4xQm.js → sources-B1-b-R0t.js} +25 -26
- package/dist/{sources-BdOYLw9j.d.ts → sources-Dljy06zL.d.ts} +6 -2
- package/dist/{sql-xdg5f4Fi.js → sql-t8teWOfo.js} +19 -3
- package/dist/{src-BDwEZWrZ.js → src-Bb7no-hS.js} +194 -123
- package/dist/ssf/index.d.ts +3 -3
- package/dist/ssf/index.js +58 -61
- package/dist/store-9KO4BlkB.d.ts +147 -0
- package/dist/{store-DMAWwAJV.js → store-ILkLUj_o.js} +4 -4
- package/dist/{store-options-ByhZq1jV.js → store-options-CbUHDoSe.js} +16 -7
- package/dist/{stores-BT2pSCub.js → stores-BHXdSbw9.js} +7 -3
- package/dist/stores-Cxz_80g-.d.ts +31 -0
- package/dist/{stream-DDpvynSB.js → stream-BMui98KK.js} +2 -1
- package/dist/{stream-D1RXubw2.d.ts → stream-Dwsg6WwE.d.ts} +2 -2
- package/dist/{subject-settings-BwBmCdTR.js → subject-settings-CkEwrw3W.js} +23 -5
- package/dist/supabase/index.d.ts +4 -4
- package/dist/supabase/index.js +3 -3
- package/dist/supabase/middleware.d.ts +11 -5
- package/dist/supabase/middleware.js +10 -7
- package/dist/{supabase-C0qESOa-.js → supabase-C1TWlnYt.js} +36 -52
- package/dist/{supabase-Boqb9a_Y.js → supabase-Dbqr141s.js} +2 -2
- package/dist/supabase-hook-BULg7Ut_.js +2 -0
- package/dist/{supabase-hook-CSLQ4zJ3.js → supabase-hook-Cva3ikXz.js} +731 -782
- package/dist/{supautils-Cn4R4cJ4.js → supautils-B6HYL9eL.js} +2 -2
- package/dist/svelte/Protected.svelte +9 -2
- package/dist/svelte/index.d.ts +6 -6
- package/dist/svelte/index.js +25 -11
- package/dist/svelte/runtime.d.ts +10 -3
- package/dist/svelte/runtime.js +4 -3
- package/dist/svelte/source.d.ts +6 -6
- package/dist/svelte/source.js +4 -4
- package/dist/{validity-uOp8UpYn.js → tenancy-DshjqY0n.js} +3 -39
- package/dist/terminal/index.d.ts +5 -5
- package/dist/terminal/index.js +18 -34
- package/dist/testing/index.d.ts +89 -77
- package/dist/testing/index.js +1276 -1262
- package/dist/testing/saas/index.d.ts +4 -4
- 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/thenable-QSmvt-vZ.js +65 -0
- package/dist/{token-CIWxJOCh.js → token-Bx9qy30p.js} +1 -1
- package/dist/tool-refusal-CQ7OEia9.js +41 -0
- package/dist/trpc/index.d.ts +5 -5
- package/dist/trpc/index.js +7 -8
- package/dist/{types-DbjRN55U.d.ts → types-7rJVLWe5.d.ts} +82 -4
- package/dist/{types-aqOHTHcp.d.ts → types-Br9IXUBG.d.ts} +1 -1
- package/dist/types-ByUo56un.d.ts +88 -0
- package/dist/{types-Cq_H8N_X.d.ts → types-C0dee0Qf.d.ts} +2 -2
- package/dist/{types-DxiCGmmd.d.ts → types-CIlTojhq.d.ts} +2 -2
- package/dist/{types-QCuYJh4q.d.ts → types-CUcFZfpU.d.ts} +1 -1
- package/dist/{types-C97IskBw.d.ts → types-CqrjB9o6.d.ts} +1 -1
- package/dist/{types-BnIEz1sU.d.ts → types-DHrmBJLH.d.ts} +24 -6
- package/dist/{types-Dnemwd77.d.ts → types-DpWfQdSh.d.ts} +5 -1
- package/dist/{types-Bvc4oWT6.d.ts → types-DvupZE0w.d.ts} +1 -1
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/{usage-GTixvDDU.js → usage-4G6t643F.js} +2 -2
- package/dist/{usage-ItS7nLcl.js → usage-CG9nOYZw.js} +7 -7
- package/dist/usage-iFLJ0-rr.js +2 -0
- package/dist/{validation-error-Ba264OaJ.d.ts → validation-error-BSSw0l_8.d.ts} +1 -1
- package/dist/{validation-error-CnjZ4t92.js → validation-error-ZfUHbKvf.js} +1 -1
- package/dist/validity-dVrUEPJt.js +62 -0
- package/dist/{verifier-BxBo6I44.js → verifier-DMr_Zmb_.js} +51 -15
- package/dist/{rls-verify-CVQTHTh5.js → verify-BztUB3GX.js} +668 -239
- package/dist/{version-BvacVTZR.js → version-BEogC-rB.js} +1 -1
- package/dist/{views-Soe7bGSC.js → views-CJsrBS1f.js} +23 -1
- package/dist/{vocabulary-DA8a8zif.d.ts → vocabulary-hhEFLFoL.d.ts} +15 -6
- package/dist/vue/index.d.ts +24 -19
- package/dist/vue/index.js +24 -8
- package/dist/watch-C8TpsAl4.js +2 -0
- package/dist/{watch-DVBJ9cBq.js → watch-zeucB7eF.js} +6 -6
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +13 -37
- package/dist/wrap-Cr93qqdP.d.ts +14 -0
- package/dist/wrap-DWsU_5Bd.js +26 -0
- package/package.json +8 -8
- package/schemas/catalog-v1.json +6 -0
- package/skills/permdock-credentials/SKILL.md +1 -1
- package/skills/permdock-credentials/references/tokens-keys-links.md +7 -2
- package/skills/permdock-data/references/queries-and-rls.md +4 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +1 -0
- package/skills/permdock-wire/SKILL.md +2 -2
- package/skills/permdock-wire/references/adapters.md +9 -1
- package/skills/permdock-wire/references/policy-options.md +10 -1
- package/dist/catalog-CRW5Mx_B.js +0 -2
- package/dist/collect-DSWOk19R.js +0 -2
- package/dist/create-CMwIl-w2.js +0 -253
- package/dist/delegation-D8pUEuce.js +0 -103
- package/dist/doctor-collect-CHk6XWOA.js +0 -665
- package/dist/doctor-igIiM_Hl.js +0 -1194
- package/dist/evaluation-items-CfCCejQd.js +0 -40
- package/dist/fields-BMl1RxoX.js +0 -26
- package/dist/grantee-Cv5Sogfv.js +0 -602
- package/dist/hosted-DJQw46hn.js +0 -266
- package/dist/instance-options-Dhhcnm3y.js +0 -22
- package/dist/load-DwJUafYN.js +0 -2
- package/dist/map-AcxK9o_D.js +0 -147
- package/dist/permissions-DVoF6dMt.js +0 -662
- package/dist/powersync-e_AZrKbg.js +0 -2
- package/dist/protected-xAr24FEC.d.ts +0 -40
- package/dist/relations-cjOL01hZ.js +0 -505
- package/dist/rls-graph-CHwx0V8q.js +0 -435
- package/dist/rls-shared-Cewd_-zI.js +0 -495
- package/dist/store-jOBYpiXC.d.ts +0 -36
- package/dist/stores-C0IMQrGI.d.ts +0 -52
- package/dist/supabase-config-BpPXt3x1.js +0 -472
- package/dist/supabase-hook-6sYLXg_u.js +0 -2
- package/dist/thenable-okc2xDsj.js +0 -10
- package/dist/types-CKNSyIf_.d.ts +0 -52
- package/dist/usage-DorAJiU_.js +0 -2
- package/dist/watch-BwxLTYMZ.js +0 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { l as scopeChain, o as resolveScope } from "./scopes-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { l as scopeChain, o as resolveScope } from "./scopes-tvpa34ep.js";
|
|
2
|
+
import { m as findRole } from "./grantee-CXY-0C87.js";
|
|
3
|
+
import { r as qualifySqlTable } from "./sql-t8teWOfo.js";
|
|
4
|
+
import { l as roleColumn } from "./sources-B1-b-R0t.js";
|
|
5
|
+
import { A as declaredArray, F as quoteQualified, M as qualified, N as quoteIdent, O as tenantTypeOf, P as quoteLiteral, f as kindFilterSql, h as roleKindSql, m as memberVia, s as signedIn, t as CUSTOM_ROLES, u as globalKindFilterSql, y as subjectIdSql } from "./shared-1uhI-e-b.js";
|
|
6
|
+
import { G as scopeSources, K as scopeTable, R as memberForSources, V as memberRoleOf, d as memberColumn, f as sourceFilters, g as roleRows, h as membershipRows, l as claimKeptLines, m as globalRoleRows, p as sourcesKeep, u as disabledColumn, y as hasCustomRoleChecksFor, z as memberForTable } from "./permission-keys-B26jeyUv.js";
|
|
7
|
+
//#region src/cli/rls/ownership-names.ts
|
|
7
8
|
/** Objects the ownership rules add next to the helpers. Names are part of the SQL contract. */
|
|
8
9
|
const OWNERSHIP = {
|
|
9
10
|
canAssign: "permdock_can_assign",
|
|
@@ -13,62 +14,234 @@ const OWNERSHIP = {
|
|
|
13
14
|
holders: "permdock_holders",
|
|
14
15
|
transferOnly: "permdock_transfer_only"
|
|
15
16
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
} : {
|
|
58
|
-
role: binding.name,
|
|
17
|
+
/** Objects `rls.assignments` adds. Names are part of the SQL contract. */
|
|
18
|
+
const ASSIGNMENTS = {
|
|
19
|
+
custom: "permdock_can_assign_custom_role",
|
|
20
|
+
customFor: "permdock_can_assign_custom_role_for",
|
|
21
|
+
trigger: "permdock_assignment"
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/cli/rls/assignments.ts
|
|
25
|
+
/** The client roles whose writes the assignment triggers check; any other role is a trusted path. */
|
|
26
|
+
const CLIENT_ROLES = [
|
|
27
|
+
"anon",
|
|
28
|
+
"anonymous",
|
|
29
|
+
"authenticated"
|
|
30
|
+
];
|
|
31
|
+
/** The mapped membership tables, `rls.assignments.tables` and the global-roles table `rls.roles`. */
|
|
32
|
+
function assignedTables(ctx) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const { name } of ctx.scopes) {
|
|
35
|
+
const mapped = scopeTable(ctx, name);
|
|
36
|
+
if (mapped === void 0) continue;
|
|
37
|
+
out.push({
|
|
38
|
+
table: mapped.table.table,
|
|
39
|
+
scope: name,
|
|
40
|
+
id: mapped.column,
|
|
41
|
+
tenant: mapped.tenantColumn ?? mapped.column,
|
|
42
|
+
role: mapped.table.role,
|
|
43
|
+
user: mapped.table.user
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (ctx.roles !== void 0) out.push({
|
|
47
|
+
table: ctx.roles.table,
|
|
48
|
+
scope: "global",
|
|
49
|
+
role: ctx.roles.role ?? "role",
|
|
50
|
+
user: ctx.roles.user ?? "user_id"
|
|
51
|
+
});
|
|
52
|
+
for (const extra of ctx.assignments?.tables ?? []) {
|
|
53
|
+
const scope = resolveScope(ctx.scopes, extra.scope);
|
|
54
|
+
if (scope === void 0) throw new Error(`PermDock CLI: rls.assignments.tables names scope '${extra.scope}', which the policy does not declare`);
|
|
55
|
+
if (scope !== ctx.scopes[0]?.name && extra.tenant === void 0) throw new Error(`PermDock CLI: rls.assignments.tables entry ${extra.table} assigns at ${scope}, below the first scope: name its tenant column`);
|
|
56
|
+
out.push({
|
|
57
|
+
table: extra.table,
|
|
59
58
|
scope,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
id: extra.id,
|
|
60
|
+
tenant: extra.tenant ?? extra.id,
|
|
61
|
+
role: extra.role,
|
|
62
|
+
...extra.user === void 0 ? {} : { user: extra.user }
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** The tables `rls.assignments.ownRole` refuses a caller's own rows on, by table key. */
|
|
68
|
+
function ownRoleTables(ctx, tables) {
|
|
69
|
+
const own = ctx.assignments?.ownRole;
|
|
70
|
+
if (own === void 0) return /* @__PURE__ */ new Set();
|
|
71
|
+
if (own === "refuse") return new Set(tables.filter((entry) => entry.user !== void 0).map((entry) => qualifySqlTable(entry.table)));
|
|
72
|
+
const out = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const [name, value] of Object.entries(own)) {
|
|
74
|
+
const entry = tables.find((table) => qualifySqlTable(table.table) === qualifySqlTable(name));
|
|
75
|
+
if (entry === void 0) throw new Error(`PermDock CLI: rls.assignments.ownRole names ${name}, which no assignment trigger guards`);
|
|
76
|
+
if (value !== "refuse") throw new Error(`PermDock CLI: rls.assignments.ownRole.${name} must be 'refuse'`);
|
|
77
|
+
if (entry.user === void 0) throw new Error(`PermDock CLI: rls.assignments.ownRole names ${name}, which has no user column: set its user in rls.assignments.tables`);
|
|
78
|
+
out.add(qualifySqlTable(entry.table));
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The tables `rls.assignments` guards, schema-qualified: each scope's mapped
|
|
84
|
+
* membership table, the global-roles table (`rls.roles`, else
|
|
85
|
+
* `supabase.hook.roles`) and `rls.assignments.tables`. Empty without
|
|
86
|
+
* `rls.assignments`.
|
|
87
|
+
*/
|
|
88
|
+
function guardedTables(config, scopes) {
|
|
89
|
+
const rls = config.rls;
|
|
90
|
+
if (rls?.assignments === void 0) return [];
|
|
91
|
+
const hook = config.supabase?.hook?.roles;
|
|
92
|
+
const roles = rls.roles ?? (hook === false ? void 0 : hook);
|
|
93
|
+
const tables = [
|
|
94
|
+
...scopes.flatMap((scope) => {
|
|
95
|
+
const mapped = memberForTable({
|
|
96
|
+
scopes,
|
|
97
|
+
...rls.memberships === void 0 ? {} : { memberships: rls.memberships }
|
|
98
|
+
}, scope.name);
|
|
99
|
+
return mapped === void 0 ? [] : [mapped.table];
|
|
100
|
+
}),
|
|
101
|
+
...roles === void 0 ? [] : [roles.table],
|
|
102
|
+
...(rls.assignments === true ? [] : rls.assignments.tables ?? []).map((entry) => entry.table)
|
|
103
|
+
];
|
|
104
|
+
return [...new Set(tables.map(qualifySqlTable))];
|
|
105
|
+
}
|
|
106
|
+
function isRoleList(role) {
|
|
107
|
+
return Array.isArray(role);
|
|
108
|
+
}
|
|
109
|
+
/** The role keys a row variable holds, one scalar per role source. */
|
|
110
|
+
function rowRoles(entry, row) {
|
|
111
|
+
return (isRoleList(entry.role) ? entry.role : [entry.role]).map((part) => roleColumn(part, qualifySqlTable(entry.table), row, {
|
|
112
|
+
label: `rls.assignments ${entry.table} role`,
|
|
113
|
+
prefix: "PermDock CLI"
|
|
114
|
+
}).lookup);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* `permdock_can_assign_custom_role`: whether the signed-in caller may assign a
|
|
118
|
+
* custom role of the tenant at the instance, by the checks the custom-role
|
|
119
|
+
* write functions run on its stored definition.
|
|
120
|
+
*/
|
|
121
|
+
function canAssignCustomSql(ctx, forUser = false) {
|
|
122
|
+
const tenantType = tenantTypeOf(ctx);
|
|
123
|
+
const fn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
|
|
124
|
+
const perms = qualified(ctx, CUSTOM_ROLES.permissions);
|
|
125
|
+
const includes = qualified(ctx, CUSTOM_ROLES.includes);
|
|
126
|
+
const guard = qualified(ctx, forUser ? CUSTOM_ROLES.guardFor : CUSTOM_ROLES.guard);
|
|
127
|
+
const userParam = forUser ? "p_user uuid, " : "";
|
|
128
|
+
const userArg = forUser ? "uuid, " : "";
|
|
129
|
+
const stored = `c.tenant_id is not distinct from p_tenant and c.scope = p_scope and c.role = p_role and (c.scope_id is null or c.scope_id = p_scope_id)`;
|
|
130
|
+
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;
|
|
131
|
+
grant execute on function ${fn}(${tenantType}, text, text, text) to authenticated;`;
|
|
132
|
+
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" : ""}
|
|
133
|
+
create or replace function ${fn}(${userParam}p_tenant ${tenantType}, p_scope text, p_scope_id text, p_role text)
|
|
134
|
+
returns boolean
|
|
135
|
+
language plpgsql
|
|
136
|
+
stable
|
|
137
|
+
security definer
|
|
138
|
+
set search_path = ''
|
|
139
|
+
as $$
|
|
140
|
+
declare
|
|
141
|
+
v_scope_id text;
|
|
142
|
+
v_found boolean := false;
|
|
143
|
+
begin
|
|
144
|
+
select true, c.scope_id into v_found, v_scope_id
|
|
145
|
+
from (
|
|
146
|
+
select c.scope_id from ${perms} c where ${stored}
|
|
147
|
+
union all
|
|
148
|
+
select c.scope_id from ${includes} c where ${stored}
|
|
149
|
+
) c
|
|
150
|
+
order by c.scope_id nulls first
|
|
151
|
+
limit 1;
|
|
152
|
+
if not coalesce(v_found, false) then
|
|
153
|
+
return false;
|
|
154
|
+
end if;
|
|
155
|
+
perform ${guard}(${forUser ? "p_user, " : ""}p_tenant, p_scope, v_scope_id, p_role);
|
|
156
|
+
return true;
|
|
157
|
+
exception
|
|
158
|
+
when insufficient_privilege or invalid_parameter_value then
|
|
159
|
+
return false;
|
|
160
|
+
end;
|
|
161
|
+
$$;
|
|
162
|
+
${grants}`;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The assignment triggers (\`rls.assignments\`): before a client role inserts,
|
|
166
|
+
* updates or deletes a row that assigns a role, every role the old and new
|
|
167
|
+
* row hold must be one the caller may assign at that instance, by
|
|
168
|
+
* \`permdock_can_assign\` for a declared role and
|
|
169
|
+
* \`permdock_can_assign_custom_role\` for a custom one. The functions run as
|
|
170
|
+
* the invoker, so \`current_user\` tells a client write from a trusted one.
|
|
171
|
+
*/
|
|
172
|
+
function assignmentSql(ctx) {
|
|
173
|
+
const own = ctx.ownership;
|
|
174
|
+
if (ctx.assignments === void 0 || own === void 0 || own.assigns.length === 0) return "";
|
|
175
|
+
const canAssign = qualified(ctx, OWNERSHIP.canAssign);
|
|
176
|
+
const custom = ctx.customRoles !== void 0 && ctx.authorize === "database";
|
|
177
|
+
const declared = ctx.customRoles?.declared;
|
|
178
|
+
const tenantType = tenantTypeOf(ctx);
|
|
179
|
+
const chunks = [];
|
|
180
|
+
const clients = `current_user::text = any(array[${CLIENT_ROLES.map(quoteLiteral).join(", ")}])`;
|
|
181
|
+
const tables = assignedTables(ctx);
|
|
182
|
+
const ownRole = ownRoleTables(ctx, tables);
|
|
183
|
+
for (const entry of tables) {
|
|
184
|
+
const table = quoteQualified(entry.table);
|
|
185
|
+
const suffix = entry.table.replaceAll(/[^a-z0-9_]/gu, "_");
|
|
186
|
+
const fn = qualified(ctx, quoteIdent(`${ASSIGNMENTS.trigger}_${suffix}`));
|
|
187
|
+
const ownUser = entry.user !== void 0 && ownRole.has(qualifySqlTable(entry.table)) ? entry.user : void 0;
|
|
188
|
+
const ownCheck = (row, reason) => ownUser === void 0 ? "" : ` if ${row}.${quoteIdent(ownUser)}::text = (${subjectIdSql(ctx)})::text then
|
|
189
|
+
raise exception using
|
|
190
|
+
errcode = '42501',
|
|
191
|
+
message = 'permdock: the caller may not change their own roles',
|
|
192
|
+
hint = '${reason}';
|
|
193
|
+
end if;
|
|
194
|
+
`;
|
|
195
|
+
const check = (row) => {
|
|
196
|
+
const id = entry.id === void 0 ? "null::text" : `${row}.${quoteIdent(entry.id)}::text`;
|
|
197
|
+
const customFn = qualified(ctx, ASSIGNMENTS.custom);
|
|
198
|
+
let allowed = `${canAssign}(v_role, ${id})`;
|
|
199
|
+
if (custom && declared !== void 0) {
|
|
200
|
+
const platform = `${customFn}(null, 'global', null, v_role)`;
|
|
201
|
+
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`;
|
|
202
|
+
}
|
|
203
|
+
return ` foreach v_role in array array[${rowRoles(entry, row).join(", ")}]::text[] loop
|
|
204
|
+
if v_role is not null and not coalesce(${allowed}, false) then
|
|
205
|
+
raise exception using
|
|
206
|
+
errcode = '42501',
|
|
207
|
+
message = 'permdock: the caller may not assign ' || v_role || ' in ' || coalesce(${id}, 'null'),
|
|
208
|
+
hint = 'not-assignable-by';
|
|
209
|
+
end if;
|
|
210
|
+
end loop;`;
|
|
211
|
+
};
|
|
212
|
+
const what = entry.id === void 0 ? "global roles" : `${entry.scope} roles`;
|
|
213
|
+
chunks.push(`-- ${entry.table}: a client role may write only the ${what} it may assign${ownUser === void 0 ? "" : ", and none on its own rows"}
|
|
214
|
+
create or replace function ${fn}()
|
|
215
|
+
returns trigger
|
|
216
|
+
language plpgsql
|
|
217
|
+
volatile
|
|
218
|
+
set search_path = ''
|
|
219
|
+
as $$
|
|
220
|
+
declare
|
|
221
|
+
v_role text;
|
|
222
|
+
begin
|
|
223
|
+
if not (${clients}) then
|
|
224
|
+
return coalesce(new, old);
|
|
225
|
+
end if;
|
|
226
|
+
if tg_op in ('UPDATE', 'DELETE') then
|
|
227
|
+
${ownCheck("old", "self-demotion")}${check("old")}
|
|
228
|
+
end if;
|
|
229
|
+
if tg_op in ('INSERT', 'UPDATE') then
|
|
230
|
+
${ownCheck("new", "not-assignable-by")}${check("new")}
|
|
231
|
+
end if;
|
|
232
|
+
return coalesce(new, old);
|
|
233
|
+
end;
|
|
234
|
+
$$;
|
|
235
|
+
revoke execute on function ${fn}() from public, anon, authenticated;
|
|
236
|
+
drop trigger if exists "permdock_assignment" on ${table};
|
|
237
|
+
create trigger "permdock_assignment"
|
|
238
|
+
before insert or update or delete on ${table}
|
|
239
|
+
for each row execute function ${fn}();`);
|
|
240
|
+
}
|
|
241
|
+
return `${chunks.join("\n\n")}\n`;
|
|
71
242
|
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/cli/rls/ownership-triggers.ts
|
|
72
245
|
function objectName(prefix, scope) {
|
|
73
246
|
return quoteIdent(`${prefix}_${scope}`);
|
|
74
247
|
}
|
|
@@ -87,7 +260,7 @@ function holdersSql(ctx, table, column, idExpr, role, into) {
|
|
|
87
260
|
const kind = typeof role === "string" ? roleKindSql(ctx, role, via) : kindFilterSql(ctx, roleExpr, via);
|
|
88
261
|
if (kind !== void 0) filters.push(kind);
|
|
89
262
|
return `select count(distinct m.${quoteIdent(table.user)}) into ${into}
|
|
90
|
-
from ${
|
|
263
|
+
from ${quoteQualified(table.table)} m${held.join}
|
|
91
264
|
where ${filters.join("\n and ")}`;
|
|
92
265
|
}
|
|
93
266
|
/**
|
|
@@ -99,7 +272,7 @@ function holdersTriggerSql(ctx, scope, table, column, counted) {
|
|
|
99
272
|
const fn = qualified(ctx, `${OWNERSHIP.holders}_${scope}`);
|
|
100
273
|
const col = quoteIdent(column);
|
|
101
274
|
const checks = counted.map((rule) => {
|
|
102
|
-
const lines = [` select count(*) into v_total from ${
|
|
275
|
+
const lines = [` select count(*) into v_total from ${quoteQualified(table.table)} m where m.${col} = v_key;`, ` ${holdersSql(ctx, table, column, "v_key", rule.role, "v_count")};`];
|
|
103
276
|
if (rule.min > 0) lines.push(` if v_total > 0 and v_count < ${String(rule.min)} then
|
|
104
277
|
raise exception using
|
|
105
278
|
errcode = '23514',
|
|
@@ -124,7 +297,7 @@ as $$
|
|
|
124
297
|
declare
|
|
125
298
|
v_ids text[] := '{}';
|
|
126
299
|
v_id text;
|
|
127
|
-
v_key ${
|
|
300
|
+
v_key ${quoteQualified(table.table)}.${col}%type;
|
|
128
301
|
v_total bigint;
|
|
129
302
|
v_count bigint;
|
|
130
303
|
begin
|
|
@@ -142,9 +315,9 @@ ${checks.join("\n")}
|
|
|
142
315
|
return null;
|
|
143
316
|
end;
|
|
144
317
|
$$;
|
|
145
|
-
drop trigger if exists ${objectName(OWNERSHIP.holders, scope)} on ${
|
|
318
|
+
drop trigger if exists ${objectName(OWNERSHIP.holders, scope)} on ${quoteQualified(table.table)};
|
|
146
319
|
create constraint trigger ${objectName(OWNERSHIP.holders, scope)}
|
|
147
|
-
after insert or update or delete on ${
|
|
320
|
+
after insert or update or delete on ${quoteQualified(table.table)}
|
|
148
321
|
deferrable initially deferred
|
|
149
322
|
for each row execute function ${fn}();`;
|
|
150
323
|
}
|
|
@@ -178,7 +351,7 @@ function transferTriggerSql(ctx, scope, table, column, roles) {
|
|
|
178
351
|
end loop;`;
|
|
179
352
|
const inserted = rows("n", "permdock_new", "1");
|
|
180
353
|
const deleted = rows("o", "permdock_old", "-1");
|
|
181
|
-
const target =
|
|
354
|
+
const target = quoteQualified(table.table);
|
|
182
355
|
const trigger = (op) => {
|
|
183
356
|
const name = quoteIdent(`${OWNERSHIP.transferOnly}_${scope}_${op}`);
|
|
184
357
|
return `drop trigger if exists ${name} on ${target};
|
|
@@ -212,6 +385,8 @@ ${trigger("insert")}
|
|
|
212
385
|
${trigger("update")}
|
|
213
386
|
${trigger("delete")}`;
|
|
214
387
|
}
|
|
388
|
+
//#endregion
|
|
389
|
+
//#region src/cli/rls/ownership-sources.ts
|
|
215
390
|
/**
|
|
216
391
|
* The membership sources holding scope `name`: `undefined` when there are
|
|
217
392
|
* none, `'not-tables'` when one of them carries no table to put a trigger on.
|
|
@@ -243,10 +418,10 @@ function sourceKeys(sources) {
|
|
|
243
418
|
};
|
|
244
419
|
}
|
|
245
420
|
function regclassOf(table) {
|
|
246
|
-
return `${quoteLiteral(
|
|
421
|
+
return `${quoteLiteral(quoteQualified(table))}::regclass`;
|
|
247
422
|
}
|
|
248
423
|
function sourceTables(sources) {
|
|
249
|
-
return [...new Set(sources.map((source) =>
|
|
424
|
+
return [...new Set(sources.map((source) => quoteQualified(source.table)))];
|
|
250
425
|
}
|
|
251
426
|
/** `min` and `max` over membership sources: the same commit-time check as on a mapped table, counted over every source of the scope. */
|
|
252
427
|
function sourceHoldersTriggerSql(ctx, scope, sources, counted) {
|
|
@@ -398,6 +573,8 @@ function sourceRowsSql(sources, user) {
|
|
|
398
573
|
const keep = sources.some((source) => source.sql.keeps);
|
|
399
574
|
return sources.map((source) => source.sql.select(user(source), keep).replaceAll(/^/gmu, " ")).join("\n union all\n");
|
|
400
575
|
}
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/cli/rls/can-assign.ts
|
|
401
578
|
/** Whether the signed-in user (or `p_user` in the `_for` form) holds, in instance `p_scope_id`, a role whose `assigns` lists `p_role`. */
|
|
402
579
|
function canAssignSql(base, own, forUser = false) {
|
|
403
580
|
const ctx = forUser ? {
|
|
@@ -470,7 +647,7 @@ ${sourceRowsSql(sources, userOf)}
|
|
|
470
647
|
const kind = kindFilterSql(ctx, role.sql, memberVia(table));
|
|
471
648
|
if (kind !== void 0) filters.push(kind);
|
|
472
649
|
parts.push(`exists (
|
|
473
|
-
select 1 from ${
|
|
650
|
+
select 1 from ${quoteQualified(table.table)} m${role.join}
|
|
474
651
|
where ${filters.join("\n and ")}
|
|
475
652
|
)`);
|
|
476
653
|
} else {
|
|
@@ -507,337 +684,168 @@ begin
|
|
|
507
684
|
return ${signedIn(ctx)} and (
|
|
508
685
|
${body}
|
|
509
686
|
);
|
|
510
|
-
end;
|
|
511
|
-
$$;`;
|
|
512
|
-
if (forUser) {
|
|
513
|
-
const user = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
514
|
-
return `-- who may assign, for a user the caller names: trusted SQL acting later for a stored user; no client role may execute it
|
|
515
|
-
create or replace function ${fn}(p_user ${user}, p_role text, p_scope_id text)
|
|
516
|
-
returns boolean
|
|
517
|
-
${language}
|
|
518
|
-
revoke execute on function ${fn}(${user}, text, text) from public, anon, authenticated;`;
|
|
519
|
-
}
|
|
520
|
-
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
|
|
521
|
-
create or replace function ${fn}(p_role text, p_scope_id text)
|
|
522
|
-
returns boolean
|
|
523
|
-
${language}
|
|
524
|
-
revoke execute on function ${fn}(text, text) from public, anon;
|
|
525
|
-
grant execute on function ${fn}(text, text) to authenticated;`;
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* The ownership objects: holder-count and transfer-only triggers on each
|
|
529
|
-
* scope's membership table, and `permdock_can_assign` for the application's
|
|
530
|
-
* own policies on those tables. Empty when no role declares a rule.
|
|
531
|
-
*/
|
|
532
|
-
function ownershipSql(ctx) {
|
|
533
|
-
const own = ctx.ownership;
|
|
534
|
-
if (own === void 0) return "";
|
|
535
|
-
const chunks = [];
|
|
536
|
-
const skipped = ctx.skipOwnershipTriggers;
|
|
537
|
-
for (const { name } of ctx.scopes) {
|
|
538
|
-
const counted = own.counted.filter((rule) => rule.scope === name);
|
|
539
|
-
if (counted.length === 0) continue;
|
|
540
|
-
if (skipped === "all" || skipped?.includes(name) === true) {
|
|
541
|
-
chunks.push(`-- ${name}: rls.ownershipTriggers leaves out the triggers, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
542
|
-
continue;
|
|
543
|
-
}
|
|
544
|
-
const transfer = counted.filter((rule) => rule.transferOnly).map((rule) => rule.role);
|
|
545
|
-
const mapped = scopeTable(ctx, name);
|
|
546
|
-
if (mapped === void 0) {
|
|
547
|
-
const sources = holderSources(ctx, name);
|
|
548
|
-
if (sources === void 0) chunks.push(`-- ${name}: no memberships table configured, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
549
|
-
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`);
|
|
550
|
-
else {
|
|
551
|
-
chunks.push(sourceHoldersTriggerSql(ctx, name, sources, counted));
|
|
552
|
-
if (transfer.length > 0) chunks.push(sourceTransferTriggerSql(ctx, name, sources, transfer));
|
|
553
|
-
}
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
chunks.push(holdersTriggerSql(ctx, name, mapped.table, mapped.column, counted));
|
|
557
|
-
if (transfer.length > 0) chunks.push(transferTriggerSql(ctx, name, mapped.table, mapped.column, transfer));
|
|
558
|
-
}
|
|
559
|
-
if (own.assigns.length > 0) {
|
|
560
|
-
chunks.push(canAssignSql(ctx, own));
|
|
561
|
-
if (ctx.authorize === "database") chunks.push(canAssignSql(ctx, own, true));
|
|
562
|
-
const custom = assignsCustomRoles(ctx);
|
|
563
|
-
if (custom) {
|
|
564
|
-
chunks.push(canAssignCustomSql(ctx));
|
|
565
|
-
if (hasCustomRoleChecksFor(ctx)) chunks.push(canAssignCustomSql(ctx, true));
|
|
566
|
-
}
|
|
567
|
-
chunks.push(canAssignAnySql(ctx, custom));
|
|
568
|
-
if (hasCanAssignAnyFor(ctx)) chunks.push(canAssignAnySql(ctx, custom, true));
|
|
569
|
-
}
|
|
570
|
-
return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
|
|
571
|
-
}
|
|
572
|
-
/** Whether the helpers keep custom roles in tables, so assigning one is checked against its stored definition. */
|
|
573
|
-
function assignsCustomRoles(ctx) {
|
|
574
|
-
return ctx.customRoles !== void 0 && ctx.authorize === "database";
|
|
575
|
-
}
|
|
576
|
-
/** Whether `permdock_can_assign_any_for` is written: every function it calls has a `_for` form. */
|
|
577
|
-
function hasCanAssignAnyFor(ctx) {
|
|
578
|
-
return ctx.authorize === "database" && (!assignsCustomRoles(ctx) || hasCustomRoleChecksFor(ctx));
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* `permdock_can_assign_any`: one check for a declared or a custom role, so
|
|
582
|
-
* SQL that assigns roles calls one function: `permdock_can_assign` for a
|
|
583
|
-
* declared role, `permdock_can_assign_custom_role` for a custom one. `p_scope`
|
|
584
|
-
* `'global'` (no tenant, no instance) assigns a platform role.
|
|
585
|
-
*/
|
|
586
|
-
function canAssignAnySql(ctx, custom, forUser = false) {
|
|
587
|
-
const tenantType = tenantTypeOf(ctx);
|
|
588
|
-
const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignAnyFor : OWNERSHIP.canAssignAny);
|
|
589
|
-
const declaredFn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
|
|
590
|
-
const customFn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
|
|
591
|
-
const userType = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
592
|
-
const userParam = forUser ? `p_user ${userType}, ` : "";
|
|
593
|
-
const userArg = forUser ? `${userType}, ` : "";
|
|
594
|
-
const user = forUser ? "p_user, " : "";
|
|
595
|
-
const declared = `${declaredFn}(${user}p_role, case when p_scope = 'global' then null else p_scope_id end)`;
|
|
596
|
-
const body = custom && ctx.customRoles !== void 0 ? `case
|
|
597
|
-
when p_role = any(${declaredArray(ctx.customRoles.declared)}) then ${declared}
|
|
598
|
-
when p_scope = 'global' then ${customFn}(${user}null, 'global', null, p_role)
|
|
599
|
-
else ${customFn}(${user}p_tenant, p_scope, p_scope_id, p_role)
|
|
600
|
-
end` : declared;
|
|
601
|
-
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;
|
|
602
|
-
grant execute on function ${fn}(text, ${tenantType}, text, text) to authenticated;`;
|
|
603
|
-
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" : ""}
|
|
604
|
-
create or replace function ${fn}(${userParam}p_role text, p_tenant ${tenantType}, p_scope text, p_scope_id text)
|
|
605
|
-
returns boolean
|
|
606
|
-
language sql
|
|
607
|
-
stable
|
|
608
|
-
security definer
|
|
609
|
-
set search_path = ''
|
|
610
|
-
as $$
|
|
611
|
-
select coalesce(${body}, false)
|
|
612
|
-
$$;
|
|
613
|
-
${grants}`;
|
|
614
|
-
}
|
|
615
|
-
/** Objects `rls.assignments` adds. Names are part of the SQL contract. */
|
|
616
|
-
const ASSIGNMENTS = {
|
|
617
|
-
custom: "permdock_can_assign_custom_role",
|
|
618
|
-
customFor: "permdock_can_assign_custom_role_for",
|
|
619
|
-
trigger: "permdock_assignment"
|
|
620
|
-
};
|
|
621
|
-
/** The client roles whose writes the assignment triggers check; any other role is a trusted path. */
|
|
622
|
-
const CLIENT_ROLES = [
|
|
623
|
-
"anon",
|
|
624
|
-
"anonymous",
|
|
625
|
-
"authenticated"
|
|
626
|
-
];
|
|
627
|
-
/** The mapped membership tables, `rls.assignments.tables` and the global-roles table `rls.roles`. */
|
|
628
|
-
function assignedTables(ctx) {
|
|
629
|
-
const out = [];
|
|
630
|
-
for (const { name } of ctx.scopes) {
|
|
631
|
-
const mapped = scopeTable(ctx, name);
|
|
632
|
-
if (mapped === void 0) continue;
|
|
633
|
-
out.push({
|
|
634
|
-
table: mapped.table.table,
|
|
635
|
-
scope: name,
|
|
636
|
-
id: mapped.column,
|
|
637
|
-
tenant: mapped.tenantColumn ?? mapped.column,
|
|
638
|
-
role: mapped.table.role,
|
|
639
|
-
user: mapped.table.user
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
if (ctx.roles !== void 0) out.push({
|
|
643
|
-
table: ctx.roles.table,
|
|
644
|
-
scope: "global",
|
|
645
|
-
role: ctx.roles.role ?? "role",
|
|
646
|
-
user: ctx.roles.user ?? "user_id"
|
|
647
|
-
});
|
|
648
|
-
for (const extra of ctx.assignments?.tables ?? []) {
|
|
649
|
-
const scope = resolveScope(ctx.scopes, extra.scope);
|
|
650
|
-
if (scope === void 0) throw new Error(`PermDock CLI: rls.assignments.tables names scope '${extra.scope}', which the policy does not declare`);
|
|
651
|
-
if (scope !== ctx.scopes[0]?.name && extra.tenant === void 0) throw new Error(`PermDock CLI: rls.assignments.tables entry ${extra.table} assigns at ${scope}, below the first scope: name its tenant column`);
|
|
652
|
-
out.push({
|
|
653
|
-
table: extra.table,
|
|
654
|
-
scope,
|
|
655
|
-
id: extra.id,
|
|
656
|
-
tenant: extra.tenant ?? extra.id,
|
|
657
|
-
role: extra.role,
|
|
658
|
-
...extra.user === void 0 ? {} : { user: extra.user }
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
return out;
|
|
662
|
-
}
|
|
663
|
-
/** The tables `rls.assignments.ownRole` refuses a caller's own rows on, by table key. */
|
|
664
|
-
function ownRoleTables(ctx, tables) {
|
|
665
|
-
const own = ctx.assignments?.ownRole;
|
|
666
|
-
if (own === void 0) return /* @__PURE__ */ new Set();
|
|
667
|
-
if (own === "refuse") return new Set(tables.filter((entry) => entry.user !== void 0).map((entry) => qualifiedTable(entry.table)));
|
|
668
|
-
const out = /* @__PURE__ */ new Set();
|
|
669
|
-
for (const [name, value] of Object.entries(own)) {
|
|
670
|
-
const entry = tables.find((table) => qualifiedTable(table.table) === qualifiedTable(name));
|
|
671
|
-
if (entry === void 0) throw new Error(`PermDock CLI: rls.assignments.ownRole names ${name}, which no assignment trigger guards`);
|
|
672
|
-
if (value !== "refuse") throw new Error(`PermDock CLI: rls.assignments.ownRole.${name} must be 'refuse'`);
|
|
673
|
-
if (entry.user === void 0) throw new Error(`PermDock CLI: rls.assignments.ownRole names ${name}, which has no user column: set its user in rls.assignments.tables`);
|
|
674
|
-
out.add(qualifiedTable(entry.table));
|
|
675
|
-
}
|
|
676
|
-
return out;
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* The tables `rls.assignments` guards, schema-qualified: each scope's mapped
|
|
680
|
-
* membership table, the global-roles table (`rls.roles`, else
|
|
681
|
-
* `supabase.hook.roles`) and `rls.assignments.tables`. Empty without
|
|
682
|
-
* `rls.assignments`.
|
|
683
|
-
*/
|
|
684
|
-
function guardedTables(config, scopes) {
|
|
685
|
-
const rls = config.rls;
|
|
686
|
-
if (rls?.assignments === void 0) return [];
|
|
687
|
-
const hook = config.supabase?.hook?.roles;
|
|
688
|
-
const roles = rls.roles ?? (hook === false ? void 0 : hook);
|
|
689
|
-
const tables = [
|
|
690
|
-
...scopes.flatMap((scope) => {
|
|
691
|
-
const mapped = memberForTable({
|
|
692
|
-
scopes,
|
|
693
|
-
...rls.memberships === void 0 ? {} : { memberships: rls.memberships }
|
|
694
|
-
}, scope.name);
|
|
695
|
-
return mapped === void 0 ? [] : [mapped.table];
|
|
696
|
-
}),
|
|
697
|
-
...roles === void 0 ? [] : [roles.table],
|
|
698
|
-
...(rls.assignments === true ? [] : rls.assignments.tables ?? []).map((entry) => entry.table)
|
|
699
|
-
];
|
|
700
|
-
return [...new Set(tables.map(qualifiedTable))];
|
|
687
|
+
end;
|
|
688
|
+
$$;`;
|
|
689
|
+
if (forUser) {
|
|
690
|
+
const user = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
691
|
+
return `-- who may assign, for a user the caller names: trusted SQL acting later for a stored user; no client role may execute it
|
|
692
|
+
create or replace function ${fn}(p_user ${user}, p_role text, p_scope_id text)
|
|
693
|
+
returns boolean
|
|
694
|
+
${language}
|
|
695
|
+
revoke execute on function ${fn}(${user}, text, text) from public, anon, authenticated;`;
|
|
696
|
+
}
|
|
697
|
+
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
|
|
698
|
+
create or replace function ${fn}(p_role text, p_scope_id text)
|
|
699
|
+
returns boolean
|
|
700
|
+
${language}
|
|
701
|
+
revoke execute on function ${fn}(text, text) from public, anon;
|
|
702
|
+
grant execute on function ${fn}(text, text) to authenticated;`;
|
|
701
703
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
+
/** Whether the helpers keep custom roles in tables, so assigning one is checked against its stored definition. */
|
|
705
|
+
function assignsCustomRoles(ctx) {
|
|
706
|
+
return ctx.customRoles !== void 0 && ctx.authorize === "database";
|
|
704
707
|
}
|
|
705
|
-
/**
|
|
706
|
-
function
|
|
707
|
-
return
|
|
708
|
-
label: `rls.assignments ${entry.table} role`,
|
|
709
|
-
prefix: "PermDock CLI"
|
|
710
|
-
}).lookup);
|
|
708
|
+
/** Whether `permdock_can_assign_any_for` is written: every function it calls has a `_for` form. */
|
|
709
|
+
function hasCanAssignAnyFor(ctx) {
|
|
710
|
+
return ctx.authorize === "database" && (!assignsCustomRoles(ctx) || hasCustomRoleChecksFor(ctx));
|
|
711
711
|
}
|
|
712
712
|
/**
|
|
713
|
-
* `
|
|
714
|
-
*
|
|
715
|
-
*
|
|
713
|
+
* `permdock_can_assign_any`: one check for a declared or a custom role, so
|
|
714
|
+
* SQL that assigns roles calls one function: `permdock_can_assign` for a
|
|
715
|
+
* declared role, `permdock_can_assign_custom_role` for a custom one. `p_scope`
|
|
716
|
+
* `'global'` (no tenant, no instance) assigns a platform role.
|
|
716
717
|
*/
|
|
717
|
-
function
|
|
718
|
+
function canAssignAnySql(ctx, custom, forUser = false) {
|
|
718
719
|
const tenantType = tenantTypeOf(ctx);
|
|
719
|
-
const fn = qualified(ctx, forUser ?
|
|
720
|
-
const
|
|
721
|
-
const
|
|
722
|
-
const
|
|
723
|
-
const userParam = forUser ?
|
|
724
|
-
const userArg = forUser ?
|
|
725
|
-
const
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
720
|
+
const fn = qualified(ctx, forUser ? OWNERSHIP.canAssignAnyFor : OWNERSHIP.canAssignAny);
|
|
721
|
+
const declaredFn = qualified(ctx, forUser ? OWNERSHIP.canAssignFor : OWNERSHIP.canAssign);
|
|
722
|
+
const customFn = qualified(ctx, forUser ? ASSIGNMENTS.customFor : ASSIGNMENTS.custom);
|
|
723
|
+
const userType = ctx.dialect === "supabase" ? "uuid" : "text";
|
|
724
|
+
const userParam = forUser ? `p_user ${userType}, ` : "";
|
|
725
|
+
const userArg = forUser ? `${userType}, ` : "";
|
|
726
|
+
const user = forUser ? "p_user, " : "";
|
|
727
|
+
const declared = `${declaredFn}(${user}p_role, case when p_scope = 'global' then null else p_scope_id end)`;
|
|
728
|
+
const body = custom && ctx.customRoles !== void 0 ? `case
|
|
729
|
+
when p_role = any(${declaredArray(ctx.customRoles.declared)}) then ${declared}
|
|
730
|
+
when p_scope = 'global' then ${customFn}(${user}null, 'global', null, p_role)
|
|
731
|
+
else ${customFn}(${user}p_tenant, p_scope, p_scope_id, p_role)
|
|
732
|
+
end` : declared;
|
|
733
|
+
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;
|
|
734
|
+
grant execute on function ${fn}(text, ${tenantType}, text, text) to authenticated;`;
|
|
735
|
+
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" : ""}
|
|
736
|
+
create or replace function ${fn}(${userParam}p_role text, p_tenant ${tenantType}, p_scope text, p_scope_id text)
|
|
730
737
|
returns boolean
|
|
731
|
-
language
|
|
738
|
+
language sql
|
|
732
739
|
stable
|
|
733
740
|
security definer
|
|
734
741
|
set search_path = ''
|
|
735
742
|
as $$
|
|
736
|
-
|
|
737
|
-
v_scope_id text;
|
|
738
|
-
v_found boolean := false;
|
|
739
|
-
begin
|
|
740
|
-
select true, c.scope_id into v_found, v_scope_id
|
|
741
|
-
from (
|
|
742
|
-
select c.scope_id from ${perms} c where ${stored}
|
|
743
|
-
union all
|
|
744
|
-
select c.scope_id from ${includes} c where ${stored}
|
|
745
|
-
) c
|
|
746
|
-
order by c.scope_id nulls first
|
|
747
|
-
limit 1;
|
|
748
|
-
if not coalesce(v_found, false) then
|
|
749
|
-
return false;
|
|
750
|
-
end if;
|
|
751
|
-
perform ${guard}(${forUser ? "p_user, " : ""}p_tenant, p_scope, v_scope_id, p_role);
|
|
752
|
-
return true;
|
|
753
|
-
exception
|
|
754
|
-
when insufficient_privilege or invalid_parameter_value then
|
|
755
|
-
return false;
|
|
756
|
-
end;
|
|
743
|
+
select coalesce(${body}, false)
|
|
757
744
|
$$;
|
|
758
745
|
${grants}`;
|
|
759
746
|
}
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region src/cli/rls/ownership.ts
|
|
749
|
+
function roleScope(policy, scopes, name) {
|
|
750
|
+
const on = policy.rolesByName.get(name)?.on ?? findRole(policy.vocabulary.roles, name)?.on;
|
|
751
|
+
return typeof on === "string" ? resolveScope(scopes, on) : void 0;
|
|
752
|
+
}
|
|
753
|
+
function isGlobalRole(policy, name) {
|
|
754
|
+
const binding = policy.rolesByName.get(name);
|
|
755
|
+
if (binding !== void 0) return binding.on === void 0;
|
|
756
|
+
const leaf = findRole(policy.vocabulary.roles, name);
|
|
757
|
+
return leaf !== void 0 && leaf.on === void 0;
|
|
758
|
+
}
|
|
759
|
+
/** The rules `rls generate` enforces; `undefined` when no role declares one. */
|
|
760
|
+
function ownershipRules(policy, scopes) {
|
|
761
|
+
const kinds = {};
|
|
762
|
+
const assigns = [];
|
|
763
|
+
const counted = [];
|
|
764
|
+
for (const binding of policy.roles) {
|
|
765
|
+
const scope = roleScope(policy, scopes, binding.name);
|
|
766
|
+
if (binding.for !== void 0) kinds[binding.name] = binding.for;
|
|
767
|
+
const assigner = scope ?? (binding.on === void 0 ? "global" : void 0);
|
|
768
|
+
for (const target of binding.assigns ?? []) {
|
|
769
|
+
const at = roleScope(policy, scopes, target);
|
|
770
|
+
if (assigner !== void 0 && at !== void 0 && (assigner === "global" || scopeChain(scopes, at).includes(assigner))) assigns.push({
|
|
771
|
+
assigner: binding.name,
|
|
772
|
+
scope: assigner,
|
|
773
|
+
role: target,
|
|
774
|
+
at
|
|
775
|
+
});
|
|
776
|
+
else if (assigner === "global" && isGlobalRole(policy, target)) assigns.push({
|
|
777
|
+
assigner: binding.name,
|
|
778
|
+
scope: "global",
|
|
779
|
+
role: target,
|
|
780
|
+
at: "global"
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
const min = binding.min ?? 0;
|
|
784
|
+
const transferOnly = binding.transferOnly === true;
|
|
785
|
+
if (scope !== void 0 && (min > 0 || binding.max !== void 0 || transferOnly)) counted.push(binding.max === void 0 ? {
|
|
786
|
+
role: binding.name,
|
|
787
|
+
scope,
|
|
788
|
+
min,
|
|
789
|
+
transferOnly
|
|
790
|
+
} : {
|
|
791
|
+
role: binding.name,
|
|
792
|
+
scope,
|
|
793
|
+
min,
|
|
794
|
+
max: binding.max,
|
|
795
|
+
transferOnly
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
if (Object.keys(kinds).length === 0 && assigns.length === 0 && counted.length === 0) return;
|
|
799
|
+
return {
|
|
800
|
+
kinds,
|
|
801
|
+
assigns,
|
|
802
|
+
counted
|
|
803
|
+
};
|
|
804
|
+
}
|
|
760
805
|
/**
|
|
761
|
-
* The
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
* \`permdock_can_assign\` for a declared role and
|
|
765
|
-
* \`permdock_can_assign_custom_role\` for a custom one. The functions run as
|
|
766
|
-
* the invoker, so \`current_user\` tells a client write from a trusted one.
|
|
806
|
+
* The ownership objects: holder-count and transfer-only triggers on each
|
|
807
|
+
* scope's membership table, and `permdock_can_assign` for the application's
|
|
808
|
+
* own policies on those tables. Empty when no role declares a rule.
|
|
767
809
|
*/
|
|
768
|
-
function
|
|
810
|
+
function ownershipSql(ctx) {
|
|
769
811
|
const own = ctx.ownership;
|
|
770
|
-
if (
|
|
771
|
-
const canAssign = qualified(ctx, OWNERSHIP.canAssign);
|
|
772
|
-
const custom = ctx.customRoles !== void 0 && ctx.authorize === "database";
|
|
773
|
-
const declared = ctx.customRoles?.declared;
|
|
774
|
-
const tenantType = tenantTypeOf(ctx);
|
|
812
|
+
if (own === void 0) return "";
|
|
775
813
|
const chunks = [];
|
|
776
|
-
const
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const customFn = qualified(ctx, ASSIGNMENTS.custom);
|
|
794
|
-
let allowed = `${canAssign}(v_role, ${id})`;
|
|
795
|
-
if (custom && declared !== void 0) {
|
|
796
|
-
const platform = `${customFn}(null, 'global', null, v_role)`;
|
|
797
|
-
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`;
|
|
814
|
+
const skipped = ctx.skipOwnershipTriggers;
|
|
815
|
+
for (const { name } of ctx.scopes) {
|
|
816
|
+
const counted = own.counted.filter((rule) => rule.scope === name);
|
|
817
|
+
if (counted.length === 0) continue;
|
|
818
|
+
if (skipped === "all" || skipped?.includes(name) === true) {
|
|
819
|
+
chunks.push(`-- ${name}: rls.ownershipTriggers leaves out the triggers, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const transfer = counted.filter((rule) => rule.transferOnly).map((rule) => rule.role);
|
|
823
|
+
const mapped = scopeTable(ctx, name);
|
|
824
|
+
if (mapped === void 0) {
|
|
825
|
+
const sources = holderSources(ctx, name);
|
|
826
|
+
if (sources === void 0) chunks.push(`-- ${name}: no memberships table configured, so min, max and transferOnly are checked only by decideRoleChange`);
|
|
827
|
+
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`);
|
|
828
|
+
else {
|
|
829
|
+
chunks.push(sourceHoldersTriggerSql(ctx, name, sources, counted));
|
|
830
|
+
if (transfer.length > 0) chunks.push(sourceTransferTriggerSql(ctx, name, sources, transfer));
|
|
798
831
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
message = 'permdock: the caller may not assign ' || v_role || ' in ' || coalesce(${id}, 'null'),
|
|
804
|
-
hint = 'not-assignable-by';
|
|
805
|
-
end if;
|
|
806
|
-
end loop;`;
|
|
807
|
-
};
|
|
808
|
-
const what = entry.id === void 0 ? "global roles" : `${entry.scope} roles`;
|
|
809
|
-
chunks.push(`-- ${entry.table}: a client role may write only the ${what} it may assign${ownUser === void 0 ? "" : ", and none on its own rows"}
|
|
810
|
-
create or replace function ${fn}()
|
|
811
|
-
returns trigger
|
|
812
|
-
language plpgsql
|
|
813
|
-
volatile
|
|
814
|
-
set search_path = ''
|
|
815
|
-
as $$
|
|
816
|
-
declare
|
|
817
|
-
v_role text;
|
|
818
|
-
begin
|
|
819
|
-
if not (${clients}) then
|
|
820
|
-
return coalesce(new, old);
|
|
821
|
-
end if;
|
|
822
|
-
if tg_op in ('UPDATE', 'DELETE') then
|
|
823
|
-
${ownCheck("old", "self-demotion")}${check("old")}
|
|
824
|
-
end if;
|
|
825
|
-
if tg_op in ('INSERT', 'UPDATE') then
|
|
826
|
-
${ownCheck("new", "not-assignable-by")}${check("new")}
|
|
827
|
-
end if;
|
|
828
|
-
return coalesce(new, old);
|
|
829
|
-
end;
|
|
830
|
-
$$;
|
|
831
|
-
revoke execute on function ${fn}() from public, anon, authenticated;
|
|
832
|
-
drop trigger if exists "permdock_assignment" on ${table};
|
|
833
|
-
create trigger "permdock_assignment"
|
|
834
|
-
before insert or update or delete on ${table}
|
|
835
|
-
for each row execute function ${fn}();`);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
chunks.push(holdersTriggerSql(ctx, name, mapped.table, mapped.column, counted));
|
|
835
|
+
if (transfer.length > 0) chunks.push(transferTriggerSql(ctx, name, mapped.table, mapped.column, transfer));
|
|
836
836
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
837
|
+
if (own.assigns.length > 0) {
|
|
838
|
+
chunks.push(canAssignSql(ctx, own));
|
|
839
|
+
if (ctx.authorize === "database") chunks.push(canAssignSql(ctx, own, true));
|
|
840
|
+
const custom = assignsCustomRoles(ctx);
|
|
841
|
+
if (custom) {
|
|
842
|
+
chunks.push(canAssignCustomSql(ctx));
|
|
843
|
+
if (hasCustomRoleChecksFor(ctx)) chunks.push(canAssignCustomSql(ctx, true));
|
|
844
|
+
}
|
|
845
|
+
chunks.push(canAssignAnySql(ctx, custom));
|
|
846
|
+
if (hasCanAssignAnyFor(ctx)) chunks.push(canAssignAnySql(ctx, custom, true));
|
|
847
|
+
}
|
|
848
|
+
return chunks.length === 0 ? "" : `${chunks.join("\n\n")}\n`;
|
|
841
849
|
}
|
|
842
850
|
//#endregion
|
|
843
|
-
export {
|
|
851
|
+
export { ASSIGNMENTS as a, guardedTables as i, ownershipSql as n, OWNERSHIP as o, assignmentSql as r, ownershipRules as t };
|