permdock 0.0.0 → 0.2.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 +327 -1
- package/dist/a2a/index.d.ts +169 -0
- package/dist/a2a/index.js +232 -0
- package/dist/adapter-context-mlWsVGGg.js +23 -0
- package/dist/ai-sdk/index.d.ts +95 -0
- package/dist/ai-sdk/index.js +154 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +5 -0
- package/dist/approvals-DbYAuNkG.js +224 -0
- package/dist/approvers-DT4AVyIg.js +52 -0
- package/dist/arazzo-BAgw9ap6.js +70 -0
- package/dist/arazzo-cmvX2jDe.js +54 -0
- package/dist/authzen/index.d.ts +39 -0
- package/dist/authzen/index.js +302 -0
- package/dist/better-auth/index.d.ts +109 -0
- package/dist/better-auth/index.js +344 -0
- package/dist/better-supabase/index.d.ts +206 -0
- package/dist/better-supabase/index.js +620 -0
- package/dist/bind-CVy3Espv.d.ts +25 -0
- package/dist/bind-VnYn2HKq.js +97 -0
- package/dist/boundary-BM8a_P6E.js +13 -0
- package/dist/boundary-BmHxVH1J.d.ts +15 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/canonical-json-DemiK4Vp.js +26 -0
- package/dist/capability-4qPdO9gk.js +173 -0
- package/dist/capability-Dq43tT1u.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-BjZSovXQ.js +2 -0
- package/dist/catalog-doc-Dsc9y7sd.js +309 -0
- package/dist/catalog-h2tlKFkq.js +48 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/catalog-z8q6bi_r.js +55 -0
- package/dist/claude-agent/index.d.ts +72 -0
- package/dist/claude-agent/index.js +77 -0
- package/dist/clerk/index.d.ts +65 -0
- package/dist/clerk/index.js +301 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.js +5 -0
- package/dist/clients-C7zLFuWT.d.ts +12 -0
- package/dist/clients-DjU5ABMy.js +23 -0
- package/dist/cloud/index.d.ts +90 -0
- package/dist/cloud/index.js +404 -0
- package/dist/cloud-BGhHS-lY.js +50 -0
- package/dist/cloud-Bo3e9Ak3.js +118 -0
- package/dist/collect-DDDv_ESs.js +502 -0
- package/dist/collect-DLlujdHC.js +83 -0
- package/dist/collect-rG2IQ3t0.js +2 -0
- package/dist/commands-DWOrSQic.js +97 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile/index.d.ts +3 -0
- package/dist/compile/index.js +2 -0
- package/dist/compile-Ba4blROR.js +613 -0
- package/dist/compile-CNnl6uIt.d.ts +94 -0
- package/dist/config-BJiM-xAm.js +157 -0
- package/dist/config-Bn00Z3ks.js +36 -0
- package/dist/config-report-Dw_JcUqT.js +44 -0
- package/dist/connection-CU8MQxRi.js +194 -0
- package/dist/context-Ttp0ekMT.js +82 -0
- package/dist/convex/index.d.ts +45 -0
- package/dist/convex/index.js +65 -0
- package/dist/create-BWyx3jua.d.ts +245 -0
- package/dist/create-CPhDL6qx.js +253 -0
- package/dist/credential-8P81ff4u.js +306 -0
- package/dist/delegation-D8pUEuce.js +103 -0
- package/dist/describe-B_gVZEVi.js +94 -0
- package/dist/describe-PdoqESvg.d.ts +36 -0
- package/dist/diff-B8Tunc3o.js +451 -0
- package/dist/diff-CzqLxZ77.js +48 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-WpvG6p4k.js +1194 -0
- package/dist/doctor-collect-ByfYlNv1.js +664 -0
- package/dist/doctor-rOr-4ZfS.js +53 -0
- package/dist/drizzle/index.d.ts +73 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +49 -0
- package/dist/elysia/index.js +59 -0
- package/dist/errors-CWfoNg9N.js +47 -0
- package/dist/errors-Cz5zn0mW.js +123 -0
- package/dist/errors-DAqevpNk.d.ts +125 -0
- package/dist/errors-DRnGZHzF.js +30 -0
- package/dist/evaluation-items-CfCCejQd.js +40 -0
- package/dist/evaluations-tz6D9A2G.js +461 -0
- package/dist/eve/index.d.ts +86 -0
- package/dist/eve/index.js +176 -0
- package/dist/expiry-BoBJ8Tvc.js +7 -0
- package/dist/express/index.d.ts +23 -0
- package/dist/express/index.js +78 -0
- package/dist/fastify/index.d.ts +33 -0
- package/dist/fastify/index.js +92 -0
- package/dist/fields-BMl1RxoX.js +26 -0
- package/dist/files-DWUN9PzI.js +125 -0
- package/dist/fixtures-ht28Cplr.js +67 -0
- package/dist/freeze-CrU8nQeX.js +60 -0
- package/dist/from-snapshot-C3sHpZNS.js +569 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Cv5Sogfv.js +602 -0
- package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
- package/dist/headers-BZSDieNT.js +6 -0
- package/dist/headers-Cf-zWvsO.d.ts +6 -0
- package/dist/helpers-H_KARDVC.js +421 -0
- package/dist/hono/index.d.ts +48 -0
- package/dist/hono/index.js +99 -0
- package/dist/hosted-DJQw46hn.js +266 -0
- package/dist/http-BIhmLfCc.js +95 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-SO7qGttf.js +44 -0
- package/dist/ids-DC4V4CFu.js +18 -0
- package/dist/index-A0YNL8jZ.d.ts +177 -0
- package/dist/index-CtE07CBF.d.ts +6 -0
- package/dist/index-D3vbkoSI.d.ts +483 -0
- package/dist/index-D6_cSa1z.d.ts +201 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +26 -0
- package/dist/instance-options-BK_rNOSC.d.ts +11 -0
- package/dist/instance-options-Dhhcnm3y.js +22 -0
- package/dist/json-schema-BsAflUGW.js +106 -0
- package/dist/jwt/index.d.ts +91 -0
- package/dist/jwt/index.js +590 -0
- package/dist/kernel-GSQnMz3O.js +208 -0
- package/dist/kysely/index.d.ts +78 -0
- package/dist/kysely/index.js +129 -0
- package/dist/live-options-H2eG8dfJ.js +60 -0
- package/dist/load-DhWEB9P1.js +75 -0
- package/dist/load-DwJUafYN.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-iaoBXHKc.js +783 -0
- package/dist/manifest-uDS9y5de.d.ts +206 -0
- package/dist/map-AcxK9o_D.js +147 -0
- package/dist/map-claims-Z4QZOiFF.js +369 -0
- package/dist/map-error-pgUWNNtG.js +23 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-DndQ5tQ-.js +77 -0
- package/dist/mcp/index.d.ts +159 -0
- package/dist/mcp/index.js +603 -0
- package/dist/nest/index.d.ts +75 -0
- package/dist/nest/index.js +261 -0
- package/dist/next/client.d.ts +23 -0
- package/dist/next/client.js +33 -0
- package/dist/next/index.d.ts +93 -0
- package/dist/next/index.js +180 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +33 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +34 -0
- package/dist/openai/index.d.ts +62 -0
- package/dist/openai/index.js +92 -0
- package/dist/openapi/index.d.ts +176 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-Bbg4wGVL.js +164 -0
- package/dist/openapi-BqRBvAng.js +538 -0
- package/dist/openapi-CqqEbHsL.js +250 -0
- package/dist/openapi-import-cmwetbzw.js +5959 -0
- package/dist/openapi-schema--ARjyfQr.js +6902 -0
- package/dist/orpc/index.d.ts +51 -0
- package/dist/orpc/index.js +145 -0
- package/dist/otel/index.d.ts +13 -0
- package/dist/otel/index.js +202 -0
- package/dist/package-root-zGkxO3oN.js +30 -0
- package/dist/parse-BuyjxhZV.js +53 -0
- package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
- package/dist/parse-local-manifest-BrMozJCo.js +36 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +682 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-CzqXjCpU.js +3471 -0
- package/dist/permissions-CDgvKG5L.d.ts +52 -0
- package/dist/permissions-CpZrivYv.js +142 -0
- package/dist/permissions-DVoF6dMt.js +662 -0
- package/dist/permissions-DttnqqxC.d.ts +214 -0
- package/dist/pins-8ILTftC9.js +12 -0
- package/dist/policy-CFZAr_Al.d.ts +2252 -0
- package/dist/policy-D8UFyERw.d.ts +20 -0
- package/dist/policy-xyWg7QdR.js +1122 -0
- package/dist/powersync-A8x_Yyvh.js +2 -0
- package/dist/powersync-C7kLglqX.js +540 -0
- package/dist/powersync-yW1UUFzp.js +62 -0
- package/dist/prisma/index.d.ts +136 -0
- package/dist/prisma/index.js +455 -0
- package/dist/problem-B43ztKIm.js +191 -0
- package/dist/problem-details-CkL50EEf.d.ts +7 -0
- package/dist/problem-details-_AMOYz-w.js +144 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-CNsMzrVd.d.ts +27 -0
- package/dist/protected-Cs6kTaHo.js +100 -0
- package/dist/protected-bZs6rbT1.d.ts +40 -0
- package/dist/provider-Cjr62YMV.d.ts +6 -0
- package/dist/provider-DLzpb2Xz.js +52 -0
- package/dist/provider-store-DNyC1y48.js +43 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +7 -0
- package/dist/react/provider-client.d.ts +2 -0
- package/dist/react/provider-client.js +3 -0
- package/dist/react-native/index.d.ts +198 -0
- package/dist/react-native/index.js +696 -0
- package/dist/refs-KYb4ntaz.js +91 -0
- package/dist/relations-cjOL01hZ.js +505 -0
- package/dist/replay-BsOyTrdV.js +28 -0
- package/dist/replay-sqBGMnNs.d.ts +5 -0
- package/dist/resource-ref-CaS9yVPi.js +20 -0
- package/dist/revocations-Bhr0aMIC.js +27 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BMNydpHk.js +2521 -0
- package/dist/rls-CEvs64Kj.js +238 -0
- package/dist/rls-fields-BMasREhR.js +3502 -0
- package/dist/rls-graph-pc9fuXYm.js +435 -0
- package/dist/rls-import-D8F7j35Q.js +943 -0
- package/dist/rls-migrate-7wikfrVI.js +367 -0
- package/dist/rls-ownership-B5Lkq62W.js +843 -0
- package/dist/rls-shared-aOQ4hGTO.js +493 -0
- package/dist/rls-verify-D8B7dOTh.js +745 -0
- package/dist/row-check-Dt35czLy.d.ts +40 -0
- package/dist/rpc-DOK54_CS.js +89 -0
- package/dist/run-YXCJDNYk.js +174 -0
- package/dist/runtime-CB8gQFvS.js +11 -0
- package/dist/saas-Bj_FFkjN.js +1203 -0
- package/dist/schema-D3MnDMxa.js +300 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-BFGBBAqR.js +1445 -0
- package/dist/scopes-CVdPrWN-.js +239 -0
- package/dist/server/index.d.ts +134 -0
- package/dist/server/index.js +11 -0
- package/dist/server-DlZ28Oz5.js +268 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/shared-DRu8VFVM.js +123 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Gs6TTVa-.d.ts +104 -0
- package/dist/skills-Biq5uup3.js +192 -0
- package/dist/skills-JaymNCdK.js +56 -0
- package/dist/snapshot-Enxd0n-s.js +975 -0
- package/dist/snapshot-cache-D6jLelln.js +44 -0
- package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
- package/dist/solid/index.d.ts +93 -0
- package/dist/solid/index.js +188 -0
- package/dist/sources-B0UvrJ21.js +534 -0
- package/dist/sources-BcJgw0lt.d.ts +192 -0
- package/dist/sql-xdg5f4Fi.js +27 -0
- package/dist/src-CSbUiozF.js +568 -0
- package/dist/ssf/index.d.ts +4 -0
- package/dist/ssf/index.js +596 -0
- package/dist/store-DMAWwAJV.js +371 -0
- package/dist/store-D_pjn91-.d.ts +36 -0
- package/dist/store-options-yO0V6gRn.js +716 -0
- package/dist/stores-4_sqiOA1.js +87 -0
- package/dist/stores-DVR5NHLE.d.ts +52 -0
- package/dist/stream-DDpvynSB.js +66 -0
- package/dist/stream-DlvG2hs-.d.ts +12 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-BwBmCdTR.js +128 -0
- package/dist/supabase/index.d.ts +183 -0
- package/dist/supabase/index.js +5 -0
- package/dist/supabase/middleware.d.ts +82 -0
- package/dist/supabase/middleware.js +102 -0
- package/dist/supabase-CwafwxxF.js +83 -0
- package/dist/supabase-DTBaC20O.js +774 -0
- package/dist/supabase-config-Bs44dare.js +472 -0
- package/dist/supabase-hook-BuxLQLpU.js +1814 -0
- package/dist/supabase-hook-D5qVd_D8.js +2 -0
- package/dist/supautils-DYZnzRWV.js +91 -0
- package/dist/svelte/PermissionBoundary.svelte +37 -0
- package/dist/svelte/Protected.svelte +40 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.js +107 -0
- package/dist/svelte/runtime.d.ts +7 -0
- package/dist/svelte/runtime.js +4 -0
- package/dist/svelte/source.d.ts +13 -0
- package/dist/svelte/source.js +11 -0
- package/dist/terminal/index.d.ts +163 -0
- package/dist/terminal/index.js +833 -0
- package/dist/testing/index.d.ts +745 -0
- package/dist/testing/index.js +3672 -0
- package/dist/testing/saas/index.d.ts +194 -0
- package/dist/testing/saas/index.js +3 -0
- package/dist/testing/saas/permissions.d.ts +2 -0
- package/dist/testing/saas/permissions.js +2 -0
- package/dist/text-diff-BdkkYSbi.js +15 -0
- package/dist/thenable-okc2xDsj.js +10 -0
- package/dist/timeout-D51niuQP.js +8 -0
- package/dist/token-CIWxJOCh.js +50 -0
- package/dist/trpc/index.d.ts +61 -0
- package/dist/trpc/index.js +122 -0
- package/dist/types-9AWO9EGt.d.ts +197 -0
- package/dist/types-BPxgszlT.d.ts +550 -0
- package/dist/types-BVyImy5I.d.ts +120 -0
- package/dist/types-Bvc4oWT6.d.ts +9 -0
- package/dist/types-C3s9xVJG.d.ts +48 -0
- package/dist/types-Dnemwd77.d.ts +183 -0
- package/dist/types-kxznc7cn.d.ts +52 -0
- package/dist/types-vKyAMtcX.d.ts +157 -0
- package/dist/types-vNSDF7Dq.d.ts +66 -0
- package/dist/types-yF5LmQVD.d.ts +306 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +31 -0
- package/dist/usage-D07N9QJ3.js +42 -0
- package/dist/usage-D82l1CwK.js +2 -0
- package/dist/usage-DG1oeCca.js +693 -0
- package/dist/validation-error-CnjZ4t92.js +33 -0
- package/dist/validation-error-CtF23SrC.d.ts +26 -0
- package/dist/validity-uOp8UpYn.js +245 -0
- package/dist/verifier-BxBo6I44.js +466 -0
- package/dist/version-BvacVTZR.js +17 -0
- package/dist/views-Soe7bGSC.js +95 -0
- package/dist/vocabulary-DA8a8zif.d.ts +56 -0
- package/dist/vue/index.d.ts +79 -0
- package/dist/vue/index.js +196 -0
- package/dist/watch-Etoa_Mvr.js +2 -0
- package/dist/watch-UihESpsJ.js +128 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +307 -0
- package/package.json +450 -5
- package/rulesets/permdock.yaml +29 -0
- package/schemas/approval-request-v1.json +175 -0
- package/schemas/catalog-v1.json +535 -0
- package/schemas/openapi/oas-3.1.json +1298 -0
- package/schemas/openapi/oas-3.2.json +1516 -0
- package/schemas/openapi/overlay-1.1.json +79 -0
- package/schemas/supabase-claims-v1.json +144 -0
- package/schemas/supabase-manifest-v1.json +361 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +213 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +94 -0
- package/skills/permdock-audit/SKILL.md +67 -0
- package/skills/permdock-credentials/SKILL.md +55 -0
- package/skills/permdock-credentials/references/tokens-keys-links.md +132 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +62 -0
- package/skills/permdock-data/references/relationships.md +67 -0
- package/skills/permdock-tenancy/SKILL.md +80 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +89 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +492 -0
- package/skills/permdock-wire/references/policy-options.md +45 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { n as isReadonlyArray, t as compact } from "./compact-y6vfu2_f.js";
|
|
2
|
+
import { n as freezeDeep } from "./freeze-CrU8nQeX.js";
|
|
3
|
+
import { _ as listPermissions, a as formerKeys } from "./permissions-DVoF6dMt.js";
|
|
4
|
+
import { n as sha256, t as bytesToBase64Url } from "./sha256-BO0swRXL.js";
|
|
5
|
+
import { t as coveredByDelegation } from "./delegation-D8pUEuce.js";
|
|
6
|
+
import { t as anonymousSubject } from "./subject-C6dLxqHU.js";
|
|
7
|
+
import { a as ownRecord, n as idList, r as isId } from "./capability-4qPdO9gk.js";
|
|
8
|
+
//#region src/core/credential.ts
|
|
9
|
+
const MAX_NAME = 256;
|
|
10
|
+
function policies(policy) {
|
|
11
|
+
return isReadonlyArray(policy) ? policy : [policy];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The first rule of `policy` the credential breaks, or `undefined`. A key
|
|
15
|
+
* without `expiresAt` passes only when some policy sets `allowNoExpiry` and
|
|
16
|
+
* none sets `maxTtl`; an empty list therefore refuses it.
|
|
17
|
+
*/
|
|
18
|
+
function credentialPolicyViolation(credential, policy) {
|
|
19
|
+
const list = policies(policy);
|
|
20
|
+
for (const item of list) if (item.kinds !== void 0 && !item.kinds.includes(credential.kind)) return "kind";
|
|
21
|
+
if (credential.expiresAt === void 0) return list.some((item) => item.allowNoExpiry === true) && list.every((item) => item.maxTtl === void 0) ? void 0 : "no-expiry";
|
|
22
|
+
for (const item of list) {
|
|
23
|
+
const max = item.maxTtl;
|
|
24
|
+
if (max !== void 0 && (typeof max !== "number" || !Number.isFinite(max) || max < 0 || credential.expiresAt - credential.createdAt > max)) return "ttl";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function parsePermissions(value) {
|
|
28
|
+
if (!Array.isArray(value) || value.length === 0) return;
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const item of value) {
|
|
31
|
+
const entry = ownRecord(item);
|
|
32
|
+
if (entry === void 0 || !isId(entry["permission"])) return;
|
|
33
|
+
const ids = entry["ids"] === void 0 ? void 0 : idList(entry["ids"]);
|
|
34
|
+
if (entry["ids"] !== void 0 && ids === void 0) return;
|
|
35
|
+
out.push(compact({
|
|
36
|
+
permission: entry["permission"],
|
|
37
|
+
ids
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function isTime(value) {
|
|
43
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validates an untrusted credential record and returns a frozen copy with
|
|
47
|
+
* only the v1 fields, or `undefined`. Unknown fields are dropped; a wrong
|
|
48
|
+
* type in a known field, a service credential without `tenant` and `roles`,
|
|
49
|
+
* or a user credential with `roles`, rejects the whole record.
|
|
50
|
+
*/
|
|
51
|
+
function parseCredential(input) {
|
|
52
|
+
const value = ownRecord(input);
|
|
53
|
+
if (value === void 0 || value["v"] !== 1 || !isId(value["id"])) return;
|
|
54
|
+
if (value["kind"] !== "user" && value["kind"] !== "service") return;
|
|
55
|
+
if (!isId(value["principal"]) || !isId(value["createdBy"])) return;
|
|
56
|
+
const permissions = parsePermissions(value["permissions"]);
|
|
57
|
+
if (permissions === void 0 || !isTime(value["createdAt"])) return;
|
|
58
|
+
if (value["expiresAt"] !== void 0 && !isTime(value["expiresAt"])) return;
|
|
59
|
+
if (value["name"] !== void 0 && (typeof value["name"] !== "string" || value["name"].length > MAX_NAME)) return;
|
|
60
|
+
let tenant;
|
|
61
|
+
let roles;
|
|
62
|
+
if (value["kind"] === "service") {
|
|
63
|
+
roles = idList(value["roles"]);
|
|
64
|
+
if (!isId(value["tenant"]) || roles === void 0) return;
|
|
65
|
+
tenant = value["tenant"];
|
|
66
|
+
} else if (value["roles"] !== void 0 || value["tenant"] !== void 0 && !isId(value["tenant"])) return;
|
|
67
|
+
else tenant = value["tenant"];
|
|
68
|
+
return freezeDeep(compact({
|
|
69
|
+
v: 1,
|
|
70
|
+
id: value["id"],
|
|
71
|
+
kind: value["kind"],
|
|
72
|
+
principal: value["principal"],
|
|
73
|
+
tenant,
|
|
74
|
+
roles,
|
|
75
|
+
permissions,
|
|
76
|
+
createdBy: value["createdBy"],
|
|
77
|
+
createdAt: value["createdAt"],
|
|
78
|
+
expiresAt: value["expiresAt"],
|
|
79
|
+
name: value["name"]
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
function leafByKey(tree) {
|
|
83
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const leaf of listPermissions(tree)) {
|
|
85
|
+
byKey.set(leaf.key, leaf);
|
|
86
|
+
for (const old of formerKeys(leaf)) byKey.set(old, leaf);
|
|
87
|
+
}
|
|
88
|
+
return byKey;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The delegation a credential narrows its principal to: OAuth scopes for
|
|
92
|
+
* permissions on every instance, one RFC 9396 entry per id otherwise. A key
|
|
93
|
+
* the definitions no longer declare is dropped, so a credential whose every
|
|
94
|
+
* key is gone delegates nothing and every check is denied.
|
|
95
|
+
*/
|
|
96
|
+
function credentialDelegation(credential, permissions) {
|
|
97
|
+
const leaves = leafByKey(permissions);
|
|
98
|
+
const scopes = [];
|
|
99
|
+
const details = [];
|
|
100
|
+
for (const entry of credential.permissions) {
|
|
101
|
+
const leaf = leaves.get(entry.permission);
|
|
102
|
+
if (leaf === void 0) continue;
|
|
103
|
+
if (entry.ids === void 0) {
|
|
104
|
+
scopes.push(leaf.scope);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
for (const id of entry.ids) details.push({
|
|
108
|
+
type: leaf.resource,
|
|
109
|
+
actions: [leaf.action],
|
|
110
|
+
identifier: id
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return compact({
|
|
114
|
+
scopes: scopes.length > 0 || details.length === 0 ? scopes : void 0,
|
|
115
|
+
authorizationDetails: details.length > 0 ? details : void 0
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The subject a verified credential acts as. A `user` credential is its
|
|
120
|
+
* owner, passed live as `owner` (roles and memberships as of this request),
|
|
121
|
+
* with the credential's delegation: every check is the owner's rights
|
|
122
|
+
* intersected with the key's. One that names a `tenant` makes it the active
|
|
123
|
+
* tenant, and `createPermDock` then keeps only the owner's memberships
|
|
124
|
+
* inside it and no global role. A `service` credential is a `service`
|
|
125
|
+
* principal whose only membership is `roles` in `tenant`. An owner whose id
|
|
126
|
+
* is not the credential's `principal`, or who is not a user, is anonymous.
|
|
127
|
+
*/
|
|
128
|
+
function credentialSubject(credential, options) {
|
|
129
|
+
const delegation = credentialDelegation(credential, options.permissions);
|
|
130
|
+
let principal;
|
|
131
|
+
if (credential.kind === "user") {
|
|
132
|
+
const owner = options.owner ?? {
|
|
133
|
+
id: credential.principal,
|
|
134
|
+
kind: "user"
|
|
135
|
+
};
|
|
136
|
+
if (owner.id !== credential.principal || owner.kind !== void 0 && owner.kind !== "user") return anonymousSubject();
|
|
137
|
+
principal = compact({
|
|
138
|
+
...owner,
|
|
139
|
+
kind: "user",
|
|
140
|
+
tenant: credential.tenant ?? owner.tenant,
|
|
141
|
+
credential
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
const membership = compact({
|
|
145
|
+
tenant: credential.tenant,
|
|
146
|
+
roles: credential.roles ?? [],
|
|
147
|
+
via: "credential"
|
|
148
|
+
});
|
|
149
|
+
principal = compact({
|
|
150
|
+
id: credential.principal,
|
|
151
|
+
kind: "service",
|
|
152
|
+
tenant: credential.tenant,
|
|
153
|
+
memberships: [membership],
|
|
154
|
+
credential
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return freezeDeep(compact({
|
|
158
|
+
principal,
|
|
159
|
+
delegation,
|
|
160
|
+
context: {},
|
|
161
|
+
expiresAt: credential.expiresAt
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
function credentialToken(credential) {
|
|
165
|
+
const payload = JSON.stringify({
|
|
166
|
+
t: "credential",
|
|
167
|
+
id: credential.id,
|
|
168
|
+
kind: credential.kind,
|
|
169
|
+
principal: credential.principal,
|
|
170
|
+
tenant: credential.tenant ?? null,
|
|
171
|
+
roles: credential.roles ?? null,
|
|
172
|
+
permissions: credential.permissions,
|
|
173
|
+
createdBy: credential.createdBy,
|
|
174
|
+
expiresAt: credential.expiresAt ?? null,
|
|
175
|
+
name: credential.name ?? null
|
|
176
|
+
});
|
|
177
|
+
return `pd1.${bytesToBase64Url(sha256(payload))}`;
|
|
178
|
+
}
|
|
179
|
+
function denied(reason, detail) {
|
|
180
|
+
return freezeDeep({
|
|
181
|
+
outcome: "denied",
|
|
182
|
+
denials: [compact({
|
|
183
|
+
role: null,
|
|
184
|
+
reason,
|
|
185
|
+
detail
|
|
186
|
+
})]
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function permissionEntries(request, declared) {
|
|
190
|
+
if (!isReadonlyArray(request.permissions) || request.permissions.length === 0) return;
|
|
191
|
+
const out = [];
|
|
192
|
+
for (const item of request.permissions) {
|
|
193
|
+
const entry = permissionEntry(item);
|
|
194
|
+
if (entry === void 0 || declared.get(entry.leaf.key) === void 0) return;
|
|
195
|
+
out.push(entry);
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function permissionEntry(item) {
|
|
200
|
+
if (!isScopedInput(item)) return { leaf: item };
|
|
201
|
+
const ids = idList(item.ids);
|
|
202
|
+
return ids === void 0 ? void 0 : {
|
|
203
|
+
leaf: item.permission,
|
|
204
|
+
ids
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function isScopedInput(item) {
|
|
208
|
+
return "permission" in item && "ids" in item;
|
|
209
|
+
}
|
|
210
|
+
function refuse(reason, detail) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
decision: denied(reason, detail)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function checkRequest(permdock, request, now) {
|
|
217
|
+
const creator = permdock.subject.principal;
|
|
218
|
+
if (creator === null) return refuse("anonymous");
|
|
219
|
+
if (creator.kind === "link" || "credential" in creator) return refuse("exceeds-creator", { creator: creator.kind === "link" ? "link" : "credential" });
|
|
220
|
+
const entries = permissionEntries(request, leafByKey(permdock.permissions));
|
|
221
|
+
if (entries === void 0 || !isId(request.id) || request.expiresAt !== void 0 && (!isTime(request.expiresAt) || request.expiresAt <= now)) return refuse("validation");
|
|
222
|
+
const subject = permdock.subject;
|
|
223
|
+
for (const entry of entries) for (const id of entry.ids ?? [void 0]) if (coveredByDelegation(entry.leaf, subject.delegation, id, subject.actor !== void 0) !== void 0) return refuse("exceeds-creator", { permission: entry.leaf.key });
|
|
224
|
+
const credential = parseCredential(compact({
|
|
225
|
+
v: 1,
|
|
226
|
+
id: request.id,
|
|
227
|
+
kind: request.kind,
|
|
228
|
+
principal: request.kind === "service" ? request.principal ?? request.id : creator.id,
|
|
229
|
+
tenant: request.tenant,
|
|
230
|
+
roles: request.kind === "service" ? request.roles : void 0,
|
|
231
|
+
permissions: entries.map((entry) => compact({
|
|
232
|
+
permission: entry.leaf.key,
|
|
233
|
+
ids: entry.ids
|
|
234
|
+
})),
|
|
235
|
+
createdBy: creator.id,
|
|
236
|
+
createdAt: now,
|
|
237
|
+
expiresAt: request.expiresAt === void 0 ? void 0 : Math.floor(request.expiresAt),
|
|
238
|
+
name: request.name
|
|
239
|
+
}));
|
|
240
|
+
if (credential === void 0) return refuse("validation");
|
|
241
|
+
if (credential.tenant !== void 0) {
|
|
242
|
+
const tenant = credential.tenant;
|
|
243
|
+
if (!permdock.tenants().includes(tenant)) return refuse("exceeds-creator", { tenant });
|
|
244
|
+
}
|
|
245
|
+
if (credential.kind === "service" && credential.tenant !== void 0) {
|
|
246
|
+
const tenant = credential.tenant;
|
|
247
|
+
const roles = new Set(permdock.assignableRoles({ tenant }).map((role) => role.key));
|
|
248
|
+
for (const name of credential.roles ?? []) if (!roles.has(name)) return refuse("exceeds-creator", { role: name });
|
|
249
|
+
const ceiling = new Set(permdock.assignablePermissions({ tenant }).map((leaf) => leaf.key));
|
|
250
|
+
for (const entry of credential.permissions) if (!ceiling.has(entry.permission)) return refuse("exceeds-creator", { permission: entry.permission });
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
ok: true,
|
|
254
|
+
credential
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The tenant whose `credentials` policy governs a credential: the key's own
|
|
259
|
+
* tenant, otherwise `active` (the creator's or the request's).
|
|
260
|
+
*/
|
|
261
|
+
function credentialTenant(credential, active) {
|
|
262
|
+
return credential.tenant ?? active;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Decides whether the subject of `permdock` may create the credential in
|
|
266
|
+
* `request`. The creator must be a signed-in user or service that is not
|
|
267
|
+
* itself a link or a credential; a delegated creator hands out only what its
|
|
268
|
+
* delegation covers. A key's tenant must be one the creator is a member of;
|
|
269
|
+
* a service key's roles among the creator's `assignableRoles` there and its
|
|
270
|
+
* permissions within `assignablePermissions` there (`exceeds-creator`). The
|
|
271
|
+
* `credentials` policy of the key's tenant (its own, or for a user key
|
|
272
|
+
* without one the creator's active tenant) then applies (`credential-policy`; a
|
|
273
|
+
* settings source that throws denies with `rule: 'unavailable'`), and
|
|
274
|
+
* `approval` makes the result `approval-required` until `approved` carries
|
|
275
|
+
* its token. PermDock never stores the credential; the application does,
|
|
276
|
+
* after a `granted` result.
|
|
277
|
+
*/
|
|
278
|
+
async function decideCredential(permdock, request, options = {}) {
|
|
279
|
+
const checked = checkRequest(permdock, request, options.now ?? Math.floor(Date.now() / 1e3));
|
|
280
|
+
if (!checked.ok) return checked.decision;
|
|
281
|
+
const { credential } = checked;
|
|
282
|
+
const tenant = credentialTenant(credential, permdock.subject.principal?.tenant);
|
|
283
|
+
let policy;
|
|
284
|
+
if (options.settings !== void 0 && tenant !== void 0) try {
|
|
285
|
+
policy = (await options.settings.settingsFor(tenant))?.credentials;
|
|
286
|
+
} catch {
|
|
287
|
+
return denied("credential-policy", { rule: "unavailable" });
|
|
288
|
+
}
|
|
289
|
+
const list = policy === void 0 ? [] : [policy];
|
|
290
|
+
const violation = credentialPolicyViolation(credential, list);
|
|
291
|
+
if (violation !== void 0) return denied("credential-policy", { rule: violation });
|
|
292
|
+
if (list.some((item) => item.approval === true)) {
|
|
293
|
+
const token = credentialToken(credential);
|
|
294
|
+
if (options.approved !== token) return freezeDeep({
|
|
295
|
+
outcome: "approval-required",
|
|
296
|
+
credential,
|
|
297
|
+
token
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return freezeDeep({
|
|
301
|
+
outcome: "granted",
|
|
302
|
+
credential
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
export { decideCredential as a, credentialTenant as i, credentialPolicyViolation as n, parseCredential as o, credentialSubject as r, credentialDelegation as t };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { i as findPermission, o as formerScopes, t as annotationsFor } from "./permissions-DVoF6dMt.js";
|
|
2
|
+
import { d as matchGrantee, s as flattenGrantee } from "./grantee-Cv5Sogfv.js";
|
|
3
|
+
import { t as isActive } from "./validity-uOp8UpYn.js";
|
|
4
|
+
//#region src/core/delegation.ts
|
|
5
|
+
/** The permission's scope and any scope it was renamed from: tokens issued before a rename keep working. */
|
|
6
|
+
function scopeNames(permission) {
|
|
7
|
+
return [permission.scope, ...formerScopes(permission)];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Whether a delegated caller may use a permission its principal holds, through
|
|
11
|
+
* OAuth `scopes`, RFC 9396 `authorizationDetails` or GNAP `access`.
|
|
12
|
+
* `undefined` means covered; otherwise the denial reason `decide` would add.
|
|
13
|
+
*/
|
|
14
|
+
function coveredByDelegation(permission, delegation, resourceId, hasActor = false) {
|
|
15
|
+
const unscoped = hasActor ? "no-delegation" : void 0;
|
|
16
|
+
if (delegation === void 0) return unscoped;
|
|
17
|
+
const hasScopes = delegation.scopes !== void 0;
|
|
18
|
+
const hasDetails = delegation.authorizationDetails !== void 0;
|
|
19
|
+
const hasAccess = delegation.access !== void 0;
|
|
20
|
+
if (!hasScopes && !hasDetails && !hasAccess) return unscoped;
|
|
21
|
+
const emptyScopes = hasScopes && (delegation.scopes?.length ?? 0) === 0;
|
|
22
|
+
const emptyAccess = hasAccess && (delegation.access?.length ?? 0) === 0;
|
|
23
|
+
if (emptyScopes && !hasDetails && !hasAccess) return "no-delegation";
|
|
24
|
+
if (emptyAccess && !hasScopes && !hasDetails) return "no-delegation";
|
|
25
|
+
if (emptyScopes && emptyAccess && !hasDetails) return "no-delegation";
|
|
26
|
+
const scopes = scopeNames(permission);
|
|
27
|
+
const scopeOk = delegation.scopes?.some((scope) => scopes.includes(scope)) ?? false;
|
|
28
|
+
const detailOk = delegation.authorizationDetails?.some((detail) => {
|
|
29
|
+
if (detail.type !== permission.resource) return false;
|
|
30
|
+
const identifier = detail["identifier"];
|
|
31
|
+
if (identifier !== void 0 && (typeof identifier !== "string" || identifier !== resourceId)) return false;
|
|
32
|
+
const actions = detail.actions;
|
|
33
|
+
if (actions === void 0) return true;
|
|
34
|
+
return Array.isArray(actions) && actions.includes(permission.action);
|
|
35
|
+
}) ?? false;
|
|
36
|
+
const accessOk = accessCovers(permission, delegation.access, resourceId);
|
|
37
|
+
if (scopeOk || detailOk || accessOk) return;
|
|
38
|
+
return "not-delegated";
|
|
39
|
+
}
|
|
40
|
+
function coversRequirements(tree, requires, delegation, resourceId, hasActor = false) {
|
|
41
|
+
return (requires ?? []).every((key) => {
|
|
42
|
+
const leaf = findPermission(tree, key);
|
|
43
|
+
return leaf !== void 0 && coveredByDelegation(leaf, delegation, resourceId, hasActor) === void 0;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function requiresStandIn(grant) {
|
|
47
|
+
return (grant.requires ?? []).length > 0 && annotationsFor(grant.permission).readOnlyHint && !flattenGrantee(grant.to).some((item) => item.kind === "role");
|
|
48
|
+
}
|
|
49
|
+
function accessCovers(permission, access, resourceId) {
|
|
50
|
+
if (access === void 0) return false;
|
|
51
|
+
return access.some((entry) => {
|
|
52
|
+
if (typeof entry === "string") return scopeNames(permission).includes(entry);
|
|
53
|
+
if (entry === null || typeof entry !== "object") return false;
|
|
54
|
+
const type = entry["type"];
|
|
55
|
+
if (typeof type !== "string" || !typeMatches(type, permission.resource)) return false;
|
|
56
|
+
const actions = entry["actions"];
|
|
57
|
+
if (actions !== void 0 && (!Array.isArray(actions) || !actions.includes(permission.action))) return false;
|
|
58
|
+
const identifier = entry["identifier"];
|
|
59
|
+
if (identifier !== void 0 && (typeof identifier !== "string" || identifier !== resourceId)) return false;
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function typeMatches(type, resource) {
|
|
64
|
+
if (type === resource) return true;
|
|
65
|
+
return type.endsWith(`/${resource}`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether the subject's principal is a `from` of the delegation: every item
|
|
69
|
+
* matches, with roles checked against the roles the principal holds here
|
|
70
|
+
* (`matchGrantee` leaves role holding to the caller) and nothing that needs a
|
|
71
|
+
* row.
|
|
72
|
+
*/
|
|
73
|
+
function handsOver(delegation, subject, heldRoles, now) {
|
|
74
|
+
const items = flattenGrantee(delegation.from);
|
|
75
|
+
if (items.length === 0 || subject.principal === null) return false;
|
|
76
|
+
return items.every((item) => {
|
|
77
|
+
if (item.kind === "role") return heldRoles.has(item.role);
|
|
78
|
+
if (item.kind === "relation") return false;
|
|
79
|
+
const match = matchGrantee(item, subject, now, void 0);
|
|
80
|
+
return match.matched && match.where === void 0;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The permission keys the policy's delegations let `subject.actor` use for
|
|
85
|
+
* `subject.principal` right now: the union over every active delegation whose
|
|
86
|
+
* `to` matches the actor and whose `from` the principal holds. `undefined`
|
|
87
|
+
* when none applies, so the call falls back to the token delegation alone.
|
|
88
|
+
* An actor with `readOnly: true` gets only each delegation's read-only keys.
|
|
89
|
+
* Attenuation only: a key here still needs a matching allow and no deny.
|
|
90
|
+
*/
|
|
91
|
+
function delegatedPermissions(delegations, subject, heldRoles, now) {
|
|
92
|
+
const actor = subject.actor;
|
|
93
|
+
if (actor === void 0 || delegations === void 0) return;
|
|
94
|
+
let keys;
|
|
95
|
+
for (const delegation of delegations) {
|
|
96
|
+
if (delegation.to.kind !== actor.kind || delegation.to.id !== void 0 && delegation.to.id !== actor.id || delegation.to.client !== void 0 && delegation.to.client !== actor.client || !isActive(delegation.validity, now) || !handsOver(delegation, subject, heldRoles, now)) continue;
|
|
97
|
+
keys ??= /* @__PURE__ */ new Set();
|
|
98
|
+
for (const key of actor.readOnly === true ? delegation.readOnly : delegation.permissions) keys.add(key);
|
|
99
|
+
}
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { requiresStandIn as i, coversRequirements as n, delegatedPermissions as r, coveredByDelegation as t };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { i as flattenApprovers } from "./approvers-DT4AVyIg.js";
|
|
2
|
+
import { r as requiredPlans } from "./problem-details-_AMOYz-w.js";
|
|
3
|
+
//#region src/core/describe.ts
|
|
4
|
+
const TENANT_REASONS = /* @__PURE__ */ new Set([
|
|
5
|
+
"tenant-mismatch",
|
|
6
|
+
"no-membership",
|
|
7
|
+
"expired-membership",
|
|
8
|
+
"scope"
|
|
9
|
+
]);
|
|
10
|
+
const DELEGATION_REASONS = /* @__PURE__ */ new Set(["not-delegated", "no-delegation"]);
|
|
11
|
+
function labelGrantee(grantee) {
|
|
12
|
+
switch (grantee.kind) {
|
|
13
|
+
case "role": return grantee.role;
|
|
14
|
+
case "plan": return grantee.plan;
|
|
15
|
+
case "actor": return grantee.actor;
|
|
16
|
+
case "authenticated": return "an authenticated user";
|
|
17
|
+
case "anyone": return "anyone";
|
|
18
|
+
case "relation": return grantee.relation;
|
|
19
|
+
case "inherit": return grantee.permission;
|
|
20
|
+
case "assurance": {
|
|
21
|
+
const parts = [];
|
|
22
|
+
if (grantee.acr !== void 0 && grantee.acr.length > 0) parts.push(`acr ${grantee.acr.join("/")}`);
|
|
23
|
+
if (grantee.amr !== void 0 && grantee.amr.length > 0) parts.push(`amr ${grantee.amr.join("/")}`);
|
|
24
|
+
return parts.length > 0 ? parts.join(" ") : "step-up";
|
|
25
|
+
}
|
|
26
|
+
default: return grantee;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const TITLES = {
|
|
30
|
+
granted: "Granted",
|
|
31
|
+
denied: "Denied",
|
|
32
|
+
approval: "Approval required",
|
|
33
|
+
tenant: "Wrong tenant",
|
|
34
|
+
delegation: "Not delegated",
|
|
35
|
+
"server-only": "Server only",
|
|
36
|
+
upgrade: "Upgrade required"
|
|
37
|
+
};
|
|
38
|
+
function labelApprover(approver) {
|
|
39
|
+
if (approver.kind === "user") return `user ${approver.id}`;
|
|
40
|
+
if (approver.kind === "permission") return `a holder of ${approver.permission}`;
|
|
41
|
+
if (approver.kind === "any-of") return `one of (${approver.of.map((entry) => flattenApprovers(entry).map(labelApprover).join(" and ")).join(", ")})`;
|
|
42
|
+
return labelGrantee(approver);
|
|
43
|
+
}
|
|
44
|
+
function approvers(approval) {
|
|
45
|
+
if (approval === void 0 || approval === "human") return [];
|
|
46
|
+
return [...flattenApprovers(approval.by), ...(approval.stages ?? []).flatMap((stage) => flattenApprovers(stage.by))].map(labelApprover);
|
|
47
|
+
}
|
|
48
|
+
function approvalDetail(permission, labels) {
|
|
49
|
+
if (labels.length === 0) return `${permission} requires human approval.`;
|
|
50
|
+
return `${permission} requires approval from ${labels.join(" and ")}.`;
|
|
51
|
+
}
|
|
52
|
+
function describe(decision, options) {
|
|
53
|
+
const messages = options?.messages;
|
|
54
|
+
const titleOf = (kind) => messages?.titles?.[kind] ?? TITLES[kind];
|
|
55
|
+
if (decision.outcome === "granted") {
|
|
56
|
+
const { permission } = decision.matched;
|
|
57
|
+
return {
|
|
58
|
+
kind: "granted",
|
|
59
|
+
title: titleOf("granted"),
|
|
60
|
+
detail: messages?.granted?.(permission) ?? `${permission} granted.`,
|
|
61
|
+
alternatives: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (decision.outcome === "approval-required") {
|
|
65
|
+
const { permission } = decision.grant;
|
|
66
|
+
const labels = approvers(decision.grant.approval);
|
|
67
|
+
return {
|
|
68
|
+
kind: "approval",
|
|
69
|
+
title: titleOf("approval"),
|
|
70
|
+
detail: messages?.approval?.(permission, labels) ?? approvalDetail(permission, labels),
|
|
71
|
+
alternatives: []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const plans = requiredPlans(decision);
|
|
75
|
+
if (plans.length > 0) return {
|
|
76
|
+
kind: "upgrade",
|
|
77
|
+
title: titleOf("upgrade"),
|
|
78
|
+
detail: messages?.upgrade?.(plans) ?? `${plans.join(" or ")} plan required.`,
|
|
79
|
+
alternatives: decision.alternatives,
|
|
80
|
+
plans
|
|
81
|
+
};
|
|
82
|
+
const reasons = decision.denials.map((denial) => denial.reason);
|
|
83
|
+
const kind = reasons.some((reason) => TENANT_REASONS.has(reason)) ? "tenant" : reasons.some((reason) => DELEGATION_REASONS.has(reason)) ? "delegation" : reasons.includes("opaque-condition") || reasons.includes("server-only") ? "server-only" : "denied";
|
|
84
|
+
const texts = messages?.reasons;
|
|
85
|
+
const detail = reasons.map((reason) => (typeof texts === "function" ? texts(reason, decision) : texts?.[reason]) ?? reason).join(messages?.separator ?? ", ");
|
|
86
|
+
return {
|
|
87
|
+
kind,
|
|
88
|
+
title: titleOf(kind),
|
|
89
|
+
detail,
|
|
90
|
+
alternatives: decision.alternatives
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { describe as t };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Qn as DeniedDecision, Yn as Decision, Zn as DenialReason } from "./policy-CFZAr_Al.js";
|
|
2
|
+
import { f as Permission } from "./permissions-DttnqqxC.js";
|
|
3
|
+
//#region src/core/describe.d.ts
|
|
4
|
+
type DecisionDescription = {
|
|
5
|
+
readonly kind: "granted" | "denied" | "approval" | "tenant" | "delegation" | "server-only" | "upgrade";
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly detail: string;
|
|
8
|
+
readonly alternatives: readonly Permission[];
|
|
9
|
+
/** On `upgrade`: the plans any of which would grant the permission. */
|
|
10
|
+
readonly plans?: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Localised text for `describe`. Every entry is optional and falls back to the
|
|
14
|
+
* English default; `kind` and `alternatives` never change.
|
|
15
|
+
*/
|
|
16
|
+
type DescribeMessages = {
|
|
17
|
+
readonly titles?: Partial<Record<DecisionDescription["kind"], string>>;
|
|
18
|
+
/**
|
|
19
|
+
* Text per denial reason; a denied detail joins the texts of its reasons
|
|
20
|
+
* with `separator`. The function form also sees the decision; `undefined`
|
|
21
|
+
* keeps the reason code, as a missing record entry does.
|
|
22
|
+
*/
|
|
23
|
+
readonly reasons?: Partial<Record<DenialReason, string>> | ((reason: DenialReason, decision: DeniedDecision) => string | undefined);
|
|
24
|
+
/** Joins denial reason texts. Default `', '`. */
|
|
25
|
+
readonly separator?: string;
|
|
26
|
+
readonly granted?: (permission: string) => string;
|
|
27
|
+
/** `approvers` is empty for `'human'` approval. */
|
|
28
|
+
readonly approval?: (permission: string, approvers: readonly string[]) => string;
|
|
29
|
+
readonly upgrade?: (plans: readonly string[]) => string;
|
|
30
|
+
};
|
|
31
|
+
type DescribeOptions = {
|
|
32
|
+
readonly messages?: DescribeMessages;
|
|
33
|
+
};
|
|
34
|
+
declare function describe(decision: Decision, options?: DescribeOptions): DecisionDescription;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { describe as i, DescribeMessages as n, DescribeOptions as r, DecisionDescription as t };
|