permdock 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -35
- package/dist/{schema-Bol6gqXA.js → 2020-C9VN-x8c.js} +5 -130
- package/dist/a2a/index.d.ts +6 -6
- package/dist/a2a/index.js +14 -17
- package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
- package/dist/adoption-scan-DCYe60Pi.js +107 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +1 -0
- package/dist/ai-sdk/index.d.ts +18 -35
- package/dist/ai-sdk/index.js +37 -23
- package/dist/{app-data-CV8pqqMi.js → app-data-MuyzVqX8.js} +2 -1
- package/dist/approval-header-CJACtHkv.js +23 -0
- package/dist/approval-prompt-Dg5LeAcT.js +62 -0
- package/dist/approval-prompt-DvSSPQJX.d.ts +57 -0
- package/dist/approvals/index.d.ts +5 -3
- package/dist/approvals/index.js +8 -5
- package/dist/approvals-p1PGoVjb.js +995 -0
- package/dist/arazzo-41WuzHou.js +36 -0
- package/dist/{arazzo-C4BcskbX.js → arazzo-CaUVBn7D.js} +20 -14
- package/dist/args-IIolxxKA.js +197 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +27 -0
- package/dist/audience-DabClFLQ.js +55 -0
- package/dist/auth-DRLEizbL.js +178 -0
- package/dist/authzen/index.d.ts +26 -6
- package/dist/authzen/index.js +94 -79
- package/dist/automation-BNM3TtDo.d.ts +114 -0
- package/dist/automation-DVjOckjV.js +157 -0
- package/dist/better-auth/index.d.ts +8 -6
- package/dist/better-auth/index.js +9 -8
- package/dist/better-supabase/index.d.ts +357 -30
- package/dist/better-supabase/index.js +936 -194
- package/dist/{bind-kk-uyCwH.js → bind-CAvgUuKs.js} +14 -10
- package/dist/{bind-BzqG8SMb.d.ts → bind-DOyrv6zi.d.ts} +12 -10
- package/dist/body-CH5of8Ne.js +10 -0
- package/dist/body-text-QcXok_SI.js +52 -0
- package/dist/{build-instance-DIIV9DbC.js → build-instance-Bu_eDqYw.js} +4 -3
- package/dist/{canonical-json-CFKyFOke.js → canonical-json-BC8_nMTw.js} +5 -3
- package/dist/{capability-CksyCWDH.d.ts → capability-Drg-1YhL.d.ts} +44 -25
- package/dist/{capability-txATtKV4.js → capability-N1K9IpqS.js} +68 -42
- package/dist/casbin-Cr7RLtA3.js +30 -0
- package/dist/casbin-Dyu2gnUD.js +234 -0
- package/dist/casl-64gpT-8x.js +24 -0
- package/dist/casl-DjEDhTCP.js +299 -0
- package/dist/catalog/index.d.ts +74 -2
- package/dist/catalog/index.js +51 -2
- package/dist/catalog-BoWglDPI.js +1 -0
- package/dist/catalog-DZVbrF-z.js +52 -0
- package/dist/{catalog-tZUyOfOv.js → catalog-HD2gf5mY.js} +51 -6
- package/dist/{catalog-doc-DWtg0gGR.js → catalog-doc-Cdn7-0pQ.js} +45 -23
- package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
- package/dist/chat/index.d.ts +2 -0
- package/dist/chat/index.js +224 -0
- package/dist/chat-sdk/index.d.ts +87 -0
- package/dist/chat-sdk/index.js +197 -0
- package/dist/claims-map-7oQ3ue4X.js +13 -0
- package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
- package/dist/claude-agent/index.d.ts +7 -14
- package/dist/claude-agent/index.js +24 -27
- package/dist/clerk/index.d.ts +9 -8
- package/dist/clerk/index.js +9 -14
- package/dist/cli/bin.js +36 -17
- package/dist/cli/index.d.ts +19 -5
- package/dist/cli/index.js +164 -4
- package/dist/cloud/index.d.ts +102 -63
- package/dist/cloud/index.js +239 -340
- package/dist/cloud-ytQ3uviA.js +87 -0
- package/dist/codemod-5mpSpnUY.js +37 -0
- package/dist/codemod-ua2hHy7w.js +2084 -0
- package/dist/collect-Ci6akVwH.js +55 -0
- package/dist/collect-D7eQDffE.js +1 -0
- package/dist/{collect-2SrB7lfk.js → collect-mLTY4nwb.js} +75 -11
- package/dist/command-L8EstFNm.js +6 -0
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -2
- package/dist/{compile-CtTP7eur.d.ts → compile-1bsAezL0.d.ts} +5 -3
- package/dist/{compile-CcEu2ZQ1.js → compile-CMqMx_IN.js} +11 -8
- package/dist/completion-BTFrHZhX.js +143 -0
- package/dist/config-Bj2Ixq3d.js +26 -0
- package/dist/{config-BIjREpyE.js → config-bs_kZeYq.js} +12 -7
- package/dist/{config-report--Xb9APJS.js → config-report-DSpsLpCE.js} +15 -5
- package/dist/{connection-DUsczpB5.js → connection-DjhIJSs2.js} +6 -5
- package/dist/{context-DieZVJXu.js → context-B_u3_bDJ.js} +2 -2
- package/dist/convex/index.d.ts +11 -10
- package/dist/convex/index.js +6 -4
- package/dist/create-B30OMUOJ.d.ts +107 -0
- package/dist/create-CBMmFie-.d.ts +138 -0
- package/dist/create-CGm28gyI.js +130 -0
- package/dist/create-CQWIpUG-.js +403 -0
- package/dist/{create-BBO24vj6.js → create-DCF2SvoT.js} +31 -32
- package/dist/create-D_NmPOdH.d.ts +110 -0
- package/dist/{credential-kue0qpbm.js → credential-BcoiejpY.js} +152 -31
- package/dist/credentials-DPyHmYz5.js +74 -0
- package/dist/delegation-j6-rc37o.js +286 -0
- package/dist/delivery/index.d.ts +5 -0
- package/dist/delivery/index.js +5 -0
- package/dist/delivery-DCAgNyCn.js +499 -0
- package/dist/delivery-event-xX4obB8F.js +40 -0
- package/dist/{denied-text-DfRk15uw.d.ts → denied-text-CiFQNuMV.d.ts} +5 -5
- package/dist/{describe-C3ldA9os.d.ts → describe-B5qgDvEW.d.ts} +2 -2
- package/dist/{describe-Cgkp7eP0.js → describe-BybMRY7A.js} +6 -2
- package/dist/device-BARQ8eYG.js +193 -0
- package/dist/{diff-rpqoe7Ag.js → diff-3jbx6Rm4.js} +60 -54
- package/dist/diff-CmuSD3vc.js +37 -0
- package/dist/doctor-3ZqNHX_G.js +44 -0
- package/dist/{doctor-owlv6ddh.js → doctor-BdZw53-k.js} +245 -50
- package/dist/drizzle/index.d.ts +5 -3
- package/dist/drizzle/index.js +2 -2
- package/dist/elysia/index.d.ts +16 -13
- package/dist/elysia/index.js +13 -12
- package/dist/env-DJZrPjRi.js +8 -0
- package/dist/errors-BDUwg2qC.js +27 -0
- package/dist/{errors-drnfxYtm.js → errors-BKy0EfhN.js} +5 -9
- package/dist/errors-BWXBMaKK.d.ts +11 -0
- package/dist/errors-CWTY6uih.js +143 -0
- package/dist/{errors-OD_4c__H.d.ts → errors-DH5QEGXh.d.ts} +2 -2
- package/dist/evaluate-CNClBvcj.js +1785 -0
- package/dist/evaluations-C_1vdSZc.d.ts +32 -0
- package/dist/{evaluations-C9YJsv3H.js → evaluations-beAooV0n.js} +145 -34
- package/dist/eve/index.d.ts +12 -16
- package/dist/eve/index.js +22 -28
- package/dist/event-time-CsegxXxB.js +23 -0
- package/dist/expiry-D4_YKRKD.js +52 -0
- package/dist/express/index.d.ts +13 -10
- package/dist/express/index.js +12 -11
- package/dist/fastify/index.d.ts +25 -22
- package/dist/fastify/index.js +13 -12
- package/dist/fetch-adapter-DD_gfmrC.js +120 -0
- package/dist/fetch-adapter-Dw0MTS3D.d.ts +31 -0
- package/dist/fields-OYG_JR78.js +203 -0
- package/dist/files-C5ZI27eV.js +124 -0
- package/dist/{fixtures-h2eG2OZV.js → fixtures-B7XrLeY7.js} +8 -7
- package/dist/flags-C3DktBDY.js +86 -0
- package/dist/{from-snapshot-7REQZe0H.js → from-snapshot-CTCMsFZk.js} +55 -52
- package/dist/{grantee-conditions-MQs5NIuX.js → grantee-conditions-CcXf8hMM.js} +4 -3
- package/dist/{grantee-CXY-0C87.js → grantee-hxrUsbpM.js} +16 -6
- package/dist/{grantee-match-fxYp9Nmt.js → grantee-match-DbENBHLB.js} +5 -4
- package/dist/h3/index.d.ts +37 -0
- package/dist/h3/index.js +35 -0
- package/dist/headers-BIjfMaoC.js +8 -0
- package/dist/headers-CIbDTUTK.d.ts +7 -0
- package/dist/{helpers-C9YmyBIa.js → helpers-CBYVMjwh.js} +45 -38
- package/dist/hono/index.d.ts +12 -9
- package/dist/hono/index.js +13 -12
- package/dist/hosted-B46-5LYE.js +315 -0
- package/dist/hosted-BPk51-kX.d.ts +78 -0
- package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
- package/dist/http-DXJ37RDA.js +46 -0
- package/dist/{http-BEbdMZ05.js → http-ujKm0JXE.js} +1 -2
- package/dist/{import-UHk1N7vH.js → import-CNV7I2Dw.js} +25 -20
- package/dist/{import-BFIlhetD.js → import-wyqXUyjm.js} +190 -53
- package/dist/index-B8YjkEZ4.d.ts +210 -0
- package/dist/index-C-6Letxg.d.ts +431 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/{index-BwMe94Xq.d.ts → index-Ch8LpNDp.d.ts} +34 -16
- package/dist/index-Cp_ilKi1.d.ts +91 -0
- package/dist/index-CueFnxQc.d.ts +110 -0
- package/dist/index-DuWxc5dt.d.ts +909 -0
- package/dist/index.d.ts +19 -15
- package/dist/index.js +31 -21
- package/dist/init-DeMYsnYJ.js +96 -0
- package/dist/init-GG6Jh8HD.js +95 -0
- package/dist/{instance-options-plkrsAog.d.ts → instance-options-BsVpyGIk.d.ts} +2 -2
- package/dist/instance-snapshot-BOEhcqIF.js +601 -0
- package/dist/instant-BluqDvyS.js +19 -0
- package/dist/internals-Dk8A8fX3.js +17 -0
- package/dist/{json-schema-DBkdUuCO.js → json-schema-B-vAwlde.js} +45 -6
- package/dist/json-schema-CN_iLFH2.d.ts +40 -0
- package/dist/jwt/index.d.ts +18 -11
- package/dist/jwt/index.js +156 -135
- package/dist/{kernel-CB3LrIJM.js → kernel-D0NVb1cK.js} +52 -24
- package/dist/kysely/index.d.ts +5 -3
- package/dist/kysely/index.js +2 -2
- package/dist/load-BxEtwlBS.js +1 -0
- package/dist/{load-C3_oTHyO.js → load-DFS_ry0W.js} +22 -6
- package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
- package/dist/login-Dfe5RlYk.js +68 -0
- package/dist/login-imhuwvn3.js +254 -0
- package/dist/manifest-C8IzW3Do.js +42 -0
- package/dist/manifest-dvL2XEe1.js +370 -0
- package/dist/manifest-json-CAGzls-C.js +9 -0
- package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
- package/dist/manifest-schema-C0kK_8V0.js +399 -0
- package/dist/{map-claims-AVlBaJoa.js → map-claims-D3oRmTk0.js} +13 -10
- package/dist/{map-error-BE27PYkE.js → map-error-Dr70oA_3.js} +3 -3
- package/dist/{markers-CiwAOZtO.js → markers-C6joDI3o.js} +2 -1
- package/dist/mcp/index.d.ts +22 -12
- package/dist/mcp/index.js +57 -39
- package/dist/{migrate-BwlB5-Wk.js → migrate-CIq4-E9F.js} +53 -14
- package/dist/nest/index.d.ts +12 -9
- package/dist/nest/index.js +15 -14
- package/dist/next/index.d.ts +18 -21
- package/dist/next/index.js +13 -11
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +2 -2
- package/dist/node/index.d.ts +10 -10
- package/dist/node/index.js +7 -9
- package/dist/{ocsf-uYFXGVw2.js → ocsf-BFp-PZZA.js} +22 -11
- package/dist/openai/index.d.ts +9 -16
- package/dist/openai/index.js +17 -24
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-DBzHQbov.js → openapi-B7HZ0f-r.js} +8 -8
- package/dist/openapi-C4dDTVEd.js +138 -0
- package/dist/{openapi-BJdBmYDG.js → openapi-DD1SV-5-.js} +10 -9
- package/dist/operation-findings-DgATDpvh.js +36 -0
- package/dist/operations-X0VM7F3m.d.ts +59 -0
- package/dist/orpc/index.d.ts +13 -10
- package/dist/orpc/index.js +20 -15
- package/dist/otel/index.d.ts +3 -2
- package/dist/otel/index.js +30 -7
- package/dist/output-1im__-08.js +88 -0
- package/dist/output-files-B3IZJksC.js +1 -0
- package/dist/{output-files-CgIsvdSa.js → output-files-DhRBBE4n.js} +24 -26
- package/dist/{permission-keys-B26jeyUv.js → ownership-DqaJNJPn.js} +1045 -153
- package/dist/{package-root-DtNnpmUd.js → package-root-Dk_ETdkp.js} +1 -1
- package/dist/{parse-CVFQHk6c.js → parse-Cxq0WxrA.js} +2 -2
- package/dist/{parse-local-manifest-DvYUR0QL.d.ts → parse-local-manifest-5ODwqu8b.d.ts} +1 -1
- package/dist/{parse-local-manifest-C-q02Pal.js → parse-local-manifest-CVSs6J9I.js} +1 -1
- package/dist/parse-request-D_tUXG6M.js +26 -0
- package/dist/paths-DXSHAMIa.js +42 -0
- package/dist/pdp/index.d.ts +7 -3
- package/dist/pdp/index.js +45 -26
- package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
- package/dist/peer-CRvC7Hg1.js +1 -0
- package/dist/permdock-BrAscV28.js +868 -0
- package/dist/permdock-SV8ZINYT.d.ts +502 -0
- package/dist/{permission-registry-BsblpB-z.js → permission-registry-Br4jSNVi.js} +3 -3
- package/dist/permissions-B-wCP5ZC.js +30 -0
- package/dist/{permissions-CCJ6lmxB.js → permissions-BPxlwVhN.js} +4 -3
- package/dist/{permissions-COH-KRgT.d.ts → permissions-u_KHg-sN.d.ts} +1 -2
- package/dist/policies-DP2nqRoF.js +295 -0
- package/dist/{policy-ur0BBK_e.d.ts → policy-77FSAq_V.d.ts} +2 -2
- package/dist/{hosted-CBHgkzDl.js → policy-DbM5YYtV.js} +17 -273
- package/dist/policy-from-catalog-C2gMa2Dj.js +450 -0
- package/dist/policy-module-CTlwMvLI.js +89 -0
- package/dist/{policy-types-sp9P1iIV.d.ts → policy-types-n0uRTsW8.d.ts} +460 -632
- package/dist/postgrest-CEgRbefE.d.ts +100 -0
- package/dist/postgrest-DAjUkF9w.js +575 -0
- package/dist/{powersync-cZe4kzhL.js → powersync-BkWWCS1K.js} +23 -24
- package/dist/powersync-DISt6SUe.js +62 -0
- package/dist/powersync-MMJ2mI2q.js +1 -0
- package/dist/preset/index.d.ts +146 -0
- package/dist/preset/index.js +1 -0
- package/dist/preset-DZdU0tne.js +1768 -0
- package/dist/principal-source-pHneukrx.js +103 -0
- package/dist/prisma/index.d.ts +6 -3
- package/dist/prisma/index.js +2 -2
- package/dist/{problem-CnWraVhC.js → problem-CTviyH5L.js} +13 -23
- package/dist/{problem-details-rIIo8B2C.d.ts → problem-details-B45V5d2b.d.ts} +1 -1
- package/dist/{problem-details-DECUnXAn.js → problem-details-cn5X30gZ.js} +43 -40
- package/dist/problem-types-CJ2325SZ.js +5 -0
- package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
- package/dist/{protected-BN7BUdY1.js → protected-BlF0F6wL.js} +21 -4
- package/dist/{protected-B3_P7xuw.d.ts → protected-RFGpakN9.d.ts} +16 -8
- package/dist/{provider-Bi-0HNL3.js → provider-B1al-AZz.js} +2 -2
- package/dist/{provider-kwHC9CZD.d.ts → provider-DILL9oPK.d.ts} +1 -1
- package/dist/{provider-store-Ctt-81Ni.js → provider-store-DM6DNcta.js} +2 -2
- package/dist/pull-CvpINcMK.js +1 -0
- package/dist/pull-jEwbf80J.js +256 -0
- package/dist/push-BD4f15co.js +116 -0
- package/dist/pushed-policy-CqtxkV4G.js +238 -0
- package/dist/pushed-policy-DCW91KQU.d.ts +57 -0
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +6 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -2
- package/dist/react-native/index.d.ts +24 -72
- package/dist/react-native/index.js +29 -25
- package/dist/{refs-f3imMjNW.js → refs-K6eWUJ0H.js} +1 -1
- package/dist/{relation-walk-BXnlLtg2.js → relation-walk-3OyDBF0S.js} +2 -2
- package/dist/{relations-CvmSpG67.js → relations-BnCWnJGI.js} +3 -3
- package/dist/renames-CGj7YwcQ.js +175 -0
- package/dist/{replay-COpmZUfx.d.ts → replay-Bkm-Ne4l.d.ts} +1 -1
- package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
- package/dist/{request-context-BVM7aWNs.js → request-context-C81Pip9n.js} +3 -3
- package/dist/request-schema-CMreVuFM.js +148 -0
- package/dist/resend/index.d.ts +126 -0
- package/dist/resend/index.js +379 -0
- package/dist/{resource-relations-D1Un3pcz.js → resource-relations-D_jw0BFx.js} +1 -1
- package/dist/{retire-BFcxL6un.js → retire-CALjIQ7e.js} +2 -2
- package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
- package/dist/rls-BAqP4q66.js +336 -0
- package/dist/{rls-BEktUaqg.js → rls-DTvYd-lI.js} +288 -690
- package/dist/{role-builder-B4GbKM-i.js → role-builder-uWQZ4v84.js} +14 -207
- package/dist/{rpc-nYC3cR_u.js → rpc-VSl5ylaQ.js} +7 -7
- package/dist/run-CUKuz0A8.js +723 -0
- package/dist/run-DDwUaJDU.js +1 -0
- package/dist/run-ticket-DocwaTKn.d.ts +124 -0
- package/dist/run-ticket-sxFI6LAk.js +337 -0
- package/dist/{runtime-D_tMXnsq.js → runtime-D3VTAiCA.js} +1 -1
- package/dist/{saas-DtaA4JuW.js → saas-BCXRuXca.js} +16 -14
- package/dist/scan-qvOe0hS3.js +29 -0
- package/dist/{schema-DjQBcAP9.js → schema-B4v0Qrac.js} +102 -8
- package/dist/schema-yLuBWWsh.js +130 -0
- package/dist/scim/index.d.ts +2 -2
- package/dist/scim/index.js +1 -2
- package/dist/{scim-vFR1O5oG.js → scim-ClHtpyV3.js} +208 -76
- package/dist/{scopes-tvpa34ep.js → scopes-CUPSjo9q.js} +12 -7
- package/dist/server/index.d.ts +12 -143
- package/dist/server/index.js +12 -9
- package/dist/server-C3WXAHZj.js +453 -0
- package/dist/{server-types-DdqBwZN_.d.ts → server-types-CKQiCnON.d.ts} +51 -68
- package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
- package/dist/{shared-1uhI-e-b.js → shared-BAK8t4Uo.js} +20 -9
- package/dist/{shared-JhMeOsDF.js → shared-DvvFzYXe.js} +8 -9
- package/dist/signals-D3NcTKUS.js +32 -0
- package/dist/sink-4-eQIRWf.d.ts +225 -0
- package/dist/sink-BEZSlMS_.js +957 -0
- package/dist/{skills-BZdRQkxs.js → skills-B6lVdD_W.js} +13 -6
- package/dist/skills-CTYwkl44.js +51 -0
- package/dist/snapshot-B7DkYg6g.js +269 -0
- package/dist/{snapshot-cache-Cod40D-X.js → snapshot-cache-DZHQ0KF6.js} +11 -5
- package/dist/{snapshot-cache-J5T7t_vD.d.ts → snapshot-cache-PXI-vGpY.d.ts} +7 -5
- package/dist/solid/index.d.ts +11 -12
- package/dist/solid/index.js +7 -7
- package/dist/solid-start/index.d.ts +52 -0
- package/dist/solid-start/index.js +49 -0
- package/dist/{sources-Dljy06zL.d.ts → sources-Xc4zkszI.d.ts} +7 -13
- package/dist/{sources-B1-b-R0t.js → sources-vs5dGOsS.js} +125 -119
- package/dist/{sql-t8teWOfo.js → sql-B5T1IL83.js} +13 -1
- package/dist/src-Cu2m75uo.js +1860 -0
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +384 -47
- package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
- package/dist/standard-webhooks-rbD7LS6h.d.ts +88 -0
- package/dist/storage-C2bWdvJH.js +132 -0
- package/dist/{store-ILkLUj_o.js → store-BDutJkDL.js} +40 -53
- package/dist/{store-options-CbUHDoSe.js → store-options-u0ycEe8s.js} +26 -21
- package/dist/{stores-Cxz_80g-.d.ts → stores-BUGNbJTt.d.ts} +7 -8
- package/dist/{stores-BHXdSbw9.js → stores-CGhM8BN_.js} +2 -2
- package/dist/{stream-Dwsg6WwE.d.ts → stream-CeCMJmBB.d.ts} +2 -2
- package/dist/{stream-BMui98KK.js → stream-CvdUS9V1.js} +9 -2
- package/dist/style-DiKWnAvn.js +1 -0
- package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
- package/dist/subject-BGoM5o8B.js +17 -0
- package/dist/supabase/index.d.ts +23 -106
- package/dist/supabase/index.js +5 -4
- package/dist/supabase/middleware.d.ts +7 -6
- package/dist/supabase/middleware.js +13 -11
- package/dist/supabase-By_frk3U.js +92 -0
- package/dist/{supabase-hook-Cva3ikXz.js → supabase-hook-BMoEhZir.js} +960 -64
- package/dist/supabase-hook-CCw5m35C.js +1 -0
- package/dist/{supabase-C1TWlnYt.js → supabase-x5JtR2tc.js} +99 -206
- package/dist/support-session-C6xJQYut.js +158 -0
- package/dist/svelte/Protected.svelte +4 -4
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +10 -10
- package/dist/svelte/runtime.d.ts +3 -3
- package/dist/svelte/runtime.js +3 -3
- package/dist/svelte/source.d.ts +7 -7
- package/dist/svelte/source.js +4 -4
- package/dist/sveltekit/index.d.ts +51 -0
- package/dist/sveltekit/index.js +27 -0
- package/dist/tanstack-start/index.d.ts +51 -0
- package/dist/tanstack-start/index.js +40 -0
- package/dist/target-Bw37TFNA.js +51 -0
- package/dist/{tenancy-DshjqY0n.js → tenancy-DN2QAx9z.js} +5 -5
- package/dist/terminal/index.d.ts +11 -9
- package/dist/terminal/index.js +23 -287
- package/dist/testing/index.d.ts +311 -38
- package/dist/testing/index.js +3475 -201
- package/dist/testing/saas/index.d.ts +9 -8
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -2
- package/dist/text-BMFRfBqc.js +8 -0
- package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
- package/dist/thenable-yD7SabqG.js +31 -0
- package/dist/time-DkWjdL98.js +71 -0
- package/dist/{token-Bx9qy30p.js → token-BLZcjQgt.js} +15 -6
- package/dist/{tool-refusal-CQ7OEia9.js → tool-refusal-CMLN5xhn.js} +2 -2
- package/dist/trpc/index.d.ts +12 -9
- package/dist/trpc/index.js +16 -14
- package/dist/types-8RnwUAvE.d.ts +107 -0
- package/dist/{store-9KO4BlkB.d.ts → types-B3_ooZgZ.d.ts} +100 -75
- package/dist/{types-Br9IXUBG.d.ts → types-BdtSDbBO.d.ts} +77 -17
- package/dist/types-BtHtcKMr.d.ts +157 -0
- package/dist/{types-CqrjB9o6.d.ts → types-BzzaHxt3.d.ts} +23 -11
- package/dist/types-C791HFu9.d.ts +332 -0
- package/dist/types-C863OQiR.d.ts +57 -0
- package/dist/{types-ByUo56un.d.ts → types-CVZSNocA.d.ts} +23 -22
- package/dist/{types-DHrmBJLH.d.ts → types-CX7AvRqP.d.ts} +7 -7
- package/dist/types-C_IiEs64.d.ts +65 -0
- package/dist/{types-7rJVLWe5.d.ts → types-Dfv0aTER.d.ts} +63 -11
- package/dist/types-DqzsnlSA.js +67 -0
- package/dist/{types-DpWfQdSh.d.ts → types-Druu9wLM.d.ts} +46 -11
- package/dist/{types-CIlTojhq.d.ts → types-_WhUP7ud.d.ts} +9 -6
- package/dist/{types-CUcFZfpU.d.ts → types-lUlG3TVd.d.ts} +2 -1
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CRVl9V3t.js +32 -0
- package/dist/usage-CZpSQP6L.js +1 -0
- package/dist/{usage-CG9nOYZw.js → usage-SveTRTyr.js} +54 -11
- package/dist/{validation-error-BSSw0l_8.d.ts → validation-error-DHUrAkku.d.ts} +1 -1
- package/dist/{validity-dVrUEPJt.js → validity-CZ7-y0Cj.js} +32 -15
- package/dist/{verifier-DMr_Zmb_.js → verifier-DHuh-okk.js} +18 -7
- package/dist/{verify-BztUB3GX.js → verify-BaAKx7t6.js} +645 -50
- package/dist/{version-BEogC-rB.js → version-CEbQ5UeM.js} +4 -3
- package/dist/version-DXkYUISx.js +1 -0
- package/dist/{views-CJsrBS1f.js → views-DTIIO-oQ.js} +22 -5
- package/dist/{permission-types-Dg39z9Kv.d.ts → vocabulary-CN8H_9UF.d.ts} +67 -2
- package/dist/vue/index.d.ts +9 -10
- package/dist/vue/index.js +8 -8
- package/dist/waiter-B986w1oK.d.ts +36 -0
- package/dist/waiter-BGUtGYoU.js +67 -0
- package/dist/watch--ovlPOc0.js +1 -0
- package/dist/{watch-zeucB7eF.js → watch-vea54PRw.js} +44 -5
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +3 -3
- package/dist/where-scope-C9vfEphl.js +306 -0
- package/dist/who-can-DWBAcaCQ.js +364 -0
- package/dist/wire-delegation-Cv3W2eaM.js +36 -0
- package/dist/wire-denial-C7PaVYEM.js +27 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.js +223 -0
- package/dist/workflow-sdk/index.d.ts +56 -0
- package/dist/workflow-sdk/index.js +78 -0
- package/dist/{wrap-Cr93qqdP.d.ts → wrap-C5jC6sKI.d.ts} +2 -1
- package/dist/{wrap-DWsU_5Bd.js → wrap-gX61EtpB.js} +7 -6
- package/package.json +136 -37
- package/schemas/access-event-v1.json +118 -0
- package/schemas/approval-request-v1.json +34 -1
- package/schemas/approval-signature-v1.json +94 -0
- package/schemas/automation-event-v1.json +113 -0
- package/schemas/capability-v1.json +84 -0
- package/schemas/catalog-event-v1.json +87 -0
- package/schemas/catalog-v1.json +123 -12
- package/schemas/cli-commands-v1.json +164 -0
- package/schemas/cli-result-v1.json +96 -0
- package/schemas/cloud-api-v1.json +5407 -0
- package/schemas/connector-v1.json +408 -0
- package/schemas/credential-event-v1.json +81 -0
- package/schemas/credential-v1.json +134 -0
- package/schemas/decision-event-v1.json +288 -0
- package/schemas/delivery-event-v1.json +117 -0
- package/schemas/directory-event-v1.json +85 -0
- package/schemas/evidence-export-v1.json +54 -0
- package/schemas/membership-event-v1.json +93 -0
- package/schemas/policy-v1.json +68 -0
- package/schemas/pushed-policy-v1.json +236 -0
- package/schemas/run-v1.json +162 -0
- package/schemas/snapshot-v1.json +138 -0
- package/schemas/supabase-claims-v1.json +4 -4
- package/schemas/supabase-manifest-v1.json +5 -1
- package/schemas/support-session-v1.json +111 -0
- package/skills/permdock/SKILL.md +16 -12
- package/skills/permdock-agents/SKILL.md +40 -23
- package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
- package/skills/permdock-agents/references/runtimes.md +22 -18
- package/skills/permdock-approvals/SKILL.md +29 -13
- package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +5 -5
- package/skills/permdock-credentials/references/tokens-keys-links.md +14 -10
- package/skills/permdock-data/SKILL.md +6 -6
- package/skills/permdock-data/references/queries-and-rls.md +10 -9
- package/skills/permdock-data/references/relationships.md +3 -3
- package/skills/permdock-tenancy/SKILL.md +20 -7
- package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +6 -4
- package/skills/permdock-wire/SKILL.md +17 -11
- package/skills/permdock-wire/references/adapters.md +139 -51
- package/skills/permdock-wire/references/policy-options.md +5 -5
- package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
- package/dist/approvals-DZL76lKW.js +0 -240
- package/dist/arazzo-CD8d0WLb.js +0 -54
- package/dist/catalog-6yWAX6Ps.js +0 -2
- package/dist/catalog-WHG4kBq7.js +0 -48
- package/dist/cloud-C7bSDgX4.js +0 -50
- package/dist/cloud-PeBN3IEn.js +0 -118
- package/dist/collect-Bw2Jdq1q.js +0 -2
- package/dist/collect-CQx4hX4N.js +0 -83
- package/dist/commands-Cpip4ULx.js +0 -97
- package/dist/config-DLt6BvV7.js +0 -36
- package/dist/diff-Cnghh_Ip.js +0 -48
- package/dist/doctor-CuVJvhV_.js +0 -53
- package/dist/duration-xK2jGFVc.js +0 -25
- package/dist/errors-CWfoNg9N.js +0 -47
- package/dist/errors-DRnGZHzF.js +0 -30
- package/dist/expiry-BoBJ8Tvc.js +0 -7
- package/dist/fields-fwfXN01n.js +0 -1016
- package/dist/headers-BZSDieNT.js +0 -6
- package/dist/headers-Cf-zWvsO.d.ts +0 -6
- package/dist/ids-DC4V4CFu.js +0 -18
- package/dist/index-DgM7w96K.d.ts +0 -6
- package/dist/index-Dz7didpC.d.ts +0 -201
- package/dist/index-UdVQgq48.d.ts +0 -550
- package/dist/load-B_shHrmd.js +0 -2
- package/dist/manifest-BngWKryL.js +0 -770
- package/dist/may-use-BOn_dR_w.js +0 -78
- package/dist/openapi-DcQ1Kg40.js +0 -164
- package/dist/ownership-BgnbOJFT.js +0 -851
- package/dist/permdock-L26udHSk.js +0 -3646
- package/dist/powersync-BFSbSElQ.js +0 -62
- package/dist/powersync-DCDgZgfZ.js +0 -2
- package/dist/rls-55DQTuf5.js +0 -250
- package/dist/run-DMcNW_8T.js +0 -174
- package/dist/server-ZYEHtNHS.js +0 -268
- package/dist/sink-BTtmfPnE.js +0 -141
- package/dist/sink-CPN4zLfs.d.ts +0 -104
- package/dist/skills-Dle4ZoLF.js +0 -56
- package/dist/snapshot-Dc9CHPME.js +0 -1078
- package/dist/src-Bb7no-hS.js +0 -639
- package/dist/subject-C6dLxqHU.js +0 -24
- package/dist/supabase-Dbqr141s.js +0 -83
- package/dist/supabase-hook-BULg7Ut_.js +0 -2
- package/dist/thenable-QSmvt-vZ.js +0 -65
- package/dist/types-C0dee0Qf.d.ts +0 -197
- package/dist/types-DvupZE0w.d.ts +0 -9
- package/dist/usage-4G6t643F.js +0 -42
- package/dist/usage-iFLJ0-rr.js +0 -2
- package/dist/vocabulary-hhEFLFoL.d.ts +0 -65
- package/dist/watch-C8TpsAl4.js +0 -2
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
import { t as compact } from "./compact-CAf557ZB.js";
|
|
2
|
+
import { n as freezeDeep } from "./freeze-CrU8nQeX.js";
|
|
3
|
+
import { a as ownRecord } from "./paths-DXSHAMIa.js";
|
|
4
|
+
import { a as problemType, o as statusProblem } from "./problem-details-cn5X30gZ.js";
|
|
5
|
+
import { a as isId } from "./expiry-D4_YKRKD.js";
|
|
6
|
+
import { d as isoNow, f as nowMillis, g as secondsOf, i as dateAt } from "./time-DkWjdL98.js";
|
|
7
|
+
import { t as canonicalJson } from "./canonical-json-BC8_nMTw.js";
|
|
8
|
+
import { r as sha256Digest } from "./sha256-D9ihqmvd.js";
|
|
9
|
+
import { s as rootMembershipId } from "./scopes-CUPSjo9q.js";
|
|
10
|
+
import { o as flattenGrantee } from "./grantee-hxrUsbpM.js";
|
|
11
|
+
import { c as isApprovalError, s as ApprovalError } from "./types-DqzsnlSA.js";
|
|
12
|
+
import { a as approvalPageSize, c as listAllApprovals, n as assertApprover, o as decodeApprovalCursor, r as matchesApprovers } from "./store-BDutJkDL.js";
|
|
13
|
+
import { n as readBodyText } from "./body-text-QcXok_SI.js";
|
|
14
|
+
import { r as hasCredentials } from "./http-BBoKefeS.js";
|
|
15
|
+
import { a as methodNotAllowed, c as problemResponse, d as validationProblem, u as unauthenticatedProblem } from "./problem-CTviyH5L.js";
|
|
16
|
+
import { f as approverPermissions, m as approverRelations, p as heldPermissions } from "./helpers-CBYVMjwh.js";
|
|
17
|
+
import { n as isAccessReviewError, t as AccessReviewError } from "./errors-BDUwg2qC.js";
|
|
18
|
+
import { t as bodyTooLarge } from "./body-CH5of8Ne.js";
|
|
19
|
+
//#region src/approvals/access-reviews/decide.ts
|
|
20
|
+
/** Whether `by` is named as a reviewer. A link, a feed or a contact is never a reviewer. */
|
|
21
|
+
function accessReviewerEligible(review, by, permissions = [], now) {
|
|
22
|
+
const principal = by.principal;
|
|
23
|
+
if (principal === null || principal.kind === "link" || principal.kind === "feed" || principal.kind === "contact") return false;
|
|
24
|
+
return matchesApprovers(review.reviewers, by, review.tenant, dateAt(now).getTime() / 1e3, {
|
|
25
|
+
relations: /* @__PURE__ */ new Set(),
|
|
26
|
+
permissions: new Set(permissions)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The decision to persist, or throws. A reviewer cannot decide a holding
|
|
31
|
+
* of their own principal. `holder()` matches only through `permissions`.
|
|
32
|
+
*/
|
|
33
|
+
function decideAccessReviewItem(review, item, verdict, at) {
|
|
34
|
+
const now = dateAt(at);
|
|
35
|
+
if (review.status !== "open") throw new AccessReviewError("review-closed", "access review is closed");
|
|
36
|
+
if (item.review !== review.id) throw new AccessReviewError("item-not-found", "item does not belong to the review");
|
|
37
|
+
if (item.status !== "pending") throw new AccessReviewError("item-not-pending", "item is already decided");
|
|
38
|
+
const principal = verdict.by.principal;
|
|
39
|
+
if (principal === null) throw new AccessReviewError("reviewer-unauthenticated", "reviewer must be authenticated");
|
|
40
|
+
if (principal.id === item.principal) throw new AccessReviewError("reviewer-is-holder", "reviewer cannot decide their own access");
|
|
41
|
+
if (!accessReviewerEligible(review, verdict.by, verdict.permissions ?? [], { epochMilliseconds: now.getTime() })) throw new AccessReviewError("reviewer-not-eligible", "reviewer is not eligible for this review");
|
|
42
|
+
return {
|
|
43
|
+
status: verdict.status,
|
|
44
|
+
decidedBy: principal.id,
|
|
45
|
+
decidedAt: now.toISOString(),
|
|
46
|
+
...verdict.note === void 0 ? {} : { note: verdict.note }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/approvals/access-reviews/handler.ts
|
|
51
|
+
function problem$1(status, detail) {
|
|
52
|
+
return problemResponse(statusProblem(status, detail));
|
|
53
|
+
}
|
|
54
|
+
function json$1(status, body) {
|
|
55
|
+
return new Response(JSON.stringify(body), {
|
|
56
|
+
status,
|
|
57
|
+
headers: { "content-type": "application/json" }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** The route a path names, the methods it allows, or `undefined` for an unknown path. */
|
|
61
|
+
function parseRoute$1(url, method) {
|
|
62
|
+
const parts = url.pathname.replace(/\/+$/u, "").split("/").filter(Boolean);
|
|
63
|
+
const last = parts.at(-1);
|
|
64
|
+
const prev = parts.at(-2);
|
|
65
|
+
const review = parts.at(-3);
|
|
66
|
+
if (last === void 0) return;
|
|
67
|
+
if (last === "items" && prev !== void 0) return method === "GET" ? {
|
|
68
|
+
kind: "items",
|
|
69
|
+
id: decodeURIComponent(prev)
|
|
70
|
+
} : { allow: "GET" };
|
|
71
|
+
if (last === "close" && prev !== void 0) return method === "POST" ? {
|
|
72
|
+
kind: "close",
|
|
73
|
+
id: decodeURIComponent(prev)
|
|
74
|
+
} : { allow: "POST" };
|
|
75
|
+
if ((last === "keep" || last === "revoke" || last === "applied") && prev !== void 0 && review !== void 0) return method === "POST" ? {
|
|
76
|
+
kind: last,
|
|
77
|
+
id: decodeURIComponent(review),
|
|
78
|
+
item: decodeURIComponent(prev)
|
|
79
|
+
} : { allow: "POST" };
|
|
80
|
+
return method === "GET" ? {
|
|
81
|
+
kind: "get",
|
|
82
|
+
id: decodeURIComponent(last)
|
|
83
|
+
} : { allow: "GET" };
|
|
84
|
+
}
|
|
85
|
+
async function resolveSubject$1(request, resolve) {
|
|
86
|
+
try {
|
|
87
|
+
return await resolve(request) ?? null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const MAX_NOTE = 2e3;
|
|
93
|
+
const MAX_NOTE_BODY_BYTES = 16384;
|
|
94
|
+
async function readNote$1(request) {
|
|
95
|
+
if (!(request.headers.get("content-type") ?? "").includes("application/json")) return;
|
|
96
|
+
const read = await readBodyText(request, MAX_NOTE_BODY_BYTES);
|
|
97
|
+
if (!read.ok) return;
|
|
98
|
+
try {
|
|
99
|
+
const body = JSON.parse(read.text);
|
|
100
|
+
if (body !== null && typeof body === "object" && "note" in body && typeof body.note === "string" && body.note.length <= MAX_NOTE) return body.note;
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function findItem(store, review, id) {
|
|
106
|
+
let cursor;
|
|
107
|
+
for (let page = 0; page < 1e3; page += 1) {
|
|
108
|
+
const listed = await store.items(cursor === void 0 ? {
|
|
109
|
+
review,
|
|
110
|
+
limit: 200
|
|
111
|
+
} : {
|
|
112
|
+
review,
|
|
113
|
+
limit: 200,
|
|
114
|
+
cursor
|
|
115
|
+
});
|
|
116
|
+
const found = listed.items.find((entry) => entry.id === id);
|
|
117
|
+
if (found !== void 0 || listed.next === void 0) return found;
|
|
118
|
+
cursor = listed.next;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function mapError$1(error, credentials) {
|
|
122
|
+
if (!isAccessReviewError(error)) return problem$1(500, "access review store failed");
|
|
123
|
+
if (error.code === "review-not-found" || error.code === "item-not-found") return problem$1(404, error.message);
|
|
124
|
+
if (error.code === "review-closed" || error.code === "item-not-pending" || error.code === "not-revoked" || error.code === "already-applied") return problem$1(409, error.message);
|
|
125
|
+
if (error.code === "reviewer-unauthenticated") return unauthenticatedProblem(credentials);
|
|
126
|
+
return problemResponse({
|
|
127
|
+
type: problemType("denied"),
|
|
128
|
+
title: "Permission denied",
|
|
129
|
+
status: 403,
|
|
130
|
+
detail: error.message
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* HTTP for one access-review store: `GET` a review and its items, `POST`
|
|
135
|
+
* `.../keep`, `.../revoke`, `.../applied` and `.../close`. A caller who is
|
|
136
|
+
* not a reviewer and did not open the review gets 404.
|
|
137
|
+
*/
|
|
138
|
+
function accessReviewsHandler(store, options) {
|
|
139
|
+
return async (request) => {
|
|
140
|
+
const url = new URL(request.url);
|
|
141
|
+
const route = parseRoute$1(url, request.method);
|
|
142
|
+
if (route === void 0) return problem$1(404, "unknown access review route");
|
|
143
|
+
if ("allow" in route) return methodNotAllowed(route.allow);
|
|
144
|
+
const subject = await resolveSubject$1(request, options.subject);
|
|
145
|
+
const principal = subject?.principal ?? null;
|
|
146
|
+
if (subject === null || principal === null) return unauthenticatedProblem(hasCredentials(request));
|
|
147
|
+
try {
|
|
148
|
+
const review = await store.get(route.id);
|
|
149
|
+
if (review === null) return problem$1(404, "access review was not found");
|
|
150
|
+
let held = [];
|
|
151
|
+
if (options.permdockFor !== void 0) held = approverPermissions({
|
|
152
|
+
v: 1,
|
|
153
|
+
id: review.id,
|
|
154
|
+
token: review.id,
|
|
155
|
+
permission: review.permissions[0] ?? "",
|
|
156
|
+
scope: "",
|
|
157
|
+
resource: {
|
|
158
|
+
type: "access-review",
|
|
159
|
+
id: review.id
|
|
160
|
+
},
|
|
161
|
+
subject: { principal: {
|
|
162
|
+
id: review.createdBy,
|
|
163
|
+
roles: [],
|
|
164
|
+
tenant: review.tenant
|
|
165
|
+
} },
|
|
166
|
+
detail: "",
|
|
167
|
+
createdAt: review.createdAt,
|
|
168
|
+
expiresAt: review.dueAt,
|
|
169
|
+
status: "pending",
|
|
170
|
+
approvers: { by: review.reviewers }
|
|
171
|
+
}, await options.permdockFor(subject, review.tenant));
|
|
172
|
+
if (review.createdBy !== principal.id && !accessReviewerEligible(review, subject, held)) return problem$1(404, "access review was not found");
|
|
173
|
+
if (route.kind === "get") return json$1(200, review);
|
|
174
|
+
if (route.kind === "items") {
|
|
175
|
+
const limit = url.searchParams.get("limit");
|
|
176
|
+
const cursor = url.searchParams.get("cursor");
|
|
177
|
+
const status = url.searchParams.get("status");
|
|
178
|
+
return json$1(200, await store.items(compact({
|
|
179
|
+
review: route.id,
|
|
180
|
+
limit: limit === null ? void 0 : Number(limit),
|
|
181
|
+
cursor: cursor ?? void 0,
|
|
182
|
+
status: status === "pending" || status === "kept" || status === "revoked" ? status : void 0
|
|
183
|
+
})));
|
|
184
|
+
}
|
|
185
|
+
if (route.kind === "close") return json$1(200, await store.close(route.id));
|
|
186
|
+
const item = await findItem(store, route.id, route.item);
|
|
187
|
+
if (item === void 0) return problem$1(404, "item was not found");
|
|
188
|
+
if (route.kind === "applied") return json$1(200, await store.markApplied(route.id, route.item, isoNow()));
|
|
189
|
+
const note = await readNote$1(request);
|
|
190
|
+
const decision = decideAccessReviewItem(review, item, {
|
|
191
|
+
status: route.kind === "keep" ? "kept" : "revoked",
|
|
192
|
+
by: subject,
|
|
193
|
+
...note === void 0 ? {} : { note },
|
|
194
|
+
permissions: held
|
|
195
|
+
});
|
|
196
|
+
return json$1(200, await store.decide(route.id, route.item, decision));
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return mapError$1(error, hasCredentials(request));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/approvals/access-reviews/start.ts
|
|
204
|
+
function itemId(review, principal, permission) {
|
|
205
|
+
return `${review}:${principal}:${permission}`;
|
|
206
|
+
}
|
|
207
|
+
function populationComplete(policy, keys) {
|
|
208
|
+
if (policy === void 0) return false;
|
|
209
|
+
for (const grant of policy.grants) {
|
|
210
|
+
if (grant.effect !== "allow" || !keys.has(grant.permission.key)) continue;
|
|
211
|
+
if (!enumerable(grant)) return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
function enumerable(grant) {
|
|
216
|
+
for (const item of flattenGrantee(grant.to)) {
|
|
217
|
+
if (item.kind === "plan" || item.kind === "relation" || item.kind === "inherit") return false;
|
|
218
|
+
if (item.kind === "role" && item.scope === "global") return false;
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
async function fromMembers(tenant, keys, options) {
|
|
223
|
+
const source = options.memberships;
|
|
224
|
+
const permdockFor = options.permdockFor;
|
|
225
|
+
const keySet = new Set(keys.map((permission) => permission.key));
|
|
226
|
+
if (source?.list === void 0 || permdockFor === void 0) return {
|
|
227
|
+
holders: [],
|
|
228
|
+
complete: false
|
|
229
|
+
};
|
|
230
|
+
try {
|
|
231
|
+
const members = await source.list({
|
|
232
|
+
scope: options.scope ?? "tenant",
|
|
233
|
+
id: tenant
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
holders: (await Promise.all(members.map(async (member) => {
|
|
237
|
+
const instance = await permdockFor(member.principal.id);
|
|
238
|
+
const via = member.membership.roles;
|
|
239
|
+
return keys.flatMap((permission) => {
|
|
240
|
+
const decide = instance.decide;
|
|
241
|
+
return decide(permission).outcome === "granted" ? [{
|
|
242
|
+
principal: member.principal.id,
|
|
243
|
+
permission: permission.key,
|
|
244
|
+
via
|
|
245
|
+
}] : [];
|
|
246
|
+
});
|
|
247
|
+
}))).flat(),
|
|
248
|
+
complete: populationComplete(options.policy, keySet)
|
|
249
|
+
};
|
|
250
|
+
} catch {
|
|
251
|
+
return {
|
|
252
|
+
holders: [],
|
|
253
|
+
complete: false
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function itemsFrom(reviewId, holders, keys) {
|
|
258
|
+
const seen = /* @__PURE__ */ new Set();
|
|
259
|
+
const items = [];
|
|
260
|
+
for (const holder of holders) {
|
|
261
|
+
if (!keys.has(holder.permission) || holder.principal.length === 0) continue;
|
|
262
|
+
const id = itemId(reviewId, holder.principal, holder.permission);
|
|
263
|
+
if (seen.has(id)) continue;
|
|
264
|
+
seen.add(id);
|
|
265
|
+
items.push({
|
|
266
|
+
v: 1,
|
|
267
|
+
id,
|
|
268
|
+
review: reviewId,
|
|
269
|
+
principal: holder.principal,
|
|
270
|
+
permission: holder.permission,
|
|
271
|
+
via: holder.via,
|
|
272
|
+
status: "pending"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return items;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Opens a review of who holds `permissions` in the caller's active tenant.
|
|
279
|
+
* The caller must be signed in. PermDock stores the population and does not
|
|
280
|
+
* revoke anything: the application changes the source, then `markApplied`.
|
|
281
|
+
*/
|
|
282
|
+
async function startAccessReview(permdock, input, options) {
|
|
283
|
+
const principal = permdock.subject.principal;
|
|
284
|
+
const tenant = principal?.tenant;
|
|
285
|
+
if (principal === null || tenant === void 0) throw new AccessReviewError("reviewer-unauthenticated", "access review needs a tenant member");
|
|
286
|
+
if (input.permissions.length === 0 || !Number.isFinite(Date.parse(input.dueAt))) throw new TypeError("PermDock: an access review needs permissions and a due date");
|
|
287
|
+
const keys = new Set(input.permissions.map((permission) => permission.key));
|
|
288
|
+
const now = dateAt(options.now);
|
|
289
|
+
const listed = options.holders === void 0 ? await fromMembers(tenant, input.permissions, options) : {
|
|
290
|
+
holders: options.holders,
|
|
291
|
+
complete: options.complete ?? true
|
|
292
|
+
};
|
|
293
|
+
const review = {
|
|
294
|
+
v: 1,
|
|
295
|
+
id: input.id,
|
|
296
|
+
tenant,
|
|
297
|
+
permissions: [...keys],
|
|
298
|
+
reviewers: input.reviewers,
|
|
299
|
+
createdBy: principal.id,
|
|
300
|
+
createdAt: now.toISOString(),
|
|
301
|
+
dueAt: input.dueAt,
|
|
302
|
+
status: "open",
|
|
303
|
+
complete: listed.complete
|
|
304
|
+
};
|
|
305
|
+
await options.reviews.create(review, itemsFrom(input.id, listed.holders, keys));
|
|
306
|
+
return review;
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/approvals/access-reviews/store.ts
|
|
310
|
+
function position(item) {
|
|
311
|
+
return [item.principal, item.id];
|
|
312
|
+
}
|
|
313
|
+
function compare(left, right) {
|
|
314
|
+
if (left[0] !== right[0]) return left[0] < right[0] ? -1 : 1;
|
|
315
|
+
if (left[1] === right[1]) return 0;
|
|
316
|
+
return left[1] < right[1] ? -1 : 1;
|
|
317
|
+
}
|
|
318
|
+
/** Pages items in `(principal, id)` order with the approval cursor codec. */
|
|
319
|
+
function pageAccessReviewItems(matching, query) {
|
|
320
|
+
const after = query.cursor === void 0 ? void 0 : decodeApprovalCursor(query.cursor);
|
|
321
|
+
if (after === null) return { items: [] };
|
|
322
|
+
const sorted = matching.filter((item) => after === void 0 || compare(position(item), after) > 0).toSorted((left, right) => compare(position(left), position(right)));
|
|
323
|
+
const size = approvalPageSize(query.limit);
|
|
324
|
+
const items = sorted.slice(0, size);
|
|
325
|
+
const last = items.at(-1);
|
|
326
|
+
return sorted.length > size && last !== void 0 ? {
|
|
327
|
+
items,
|
|
328
|
+
next: JSON.stringify(position(last))
|
|
329
|
+
} : { items };
|
|
330
|
+
}
|
|
331
|
+
function requireOpen(review) {
|
|
332
|
+
if (review.status !== "open") throw new AccessReviewError("review-closed", "access review is closed");
|
|
333
|
+
}
|
|
334
|
+
/** In-process access reviews for tests and single-replica apps. */
|
|
335
|
+
function memoryAccessReviewStore() {
|
|
336
|
+
const records = /* @__PURE__ */ new Map();
|
|
337
|
+
const load = (id) => {
|
|
338
|
+
const recorded = records.get(id);
|
|
339
|
+
if (recorded === void 0) throw new AccessReviewError("review-not-found", "access review was not found");
|
|
340
|
+
return recorded;
|
|
341
|
+
};
|
|
342
|
+
return Object.freeze({
|
|
343
|
+
reviews: () => [...records.values()].map((recorded) => recorded.review),
|
|
344
|
+
create(review, items) {
|
|
345
|
+
if (records.has(review.id)) return;
|
|
346
|
+
const byId = /* @__PURE__ */ new Map();
|
|
347
|
+
for (const item of items) {
|
|
348
|
+
if (item.review !== review.id) throw new AccessReviewError("item-not-found", "item does not belong to the review");
|
|
349
|
+
byId.set(item.id, item);
|
|
350
|
+
}
|
|
351
|
+
records.set(review.id, {
|
|
352
|
+
review: freezeDeep(review),
|
|
353
|
+
items: byId
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
get(id) {
|
|
357
|
+
return records.get(id)?.review ?? null;
|
|
358
|
+
},
|
|
359
|
+
items(query) {
|
|
360
|
+
const recorded = records.get(query.review);
|
|
361
|
+
if (recorded === void 0) return { items: [] };
|
|
362
|
+
return pageAccessReviewItems([...recorded.items.values()].filter((item) => query.status === void 0 || item.status === query.status), query);
|
|
363
|
+
},
|
|
364
|
+
decide(reviewId, itemId, decision) {
|
|
365
|
+
const recorded = load(reviewId);
|
|
366
|
+
requireOpen(recorded.review);
|
|
367
|
+
const current = recorded.items.get(itemId);
|
|
368
|
+
if (current === void 0) throw new AccessReviewError("item-not-found", "item was not found");
|
|
369
|
+
if (current.status !== "pending") throw new AccessReviewError("item-not-pending", "item is already decided");
|
|
370
|
+
const next = freezeDeep({
|
|
371
|
+
...current,
|
|
372
|
+
status: decision.status,
|
|
373
|
+
decidedBy: decision.decidedBy,
|
|
374
|
+
decidedAt: decision.decidedAt,
|
|
375
|
+
...decision.note === void 0 ? {} : { note: decision.note }
|
|
376
|
+
});
|
|
377
|
+
recorded.items.set(itemId, next);
|
|
378
|
+
return next;
|
|
379
|
+
},
|
|
380
|
+
markApplied(reviewId, itemId, at) {
|
|
381
|
+
const recorded = load(reviewId);
|
|
382
|
+
const current = recorded.items.get(itemId);
|
|
383
|
+
if (current === void 0) throw new AccessReviewError("item-not-found", "item was not found");
|
|
384
|
+
if (current.status !== "revoked") throw new AccessReviewError("not-revoked", "only a revoked holding can be marked applied");
|
|
385
|
+
if (current.appliedAt !== void 0) throw new AccessReviewError("already-applied", "holding is already marked applied");
|
|
386
|
+
const next = freezeDeep({
|
|
387
|
+
...current,
|
|
388
|
+
appliedAt: at
|
|
389
|
+
});
|
|
390
|
+
recorded.items.set(itemId, next);
|
|
391
|
+
return next;
|
|
392
|
+
},
|
|
393
|
+
close(id) {
|
|
394
|
+
const recorded = load(id);
|
|
395
|
+
requireOpen(recorded.review);
|
|
396
|
+
const next = freezeDeep({
|
|
397
|
+
...recorded.review,
|
|
398
|
+
status: "closed"
|
|
399
|
+
});
|
|
400
|
+
records.set(id, {
|
|
401
|
+
review: next,
|
|
402
|
+
items: recorded.items
|
|
403
|
+
});
|
|
404
|
+
return next;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/approvals/delegation.ts
|
|
410
|
+
function isInstant(value) {
|
|
411
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
412
|
+
}
|
|
413
|
+
function stringList(value) {
|
|
414
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 64 || !value.every(isId)) return;
|
|
415
|
+
return [...new Set(value)];
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* A frozen copy of a delegation with only the known fields, or `undefined`
|
|
419
|
+
* when a known field has the wrong type, `until` is not after `from`, or
|
|
420
|
+
* the delegate and the delegator are the same principal.
|
|
421
|
+
*/
|
|
422
|
+
function parseApprovalDelegation(input) {
|
|
423
|
+
const value = ownRecord(input);
|
|
424
|
+
if (value === void 0) return;
|
|
425
|
+
if (!isId(value["id"]) || !isId(value["delegator"]) || !isId(value["delegate"]) || value["delegator"] === value["delegate"]) return;
|
|
426
|
+
if (!isInstant(value["from"]) || !isInstant(value["until"])) return;
|
|
427
|
+
if (Date.parse(value["from"]) >= Date.parse(value["until"])) return;
|
|
428
|
+
if (value["tenant"] !== void 0 && !isId(value["tenant"])) return;
|
|
429
|
+
const permissions = value["permissions"] === void 0 ? void 0 : stringList(value["permissions"]);
|
|
430
|
+
if (value["permissions"] !== void 0 && permissions === void 0) return;
|
|
431
|
+
const resources = value["resources"] === void 0 ? void 0 : stringList(value["resources"]);
|
|
432
|
+
if (value["resources"] !== void 0 && resources === void 0) return;
|
|
433
|
+
const tenant = value["tenant"];
|
|
434
|
+
return freezeDeep({
|
|
435
|
+
id: value["id"],
|
|
436
|
+
delegator: value["delegator"],
|
|
437
|
+
delegate: value["delegate"],
|
|
438
|
+
...typeof tenant === "string" ? { tenant } : {},
|
|
439
|
+
...permissions === void 0 ? {} : { permissions },
|
|
440
|
+
...resources === void 0 ? {} : { resources },
|
|
441
|
+
from: value["from"],
|
|
442
|
+
until: value["until"]
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/** In-process delegations. An entry that does not parse is dropped. */
|
|
446
|
+
function memoryApprovalDelegationSource(entries) {
|
|
447
|
+
const stored = entries.flatMap((entry) => {
|
|
448
|
+
const parsed = parseApprovalDelegation(entry);
|
|
449
|
+
return parsed === void 0 ? [] : [parsed];
|
|
450
|
+
});
|
|
451
|
+
return Object.freeze({ delegationsFor(query) {
|
|
452
|
+
const at = Date.parse(query.at);
|
|
453
|
+
if (!Number.isFinite(at)) return [];
|
|
454
|
+
return stored.filter((entry) => {
|
|
455
|
+
if (entry.delegate !== query.delegate) return false;
|
|
456
|
+
if (query.tenant === void 0) {
|
|
457
|
+
if (entry.tenant !== void 0) return false;
|
|
458
|
+
} else if (entry.tenant !== void 0 && entry.tenant !== query.tenant) return false;
|
|
459
|
+
return Date.parse(entry.from) <= at && at < Date.parse(entry.until);
|
|
460
|
+
});
|
|
461
|
+
} });
|
|
462
|
+
}
|
|
463
|
+
function inTenant(subject, tenant) {
|
|
464
|
+
if (tenant === void 0) return true;
|
|
465
|
+
const principal = subject.principal;
|
|
466
|
+
if (principal === null) return false;
|
|
467
|
+
if (principal.tenant === tenant) return true;
|
|
468
|
+
return (principal.memberships ?? []).some((membership) => rootMembershipId(membership) === tenant);
|
|
469
|
+
}
|
|
470
|
+
function covers(entry, request, delegate, tenant, at) {
|
|
471
|
+
if (entry.delegate !== delegate || entry.delegator === delegate) return false;
|
|
472
|
+
if (Date.parse(entry.from) > at || !(at < Date.parse(entry.until))) return false;
|
|
473
|
+
if (entry.tenant !== void 0 && entry.tenant !== tenant) return false;
|
|
474
|
+
if (entry.permissions !== void 0 && !entry.permissions.includes(request.permission)) return false;
|
|
475
|
+
return entry.resources === void 0 || entry.resources.includes(request.resource.type);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* The first active delegation under which `delegate` may approve `request`,
|
|
479
|
+
* or `undefined`. The delegator is resolved by `subjectFor` and checked with
|
|
480
|
+
* `assertApprover`, so relation and permission approvers use the delegator's
|
|
481
|
+
* facts. A throw, a missing option or a delegator the source does not name
|
|
482
|
+
* matches nobody. The delegator's own delegations are never read.
|
|
483
|
+
*/
|
|
484
|
+
async function matchDelegation(request, delegate, options) {
|
|
485
|
+
const principal = delegate.principal;
|
|
486
|
+
const source = options.approvalDelegations;
|
|
487
|
+
const subjectFor = options.subjectFor;
|
|
488
|
+
if (principal === null || principal.kind === "link" || principal.kind === "feed" || principal.kind === "contact" || source === void 0 || subjectFor === void 0) return;
|
|
489
|
+
const now = dateAt(options.now);
|
|
490
|
+
const tenant = request.subject.principal?.tenant;
|
|
491
|
+
if (!inTenant(delegate, tenant)) return;
|
|
492
|
+
let listed;
|
|
493
|
+
try {
|
|
494
|
+
listed = await source.delegationsFor({
|
|
495
|
+
delegate: principal.id,
|
|
496
|
+
...tenant === void 0 ? {} : { tenant },
|
|
497
|
+
at: now.toISOString()
|
|
498
|
+
});
|
|
499
|
+
} catch {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (!Array.isArray(listed)) return;
|
|
503
|
+
const at = now.getTime();
|
|
504
|
+
return (await Promise.all(listed.map(async (raw) => {
|
|
505
|
+
const entry = parseApprovalDelegation(raw);
|
|
506
|
+
if (entry === void 0 || !covers(entry, request, principal.id, tenant, at)) return;
|
|
507
|
+
try {
|
|
508
|
+
const resolved = await subjectFor(entry.delegator, request);
|
|
509
|
+
if (resolved === null || resolved === void 0 || resolved.principal === null || resolved.principal.id !== entry.delegator || resolved.principal.kind === "link" || resolved.principal.kind === "feed" || resolved.principal.kind === "contact") return;
|
|
510
|
+
const facts = await options.factsFor(resolved);
|
|
511
|
+
assertApprover(request, resolved, options.requireDistinct, { epochMilliseconds: now.getTime() }, facts.relations, facts.permissions);
|
|
512
|
+
return {
|
|
513
|
+
delegation: entry,
|
|
514
|
+
delegator: resolved,
|
|
515
|
+
relations: facts.relations,
|
|
516
|
+
permissions: facts.permissions
|
|
517
|
+
};
|
|
518
|
+
} catch {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
}))).find((match) => match !== void 0);
|
|
522
|
+
}
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/approvals/idempotency.ts
|
|
525
|
+
/** An in-process `IdempotencyStore`, for one instance and for tests. */
|
|
526
|
+
function memoryIdempotencyStore() {
|
|
527
|
+
const entries = /* @__PURE__ */ new Map();
|
|
528
|
+
return {
|
|
529
|
+
get(key) {
|
|
530
|
+
const entry = entries.get(key);
|
|
531
|
+
if (entry === void 0) return;
|
|
532
|
+
if (entry.expiresAt <= nowMillis()) {
|
|
533
|
+
entries.delete(key);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
return entry.response;
|
|
537
|
+
},
|
|
538
|
+
set(key, response, expiresAt) {
|
|
539
|
+
entries.set(key, {
|
|
540
|
+
response,
|
|
541
|
+
expiresAt
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/approvals/signature.ts
|
|
548
|
+
function actionOf(request) {
|
|
549
|
+
const prefix = `${request.resource.type}.`;
|
|
550
|
+
return request.permission.startsWith(prefix) ? request.permission.slice(prefix.length) : request.permission.slice(request.permission.lastIndexOf(".") + 1);
|
|
551
|
+
}
|
|
552
|
+
function approvalSignatureClaims(request) {
|
|
553
|
+
const actor = request.subject.actor;
|
|
554
|
+
return compact({
|
|
555
|
+
sub: request.subject.principal?.id,
|
|
556
|
+
act: actor === void 0 ? void 0 : { sub: actor.id },
|
|
557
|
+
token: request.token,
|
|
558
|
+
resolved_by: request.resolvedBy,
|
|
559
|
+
approvals: (request.approvals ?? []).map((item) => compact({
|
|
560
|
+
by: item.by,
|
|
561
|
+
at: item.at,
|
|
562
|
+
stage: item.stage,
|
|
563
|
+
vouched: item.vouched,
|
|
564
|
+
on_behalf_of: item.onBehalfOf,
|
|
565
|
+
delegation: item.delegation
|
|
566
|
+
})),
|
|
567
|
+
authorization_details: [compact({
|
|
568
|
+
type: request.resource.type,
|
|
569
|
+
actions: [actionOf(request)],
|
|
570
|
+
identifier: request.resource.id
|
|
571
|
+
})]
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Signs an approved request as a `vnd.permdock.approval+jwt`: `jti` is the
|
|
576
|
+
* request id, `iat` its `resolvedAt` and `exp` its `expiresAt`. `undefined`
|
|
577
|
+
* without a signer or for any status but `approved`.
|
|
578
|
+
*/
|
|
579
|
+
function signApproval(request, signer, options = {}) {
|
|
580
|
+
if (signer === void 0 || request.status !== "approved") return Promise.resolve(void 0);
|
|
581
|
+
return signer.sign(approvalSignatureClaims(request), compact({
|
|
582
|
+
typ: "vnd.permdock.approval+jwt",
|
|
583
|
+
audience: options.audience,
|
|
584
|
+
jti: request.id,
|
|
585
|
+
issuedAt: secondsOf(request.resolvedAt),
|
|
586
|
+
expiresAt: secondsOf(request.expiresAt)
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/approvals/handler.ts
|
|
591
|
+
const IDEMPOTENCY_TTL_MS = 864e5;
|
|
592
|
+
const MAX_IDEMPOTENCY_KEY_LENGTH = 256;
|
|
593
|
+
function problem(status, detail) {
|
|
594
|
+
return problemResponse(statusProblem(status, detail));
|
|
595
|
+
}
|
|
596
|
+
function json(status, body, headers = {}) {
|
|
597
|
+
return new Response(JSON.stringify(body), {
|
|
598
|
+
status,
|
|
599
|
+
headers: {
|
|
600
|
+
...headers,
|
|
601
|
+
"content-type": "application/json"
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function viewOf(request) {
|
|
606
|
+
const { token: _token, ...view } = request;
|
|
607
|
+
return view;
|
|
608
|
+
}
|
|
609
|
+
/** A strong RFC 9110 entity tag: the `sha-256:` digest of the view's RFC 8785 JSON. */
|
|
610
|
+
function entityTag(request) {
|
|
611
|
+
return `"${sha256Digest(canonicalJson(viewOf(request)))}"`;
|
|
612
|
+
}
|
|
613
|
+
function entityTags(field) {
|
|
614
|
+
return field.split(",").map((tag) => tag.trim()).filter((tag) => tag !== "");
|
|
615
|
+
}
|
|
616
|
+
/** RFC 9110 section 13.1.1: strong comparison, so a weak tag never matches. */
|
|
617
|
+
function ifMatchFails(request, current) {
|
|
618
|
+
const field = request.headers.get("if-match");
|
|
619
|
+
if (field === null) return false;
|
|
620
|
+
const tags = entityTags(field);
|
|
621
|
+
if (tags.includes("*")) return false;
|
|
622
|
+
return !tags.includes(entityTag(current));
|
|
623
|
+
}
|
|
624
|
+
/** RFC 9110 section 13.1.2: weak comparison. */
|
|
625
|
+
function ifNoneMatchHolds(request, etag) {
|
|
626
|
+
const field = request.headers.get("if-none-match");
|
|
627
|
+
if (field === null) return false;
|
|
628
|
+
return entityTags(field).some((tag) => tag === "*" || tag.replace(/^W\//u, "") === etag);
|
|
629
|
+
}
|
|
630
|
+
function decodeSegment(segment) {
|
|
631
|
+
try {
|
|
632
|
+
return decodeURIComponent(segment);
|
|
633
|
+
} catch {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* The route the path names and the methods it allows. The handler mounts at
|
|
639
|
+
* any prefix, so a path reads from its last segments; a path naming none is
|
|
640
|
+
* `undefined` (`404`).
|
|
641
|
+
*/
|
|
642
|
+
function parseRoute(url) {
|
|
643
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
644
|
+
const last = parts.at(-1);
|
|
645
|
+
const prev = parts.at(-2);
|
|
646
|
+
if (last === void 0) return;
|
|
647
|
+
if (last === "pending" || last === "mine") {
|
|
648
|
+
const kind = last;
|
|
649
|
+
return {
|
|
650
|
+
allow: "GET",
|
|
651
|
+
route: (method) => method === "GET" ? { kind } : void 0
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
if ((last === "approve" || last === "reject" || last === "signature") && prev !== void 0) {
|
|
655
|
+
const id = decodeSegment(prev);
|
|
656
|
+
if (id === void 0) return;
|
|
657
|
+
const kind = last;
|
|
658
|
+
const method = kind === "signature" ? "GET" : "POST";
|
|
659
|
+
return {
|
|
660
|
+
allow: method,
|
|
661
|
+
route: (requested) => requested === method ? {
|
|
662
|
+
kind,
|
|
663
|
+
id
|
|
664
|
+
} : void 0
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
const id = decodeSegment(last);
|
|
668
|
+
if (id === void 0) return;
|
|
669
|
+
return {
|
|
670
|
+
allow: "GET",
|
|
671
|
+
route: (method) => method === "GET" ? {
|
|
672
|
+
kind: "get",
|
|
673
|
+
id
|
|
674
|
+
} : void 0
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
/** `store.getById`, else the first page run of `list` that carries the id. */
|
|
678
|
+
async function findById(store, id) {
|
|
679
|
+
if (store.getById !== void 0) return store.getById(id);
|
|
680
|
+
return (await listAllApprovals(store, {})).find((request) => request.id === id) ?? null;
|
|
681
|
+
}
|
|
682
|
+
/** RFC 8288 `Link: <…>; rel="next"` for a page with a `next` cursor. */
|
|
683
|
+
function nextLink(url, next) {
|
|
684
|
+
if (next === void 0) return {};
|
|
685
|
+
const target = new URL(url);
|
|
686
|
+
target.searchParams.set("cursor", next);
|
|
687
|
+
return { Link: `<${target.href}>; rel="next"` };
|
|
688
|
+
}
|
|
689
|
+
/** The signature sub-resource beside an `…/<id>/approve` or `…/<id>/reject` URL. */
|
|
690
|
+
function signatureLink(url) {
|
|
691
|
+
const target = new URL(url);
|
|
692
|
+
target.search = "";
|
|
693
|
+
target.pathname = target.pathname.replace(/\/(?:approve|reject)\/*$/u, "/signature");
|
|
694
|
+
return { Link: `<${target.href}>; rel="alternate"; type="application/jwt"` };
|
|
695
|
+
}
|
|
696
|
+
async function resolveSubject(request, resolve) {
|
|
697
|
+
try {
|
|
698
|
+
const subject = await resolve(request);
|
|
699
|
+
if (subject === null || subject === void 0) return null;
|
|
700
|
+
return subject;
|
|
701
|
+
} catch {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function membershipTenants(subject) {
|
|
706
|
+
const principal = subject.principal;
|
|
707
|
+
if (principal === null) return [];
|
|
708
|
+
const tenants = /* @__PURE__ */ new Set();
|
|
709
|
+
if (principal.tenant !== void 0) tenants.add(principal.tenant);
|
|
710
|
+
for (const membership of principal.memberships ?? []) {
|
|
711
|
+
const tenant = rootMembershipId(membership);
|
|
712
|
+
if (tenant !== void 0) tenants.add(tenant);
|
|
713
|
+
}
|
|
714
|
+
return [...tenants];
|
|
715
|
+
}
|
|
716
|
+
function belongsToTenant(subject, tenant) {
|
|
717
|
+
return membershipTenants(subject).includes(tenant);
|
|
718
|
+
}
|
|
719
|
+
async function factsFor(request, subject, options) {
|
|
720
|
+
return {
|
|
721
|
+
relations: await approverRelations(request, subject, options),
|
|
722
|
+
permissions: await heldPermissions(request, subject, options.permdockFor)
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/** The caller as themselves, or the first delegation that makes them an approver. */
|
|
726
|
+
async function actingApprover(request, subject, requireDistinct, options) {
|
|
727
|
+
const facts = await factsFor(request, subject, options);
|
|
728
|
+
try {
|
|
729
|
+
assertApprover(request, subject, requireDistinct, void 0, facts.relations, facts.permissions);
|
|
730
|
+
return {
|
|
731
|
+
kind: "self",
|
|
732
|
+
...facts
|
|
733
|
+
};
|
|
734
|
+
} catch (error) {
|
|
735
|
+
if (!isApprovalError(error) || error.code !== "approver-not-eligible") throw error;
|
|
736
|
+
}
|
|
737
|
+
const match = await matchDelegation(request, subject, {
|
|
738
|
+
...options.approvalDelegations === void 0 ? {} : { approvalDelegations: options.approvalDelegations },
|
|
739
|
+
...options.subjectFor === void 0 ? {} : { subjectFor: options.subjectFor },
|
|
740
|
+
requireDistinct,
|
|
741
|
+
factsFor: (delegator) => factsFor(request, delegator, options)
|
|
742
|
+
});
|
|
743
|
+
if (match === void 0) throw new ApprovalError("approver-not-eligible", "approver does not hold an eligible role");
|
|
744
|
+
return {
|
|
745
|
+
kind: "delegated",
|
|
746
|
+
delegation: match.delegation.id,
|
|
747
|
+
delegator: match.delegator,
|
|
748
|
+
relations: match.relations,
|
|
749
|
+
permissions: match.permissions
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
async function mayResolve(request, subject, requireDistinct, options) {
|
|
753
|
+
try {
|
|
754
|
+
await actingApprover(request, subject, requireDistinct, options);
|
|
755
|
+
return true;
|
|
756
|
+
} catch {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* The requester, a tenant member for a tenant request, and for a request
|
|
762
|
+
* without a tenant only who answered it or may answer it.
|
|
763
|
+
*/
|
|
764
|
+
function canView(request, subject, requireDistinct, options) {
|
|
765
|
+
const principal = subject.principal;
|
|
766
|
+
if (principal === null) return Promise.resolve(false);
|
|
767
|
+
if (request.subject.principal?.id === principal.id) return Promise.resolve(true);
|
|
768
|
+
const tenant = request.subject.principal?.tenant;
|
|
769
|
+
if (tenant !== void 0) return Promise.resolve(belongsToTenant(subject, tenant));
|
|
770
|
+
if (request.resolvedBy === principal.id || (request.approvals ?? []).some((item) => item.by === principal.id)) return Promise.resolve(true);
|
|
771
|
+
return mayResolve(request, subject, requireDistinct, options);
|
|
772
|
+
}
|
|
773
|
+
/** The requester and who answered: the signed approval carries the resume token, so only they read it. */
|
|
774
|
+
function party(request, principalId) {
|
|
775
|
+
return request.subject.principal?.id === principalId || request.resolvedBy === principalId || (request.approvals ?? []).some((item) => item.by === principalId);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* The tenants whose pending requests the approver may list: the requested one,
|
|
779
|
+
* else the active one, else every membership tenant. Requests without a tenant
|
|
780
|
+
* are listed only when no tenant was requested or active.
|
|
781
|
+
*/
|
|
782
|
+
function pageQuery(url) {
|
|
783
|
+
const limit = url.searchParams.get("limit");
|
|
784
|
+
const cursor = url.searchParams.get("cursor");
|
|
785
|
+
return compact({
|
|
786
|
+
limit: limit === null ? void 0 : Number(limit),
|
|
787
|
+
cursor: cursor ?? void 0
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function inboxScope(url, subject) {
|
|
791
|
+
const requested = url.searchParams.get("tenant");
|
|
792
|
+
if (requested !== null && requested !== "") {
|
|
793
|
+
if (!belongsToTenant(subject, requested)) return { ok: false };
|
|
794
|
+
return {
|
|
795
|
+
ok: true,
|
|
796
|
+
tenants: [requested],
|
|
797
|
+
tenantless: false
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const active = subject.principal?.tenant;
|
|
801
|
+
if (active !== void 0) return {
|
|
802
|
+
ok: true,
|
|
803
|
+
tenants: [active],
|
|
804
|
+
tenantless: false
|
|
805
|
+
};
|
|
806
|
+
return {
|
|
807
|
+
ok: true,
|
|
808
|
+
tenants: membershipTenants(subject),
|
|
809
|
+
tenantless: true
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
const MAX_BODY_BYTES = 16384;
|
|
813
|
+
const MAX_NOTE_LENGTH = 2e3;
|
|
814
|
+
/** An unreadable body is no note; an oversized body or note is refused, never truncated. */
|
|
815
|
+
async function readNote(request) {
|
|
816
|
+
if (!(request.headers.get("content-type") ?? "").includes("application/json")) return { text: "" };
|
|
817
|
+
const read = await readBodyText(request, MAX_BODY_BYTES);
|
|
818
|
+
if (!read.ok) return read.cause === "too-large" ? {
|
|
819
|
+
text: "",
|
|
820
|
+
refusal: bodyTooLarge(MAX_BODY_BYTES)
|
|
821
|
+
} : { text: "" };
|
|
822
|
+
const { text } = read;
|
|
823
|
+
try {
|
|
824
|
+
const body = JSON.parse(text);
|
|
825
|
+
if (body !== null && typeof body === "object" && "note" in body && typeof body.note === "string") return body.note.length > MAX_NOTE_LENGTH ? {
|
|
826
|
+
text,
|
|
827
|
+
refusal: validationProblem(`note exceeds ${String(MAX_NOTE_LENGTH)} characters`)
|
|
828
|
+
} : {
|
|
829
|
+
text,
|
|
830
|
+
note: body.note
|
|
831
|
+
};
|
|
832
|
+
} catch {
|
|
833
|
+
return { text };
|
|
834
|
+
}
|
|
835
|
+
return { text };
|
|
836
|
+
}
|
|
837
|
+
function mapError(error, credentials) {
|
|
838
|
+
if (!isApprovalError(error)) return problem(500, "approval store failed");
|
|
839
|
+
switch (error.code) {
|
|
840
|
+
case "approval-not-found": return problem(404, error.message);
|
|
841
|
+
case "approval-not-pending":
|
|
842
|
+
case "approval-expired":
|
|
843
|
+
case "approver-repeated": return problem(409, error.message);
|
|
844
|
+
case "approver-unauthenticated": return unauthenticatedProblem(credentials);
|
|
845
|
+
case "approver-not-eligible":
|
|
846
|
+
case "approver-is-actor":
|
|
847
|
+
case "approver-is-principal": return problemResponse({
|
|
848
|
+
type: problemType("approver-not-eligible"),
|
|
849
|
+
title: "Approver not eligible",
|
|
850
|
+
status: 403,
|
|
851
|
+
detail: error.message,
|
|
852
|
+
reason: error.code
|
|
853
|
+
});
|
|
854
|
+
default: {
|
|
855
|
+
const unhandled = error.code;
|
|
856
|
+
return problem(500, `unknown approval error ${String(unhandled)}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/** The `Idempotency-Key` field: an RFC 9651 sf-string, 1 to 256 characters. */
|
|
861
|
+
function idempotencyKey(request) {
|
|
862
|
+
const field = request.headers.get("idempotency-key");
|
|
863
|
+
if (field === null) return {};
|
|
864
|
+
const key = /^"([\u0020-\u007E]*)"$/u.exec(field.trim())?.[1];
|
|
865
|
+
if (key === void 0 || key === "" || key.length > MAX_IDEMPOTENCY_KEY_LENGTH || /["\\]/u.test(key)) return { refusal: validationProblem("Idempotency-Key must be a quoted string of 1 to 256 characters") };
|
|
866
|
+
return { key };
|
|
867
|
+
}
|
|
868
|
+
async function stored(response, fingerprint) {
|
|
869
|
+
return {
|
|
870
|
+
fingerprint,
|
|
871
|
+
status: response.status,
|
|
872
|
+
headers: [...response.headers.entries()],
|
|
873
|
+
body: await response.clone().text()
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function approvalsHandler(store, options) {
|
|
877
|
+
const requireDistinct = options.requireDistinctApprover === true;
|
|
878
|
+
const idempotency = options.idempotency ?? memoryIdempotencyStore();
|
|
879
|
+
const visible = async (id, subject) => {
|
|
880
|
+
const current = await findById(store, id);
|
|
881
|
+
return current !== null && await canView(current, subject, requireDistinct, options) ? current : null;
|
|
882
|
+
};
|
|
883
|
+
const resolve = async (request, url, route, subject, read) => {
|
|
884
|
+
const current = await visible(route.id, subject);
|
|
885
|
+
if (current === null) return problem(404, "approval was not found");
|
|
886
|
+
if (ifMatchFails(request, current)) return problem(412, "the approval changed since it was read");
|
|
887
|
+
const acting = await actingApprover(current, subject, requireDistinct, options);
|
|
888
|
+
const resolved = await store.resolve(current.token, compact({
|
|
889
|
+
status: route.kind === "approve" ? "approved" : "rejected",
|
|
890
|
+
by: subject,
|
|
891
|
+
note: read.note,
|
|
892
|
+
relations: acting.kind === "self" ? acting.relations : void 0,
|
|
893
|
+
permissions: acting.kind === "self" ? acting.permissions : void 0,
|
|
894
|
+
onBehalfOf: acting.kind === "delegated" ? {
|
|
895
|
+
subject: acting.delegator,
|
|
896
|
+
delegation: acting.delegation,
|
|
897
|
+
relations: acting.relations,
|
|
898
|
+
permissions: acting.permissions
|
|
899
|
+
} : void 0
|
|
900
|
+
}));
|
|
901
|
+
return json(200, viewOf(resolved), {
|
|
902
|
+
ETag: entityTag(resolved),
|
|
903
|
+
...options.signer !== void 0 && resolved.status === "approved" ? signatureLink(url) : {}
|
|
904
|
+
});
|
|
905
|
+
};
|
|
906
|
+
return async (request) => {
|
|
907
|
+
const url = new URL(request.url);
|
|
908
|
+
const parsed = parseRoute(url);
|
|
909
|
+
if (parsed === void 0) return problem(404, "unknown approvals route");
|
|
910
|
+
const route = parsed.route(request.method);
|
|
911
|
+
if (route === void 0) return methodNotAllowed(parsed.allow);
|
|
912
|
+
await store.expire();
|
|
913
|
+
const subject = await resolveSubject(request, options.subject);
|
|
914
|
+
const principal = subject?.principal ?? null;
|
|
915
|
+
if (subject === null || principal === null) return unauthenticatedProblem(hasCredentials(request));
|
|
916
|
+
try {
|
|
917
|
+
switch (route.kind) {
|
|
918
|
+
case "pending": {
|
|
919
|
+
const scoped = inboxScope(url, subject);
|
|
920
|
+
if (!scoped.ok) return problemResponse({
|
|
921
|
+
type: problemType("denied"),
|
|
922
|
+
title: "Permission denied",
|
|
923
|
+
status: 403,
|
|
924
|
+
detail: "approver does not belong to that tenant"
|
|
925
|
+
});
|
|
926
|
+
const page = await store.list({
|
|
927
|
+
...pageQuery(url),
|
|
928
|
+
status: "pending"
|
|
929
|
+
});
|
|
930
|
+
const tenants = new Set(scoped.tenants);
|
|
931
|
+
const eligible = await Promise.all(page.items.map(async (item) => {
|
|
932
|
+
const tenant = item.subject.principal?.tenant;
|
|
933
|
+
return (tenant === void 0 ? scoped.tenantless : tenants.has(tenant)) && await mayResolve(item, subject, requireDistinct, options);
|
|
934
|
+
}));
|
|
935
|
+
return json(200, compact({
|
|
936
|
+
items: page.items.filter((_, index) => eligible[index] === true).map(viewOf),
|
|
937
|
+
next: page.next
|
|
938
|
+
}), nextLink(url, page.next));
|
|
939
|
+
}
|
|
940
|
+
case "mine": {
|
|
941
|
+
const page = await store.list({
|
|
942
|
+
...pageQuery(url),
|
|
943
|
+
principalId: principal.id
|
|
944
|
+
});
|
|
945
|
+
return json(200, compact({
|
|
946
|
+
items: page.items.map(viewOf),
|
|
947
|
+
next: page.next
|
|
948
|
+
}), nextLink(url, page.next));
|
|
949
|
+
}
|
|
950
|
+
case "get": {
|
|
951
|
+
const current = await visible(route.id, subject);
|
|
952
|
+
if (current === null) return problem(404, "approval was not found");
|
|
953
|
+
const etag = entityTag(current);
|
|
954
|
+
if (ifNoneMatchHolds(request, etag)) return new Response(null, {
|
|
955
|
+
status: 304,
|
|
956
|
+
headers: { ETag: etag }
|
|
957
|
+
});
|
|
958
|
+
return json(200, viewOf(current), { ETag: etag });
|
|
959
|
+
}
|
|
960
|
+
case "signature": {
|
|
961
|
+
const current = await findById(store, route.id);
|
|
962
|
+
const signed = current === null || !party(current, principal.id) ? void 0 : await signApproval(current, options.signer, compact({ audience: options.audience }));
|
|
963
|
+
if (signed === void 0) return problem(404, "no signed approval for that request");
|
|
964
|
+
return new Response(signed, {
|
|
965
|
+
status: 200,
|
|
966
|
+
headers: { "content-type": "application/jwt" }
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
case "approve":
|
|
970
|
+
case "reject": {
|
|
971
|
+
const keyed = idempotencyKey(request);
|
|
972
|
+
if ("refusal" in keyed) return keyed.refusal;
|
|
973
|
+
const read = await readNote(request);
|
|
974
|
+
if (read.refusal !== void 0) return read.refusal;
|
|
975
|
+
if (keyed.key === void 0) return await resolve(request, url, route, subject, read);
|
|
976
|
+
const key = `${principal.id}\n${keyed.key}`;
|
|
977
|
+
const fingerprint = `${route.kind} ${route.id} ${sha256Digest(read.text)}`;
|
|
978
|
+
const replay = await idempotency.get(key);
|
|
979
|
+
if (replay !== void 0) return replay.fingerprint === fingerprint ? new Response(replay.body, {
|
|
980
|
+
status: replay.status,
|
|
981
|
+
headers: replay.headers.map(([name, value]) => [name, value])
|
|
982
|
+
}) : problem(422, "Idempotency-Key was used for another request");
|
|
983
|
+
const response = await resolve(request, url, route, subject, read);
|
|
984
|
+
if (response.ok) await idempotency.set(key, await stored(response, fingerprint), nowMillis() + IDEMPOTENCY_TTL_MS);
|
|
985
|
+
return response;
|
|
986
|
+
}
|
|
987
|
+
default: return problem(404, `unknown approvals route ${String(route)}`);
|
|
988
|
+
}
|
|
989
|
+
} catch (error) {
|
|
990
|
+
return mapError(error, hasCredentials(request));
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
//#endregion
|
|
995
|
+
export { matchDelegation as a, memoryAccessReviewStore as c, accessReviewsHandler as d, accessReviewerEligible as f, memoryIdempotencyStore as i, pageAccessReviewItems as l, approvalSignatureClaims as n, memoryApprovalDelegationSource as o, decideAccessReviewItem as p, signApproval as r, parseApprovalDelegation as s, approvalsHandler as t, startAccessReview as u };
|