permdock 0.0.0 → 0.1.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 +268 -1
- package/dist/a2a/index.d.ts +178 -0
- package/dist/a2a/index.js +271 -0
- package/dist/ai-sdk/index.d.ts +74 -0
- package/dist/ai-sdk/index.js +137 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +4 -0
- package/dist/approvals-DaxY-Bdg.js +218 -0
- package/dist/approvers-aH0PJkmf.js +20 -0
- package/dist/arazzo-CTeVuXr5.js +70 -0
- package/dist/arazzo-aoTkqYsJ.js +54 -0
- package/dist/authzen/index.d.ts +48 -0
- package/dist/authzen/index.js +336 -0
- package/dist/better-auth/index.d.ts +92 -0
- package/dist/better-auth/index.js +324 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/capability-B68OFnZx.js +171 -0
- package/dist/capability-BDG_nkxt.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-B0E6bggc.js +48 -0
- package/dist/catalog-H-vFGwWK.js +55 -0
- package/dist/catalog-doc-BSo_Q_-6.js +298 -0
- package/dist/catalog-idsHc0vo.js +2 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/claude-agent/index.d.ts +81 -0
- package/dist/claude-agent/index.js +83 -0
- package/dist/clerk/index.d.ts +56 -0
- package/dist/clerk/index.js +280 -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/cloud/index.d.ts +91 -0
- package/dist/cloud/index.js +390 -0
- package/dist/cloud-BNQTrmrr.js +50 -0
- package/dist/cloud-Cuxr6Czw.js +117 -0
- package/dist/collect-B7_TIfmg.js +49 -0
- package/dist/collect-O-tOtbnk.js +350 -0
- package/dist/collect-fRPAQIWb.js +2 -0
- package/dist/commands-BMh_Etm2.js +93 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile-CQbvyIBr.d.ts +30 -0
- package/dist/compile-D22iwvuV.js +558 -0
- package/dist/config-BPYmjTOp.js +26 -0
- package/dist/connection-JvqwHODC.js +194 -0
- package/dist/context-CKEAabd7.js +73 -0
- package/dist/context-gtAwAILx.js +6 -0
- package/dist/convex/index.d.ts +40 -0
- package/dist/convex/index.js +55 -0
- package/dist/create-BKF3dgFk.d.ts +162 -0
- package/dist/create-CSkhDU9T.js +222 -0
- package/dist/credential-B_rkUNlY.js +326 -0
- package/dist/credentials-BgRX3WEg.js +268 -0
- package/dist/describe-Bt1YD5uS.js +99 -0
- package/dist/describe-DernyItf.d.ts +37 -0
- package/dist/diff-BUzf5Poi.js +48 -0
- package/dist/diff-x5Il8LVa.js +418 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-BGle8Gqi.js +1314 -0
- package/dist/doctor-Bqo6FwUa.js +53 -0
- package/dist/drizzle/index.d.ts +72 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +71 -0
- package/dist/elysia/index.js +103 -0
- package/dist/errors-DSMnAVNa.js +43 -0
- package/dist/errors-DbZ1YOnb.d.ts +138 -0
- package/dist/errors-Iy71s7Oj.js +177 -0
- package/dist/errors-m3u9TmDu.js +27 -0
- package/dist/evaluations-DsAU9ppl.js +479 -0
- package/dist/eve/index.d.ts +95 -0
- package/dist/eve/index.js +181 -0
- package/dist/express/index.d.ts +46 -0
- package/dist/express/index.js +108 -0
- package/dist/fastify/index.d.ts +56 -0
- package/dist/fastify/index.js +141 -0
- package/dist/files-B_wAGYmt.js +121 -0
- package/dist/fixtures-Cvaw0gDG.js +64 -0
- package/dist/freeze-y1_jQL4v.js +31 -0
- package/dist/from-snapshot-BMGnhmgv.js +552 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Bv04_Vzf.js +521 -0
- package/dist/headers-B5RRv3Xm.js +6 -0
- package/dist/headers-C8SHQccD.d.ts +6 -0
- package/dist/helpers-DnSOrhB9.js +682 -0
- package/dist/hono/index.d.ts +70 -0
- package/dist/hono/index.js +131 -0
- package/dist/hosted-BwKQ1BTA.js +265 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-GgVApX1k.js +111 -0
- package/dist/index-CltcQ4Bf.d.ts +138 -0
- package/dist/index-LF_hS-yn.d.ts +177 -0
- package/dist/index-fG6NSXlc.d.ts +9 -0
- package/dist/index-xIxJLwOQ.d.ts +415 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +21 -0
- package/dist/jwt/index.d.ts +87 -0
- package/dist/jwt/index.js +571 -0
- package/dist/kernel-YOGCWNLf.js +211 -0
- package/dist/kysely/index.d.ts +77 -0
- package/dist/kysely/index.js +129 -0
- package/dist/load-BZ24CpZP.js +69 -0
- package/dist/load-QOOm1z70.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-DDQbC6K0.d.ts +118 -0
- package/dist/map-DowSO96j.js +158 -0
- package/dist/map-claims-B_6I1Rn-.js +360 -0
- package/dist/map-error-RJ3ZsUyA.js +16 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-CnFEjVEp.js +37 -0
- package/dist/mcp/index.d.ts +127 -0
- package/dist/mcp/index.js +563 -0
- package/dist/nest/index.d.ts +80 -0
- package/dist/nest/index.js +270 -0
- package/dist/next/client.d.ts +24 -0
- package/dist/next/client.js +31 -0
- package/dist/next/index.d.ts +110 -0
- package/dist/next/index.js +195 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +2 -0
- package/dist/node/index.d.ts +43 -0
- package/dist/node/index.js +66 -0
- package/dist/openai/index.d.ts +71 -0
- package/dist/openai/index.js +105 -0
- package/dist/openapi/index.d.ts +93 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-CwePaF7C.js +164 -0
- package/dist/openapi-DWD0Uc_4.js +250 -0
- package/dist/openapi-DcqM8nU7.js +306 -0
- package/dist/openapi-import-BWRUNNeW.js +5959 -0
- package/dist/openapi-schema-D8j_T9tO.js +6902 -0
- package/dist/orpc/index.d.ts +64 -0
- package/dist/orpc/index.js +188 -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-DPfIvFMU.js +135 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +795 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-yoZ7EjxX.js +2956 -0
- package/dist/permissions-B4apnahQ.d.ts +185 -0
- package/dist/permissions-B8vCg_bI.d.ts +52 -0
- package/dist/permissions-CofRqesP.js +142 -0
- package/dist/permissions-DC7GROPA.js +600 -0
- package/dist/pg-4GJWDUxG.js +953 -0
- package/dist/pins-CqE1jZuF.js +12 -0
- package/dist/plugin-4gr7IxFJ.js +65 -0
- package/dist/policy-ANp36PnI.js +971 -0
- package/dist/policy-CInmNGkM.d.ts +1959 -0
- package/dist/policy-Cq2fB8Q5.d.ts +20 -0
- package/dist/prisma/index.d.ts +135 -0
- package/dist/prisma/index.js +430 -0
- package/dist/problem-Cgp9-KZA.js +226 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-B4K3BKoX.js +138 -0
- package/dist/protected-BuPAycJT.d.ts +26 -0
- package/dist/protected-wY7nXhY9.d.ts +23 -0
- package/dist/provider-_wx46HpB.js +48 -0
- package/dist/provider-wTAUda2v.d.ts +6 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -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 +42 -0
- package/dist/react-native/index.js +174 -0
- package/dist/refs-ChLSxbST.js +90 -0
- package/dist/relations-r4soIFZB.js +426 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BOFU3chG.js +233 -0
- package/dist/rls-CJVja9Ww.js +2119 -0
- package/dist/rls-graph-DxQ39PJj.js +371 -0
- package/dist/rls-helpers-BxRuif0l.js +1346 -0
- package/dist/rls-import-CXV6LBlS.js +942 -0
- package/dist/rls-migrate-DrFPbf8p.js +366 -0
- package/dist/row-check-BajTgQZ-.d.ts +43 -0
- package/dist/run-CJ6UvS3a.js +155 -0
- package/dist/runtime-DP9ttFh1.js +27 -0
- package/dist/saas-BRrZzJkM.js +1193 -0
- package/dist/schema-P8yoma__.js +296 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-9NFa0XVZ.js +1445 -0
- package/dist/scopes-cKVp_WTw.js +212 -0
- package/dist/server/index.d.ts +123 -0
- package/dist/server/index.js +8 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Cfr9-7YC.d.ts +104 -0
- package/dist/skills-C_lpV4Jx.js +50 -0
- package/dist/skills-Dx3QLnMY.js +110 -0
- package/dist/snapshot-DE8dE_hz.js +1051 -0
- package/dist/solid/index.d.ts +63 -0
- package/dist/solid/index.js +197 -0
- package/dist/source-DoBBYJi2.js +6 -0
- package/dist/sources-DOpW5Ila.d.ts +347 -0
- package/dist/sources-DVHM3Mwq.js +284 -0
- package/dist/sql-Bll1d7y0.js +22 -0
- package/dist/src-CTNT-PBW.js +430 -0
- package/dist/ssf/index.d.ts +3 -0
- package/dist/ssf/index.js +622 -0
- package/dist/store-Bq90395i.d.ts +27 -0
- package/dist/store-C6hFW6ta.js +508 -0
- package/dist/stores-CkG2HrSg.js +140 -0
- package/dist/stores-CvOnu_2T.d.ts +41 -0
- package/dist/stream-BhMfZOwr.d.ts +12 -0
- package/dist/stream-Q_xlB6Ko.js +52 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-xt7V845g.js +82 -0
- package/dist/supabase/index.d.ts +67 -0
- package/dist/supabase/index.js +4 -0
- package/dist/supabase/middleware.d.ts +77 -0
- package/dist/supabase/middleware.js +68 -0
- package/dist/supabase-D8UlBA7Z.js +83 -0
- package/dist/supabase-DTxGtYdi.js +796 -0
- package/dist/supabase-hook-DW-xP4hU.js +2 -0
- package/dist/supabase-hook-D_OqGb_V.js +1528 -0
- package/dist/svelte/Protected.svelte +34 -0
- package/dist/svelte/index.d.ts +10 -0
- package/dist/svelte/index.js +53 -0
- package/dist/svelte/runtime.d.ts +6 -0
- package/dist/svelte/runtime.js +3 -0
- package/dist/svelte/source.d.ts +10 -0
- package/dist/svelte/source.js +8 -0
- package/dist/terminal/index.d.ts +171 -0
- package/dist/terminal/index.js +821 -0
- package/dist/testing/index.d.ts +656 -0
- package/dist/testing/index.js +3208 -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/trpc/index.d.ts +80 -0
- package/dist/trpc/index.js +205 -0
- package/dist/types-B8w5PFtS.d.ts +141 -0
- package/dist/types-B9dmiiab.d.ts +66 -0
- package/dist/types-BBK-exvJ.d.ts +177 -0
- package/dist/types-BvBk1wgn.d.ts +118 -0
- package/dist/types-CxquUByI.d.ts +9 -0
- package/dist/types-D6Z6-rKJ.d.ts +312 -0
- package/dist/types-DbWdEfcm.d.ts +87 -0
- package/dist/types-bva3i4Gf.d.ts +98 -0
- package/dist/types-i4BAOvdb.d.ts +97 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +30 -0
- package/dist/usage-1ozMubLY.js +42 -0
- package/dist/usage-CUnK6Ou9.js +530 -0
- package/dist/usage-PyEor6GA.js +2 -0
- package/dist/validity-D0uRmJKJ.js +224 -0
- package/dist/verifier-B90agnhl.js +461 -0
- package/dist/version-CnSh33Fj.js +16 -0
- package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
- package/dist/vue/index.d.ts +57 -0
- package/dist/vue/index.js +203 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +313 -0
- package/package.json +423 -4
- package/rulesets/permdock.yaml +29 -0
- package/schemas/catalog-v1.json +507 -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 +139 -0
- package/schemas/supabase-manifest-v1.json +217 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +211 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +83 -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 +127 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +54 -0
- package/skills/permdock-data/references/relationships.md +65 -0
- package/skills/permdock-tenancy/SKILL.md +77 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +480 -0
- package/skills/permdock-wire/references/policy-options.md +43 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
import { n as isReadonlyArray, r as sole, t as compact } from "./compact-y6vfu2_f.js";
|
|
2
|
+
import { n as freezeDeep } from "./freeze-y1_jQL4v.js";
|
|
3
|
+
import { C as ownGet, S as isForbiddenKey, i as findPermission, o as formerScopes } from "./permissions-DC7GROPA.js";
|
|
4
|
+
import { c as isConditionRef, i as resolveConditionRef, s as isConditionDate, u as parentHop } from "./refs-ChLSxbST.js";
|
|
5
|
+
import { a as resolveScope, c as scopeChain, d as subjectMemberships, f as tenantOf, l as scopeIdOf, r as findScope, t as activeFor, u as scopeList } from "./scopes-cKVp_WTw.js";
|
|
6
|
+
import { C as listPlans, h as resourceRoleCondition, l as matchGrantee, s as flattenGrantee, w as listRoles } from "./grantee-Bv04_Vzf.js";
|
|
7
|
+
import { f as tenantsOf, i as isMembershipExpired, o as membershipField, r as inTeam, t as isActive } from "./validity-D0uRmJKJ.js";
|
|
8
|
+
import { n as sha256, t as bytesToBase64Url } from "./sha256-BO0swRXL.js";
|
|
9
|
+
//#region src/conditions/evaluate.ts
|
|
10
|
+
function isExpired(membership, now) {
|
|
11
|
+
return membership.expiresAt !== void 0 && membership.expiresAt <= now;
|
|
12
|
+
}
|
|
13
|
+
function resolveRef(ref, subject) {
|
|
14
|
+
return resolveConditionRef(ref, subject);
|
|
15
|
+
}
|
|
16
|
+
function unwrap(value, subject) {
|
|
17
|
+
if (isConditionRef(value)) return resolveRef(value.ref, subject);
|
|
18
|
+
if (isConditionDate(value)) return new Date(isoInstant(value.date) ?? NaN);
|
|
19
|
+
if (isReadonlyArray(value)) return value.map((item) => unwrap(item, subject));
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
const ISO_INSTANT = /^(\d{4}-\d{2}-\d{2})(?:[T ](\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?)(Z|[+-]\d{2}(?::?\d{2})?)?)?$/u;
|
|
23
|
+
function isoInstant(value) {
|
|
24
|
+
const match = ISO_INSTANT.exec(value);
|
|
25
|
+
if (match === null) return;
|
|
26
|
+
const [, day, time, zone] = match;
|
|
27
|
+
let offset = zone ?? (time === void 0 ? "" : "Z");
|
|
28
|
+
if (/^[+-]\d{2}$/u.test(offset)) offset = `${offset}:00`;
|
|
29
|
+
else if (/^[+-]\d{4}$/u.test(offset)) offset = `${offset.slice(0, 3)}:${offset.slice(3)}`;
|
|
30
|
+
const parsed = Date.parse(time === void 0 ? `${day}` : `${day}T${time}${offset}`);
|
|
31
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
32
|
+
}
|
|
33
|
+
function instantOf(value) {
|
|
34
|
+
if (value instanceof Date) {
|
|
35
|
+
const time = value.getTime();
|
|
36
|
+
return Number.isNaN(time) ? void 0 : time;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
39
|
+
if (typeof value === "string") return isoInstant(value);
|
|
40
|
+
}
|
|
41
|
+
function comparablePair(left, right) {
|
|
42
|
+
if (left instanceof Date || right instanceof Date) {
|
|
43
|
+
const a = instantOf(left);
|
|
44
|
+
const b = instantOf(right);
|
|
45
|
+
return a === void 0 || b === void 0 ? void 0 : [a, b];
|
|
46
|
+
}
|
|
47
|
+
const kind = typeof left;
|
|
48
|
+
if (kind === typeof right && (kind === "string" || kind === "number" || kind === "boolean")) return [left, right];
|
|
49
|
+
}
|
|
50
|
+
function compare(op, left, right) {
|
|
51
|
+
if (left === void 0 || left === null || right === void 0 || right === null) return false;
|
|
52
|
+
const comparable = comparablePair(left, right);
|
|
53
|
+
if (comparable === void 0) return false;
|
|
54
|
+
const [a, b] = comparable;
|
|
55
|
+
switch (op) {
|
|
56
|
+
case "eq": return a === b;
|
|
57
|
+
case "ne": return a !== b;
|
|
58
|
+
case "gt": return a > b;
|
|
59
|
+
case "gte": return a >= b;
|
|
60
|
+
case "lt": return a < b;
|
|
61
|
+
case "lte": return a <= b;
|
|
62
|
+
default:
|
|
63
|
+
/* v8 ignore next */
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function contains(left, right) {
|
|
68
|
+
if (left === void 0 || left === null || right === void 0 || right === null) return false;
|
|
69
|
+
if (typeof left === "string" && typeof right === "string") return left.includes(right);
|
|
70
|
+
if (Array.isArray(left)) return left.includes(right);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function inList(left, right) {
|
|
74
|
+
if (left === void 0 || left === null || !Array.isArray(right)) return false;
|
|
75
|
+
return right.includes(left);
|
|
76
|
+
}
|
|
77
|
+
function matchesParentHop(data, parents, on) {
|
|
78
|
+
for (const parent of parents ?? []) {
|
|
79
|
+
const hop = parentHop(parent);
|
|
80
|
+
if (hop.resource !== void 0 && hop.resource !== on.resource) continue;
|
|
81
|
+
if (ownGet(data, hop.field) === on.id) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
function evaluateMemberOf(condition, data, subject, now, scopes) {
|
|
86
|
+
if (data === null || typeof data !== "object") return false;
|
|
87
|
+
const rowValue = ownGet(data, condition.field);
|
|
88
|
+
if (rowValue === void 0 || rowValue === null) return false;
|
|
89
|
+
const memberships = subjectMemberships(subject.principal?.memberships, scopes);
|
|
90
|
+
const wanted = new Set(condition.roles);
|
|
91
|
+
const active = subject.principal?.tenant === "" ? void 0 : subject.principal?.tenant;
|
|
92
|
+
const scope = condition.scope === "resource" ? void 0 : resolveScope(scopes, condition.scope);
|
|
93
|
+
if (condition.scope !== "resource" && scope === void 0) return false;
|
|
94
|
+
for (const membership of memberships) {
|
|
95
|
+
if (isExpired(membership, now)) continue;
|
|
96
|
+
if (wanted.size > 0 && !membership.roles.some((role) => wanted.has(role))) continue;
|
|
97
|
+
if (scope !== void 0) {
|
|
98
|
+
if (membership.scope !== scope || membership.id !== rowValue) continue;
|
|
99
|
+
const tenant = tenantOf(membership, scopes);
|
|
100
|
+
if (active !== void 0 && tenant !== void 0 && tenant !== active) continue;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (membership.on === void 0) continue;
|
|
104
|
+
if (condition.resource !== void 0 && membership.on.resource !== condition.resource) {
|
|
105
|
+
if (matchesParentHop(data, condition.parents, membership.on)) return true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (membership.on.id === rowValue) return true;
|
|
109
|
+
if (matchesParentHop(data, condition.parents, membership.on)) return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function evaluateCondition(condition, data, subject, now = Date.now() / 1e3, scopes = scopeList(void 0), related, onOpaque) {
|
|
114
|
+
switch (condition.op) {
|
|
115
|
+
case "and": return condition.conditions.every((child) => evaluateCondition(child, data, subject, now, scopes, related, onOpaque));
|
|
116
|
+
case "or": return condition.conditions.some((child) => evaluateCondition(child, data, subject, now, scopes, related, onOpaque));
|
|
117
|
+
case "not": return !evaluateCondition(condition.condition, data, subject, now, scopes, related, onOpaque);
|
|
118
|
+
case "isNull": {
|
|
119
|
+
if (data === null || typeof data !== "object") return false;
|
|
120
|
+
const value = ownGet(data, condition.field);
|
|
121
|
+
const isNull = value === null || value === void 0;
|
|
122
|
+
return condition.value ? isNull : !isNull;
|
|
123
|
+
}
|
|
124
|
+
case "in":
|
|
125
|
+
case "notIn": {
|
|
126
|
+
if (data === null || typeof data !== "object") return false;
|
|
127
|
+
const left = ownGet(data, condition.field);
|
|
128
|
+
const matched = inList(left, unwrap(condition.value, subject));
|
|
129
|
+
return condition.op === "in" ? matched : !matched && left !== void 0 && left !== null;
|
|
130
|
+
}
|
|
131
|
+
case "contains":
|
|
132
|
+
if (data === null || typeof data !== "object") return false;
|
|
133
|
+
return contains(ownGet(data, condition.field), unwrap(condition.value, subject));
|
|
134
|
+
case "eq":
|
|
135
|
+
case "ne":
|
|
136
|
+
case "gt":
|
|
137
|
+
case "gte":
|
|
138
|
+
case "lt":
|
|
139
|
+
case "lte":
|
|
140
|
+
if (condition.field === "_" && condition.op === "eq") return condition.value === true;
|
|
141
|
+
if (data === null || typeof data !== "object") return false;
|
|
142
|
+
return compare(condition.op, ownGet(data, condition.field), unwrap(condition.value, subject));
|
|
143
|
+
case "memberOf": return evaluateMemberOf(condition, data, subject, now, scopes);
|
|
144
|
+
case "related": return related?.(condition, data) === true;
|
|
145
|
+
case "sqlFunction": return evaluateCondition(condition.twin, data, subject, now, scopes, related, onOpaque);
|
|
146
|
+
case "opaque":
|
|
147
|
+
onOpaque?.();
|
|
148
|
+
return false;
|
|
149
|
+
default:
|
|
150
|
+
/* v8 ignore next */
|
|
151
|
+
return condition;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/core/arazzo.ts
|
|
156
|
+
const VERSION = /^1\.[01]\.\d+(?:-.+)?$/u;
|
|
157
|
+
const STEP_TARGETS = [
|
|
158
|
+
"operationId",
|
|
159
|
+
"operationPath",
|
|
160
|
+
"workflowId",
|
|
161
|
+
"channelPath"
|
|
162
|
+
];
|
|
163
|
+
const denied = (reason) => freezeDeep({
|
|
164
|
+
outcome: "denied",
|
|
165
|
+
denials: [{
|
|
166
|
+
role: null,
|
|
167
|
+
reason
|
|
168
|
+
}],
|
|
169
|
+
alternatives: []
|
|
170
|
+
});
|
|
171
|
+
function isRecord$1(value) {
|
|
172
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
173
|
+
}
|
|
174
|
+
function asString(value) {
|
|
175
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
176
|
+
}
|
|
177
|
+
function parseParameters(raw, components, where) {
|
|
178
|
+
if (raw === void 0) return [];
|
|
179
|
+
if (!Array.isArray(raw)) return `${where}: parameters must be an array`;
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const item of raw) {
|
|
182
|
+
if (!isRecord$1(item)) return `${where}: a parameter is not an object`;
|
|
183
|
+
const reference = asString(item["reference"]);
|
|
184
|
+
if (reference !== void 0) {
|
|
185
|
+
const match = /^\$components\.parameters\.(.+)$/u.exec(reference);
|
|
186
|
+
const target = match?.[1] === void 0 ? void 0 : components[match[1]];
|
|
187
|
+
const name = isRecord$1(target) ? asString(target["name"]) : void 0;
|
|
188
|
+
if (!isRecord$1(target) || name === void 0 || !("value" in target)) return `${where}: unresolved parameter reference ${reference}`;
|
|
189
|
+
out.push({
|
|
190
|
+
name,
|
|
191
|
+
value: "value" in item ? item["value"] : target["value"]
|
|
192
|
+
});
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const name = asString(item["name"]);
|
|
196
|
+
if (name === void 0 || !("value" in item)) return `${where}: a parameter needs a name and a value`;
|
|
197
|
+
out.push({
|
|
198
|
+
name,
|
|
199
|
+
value: item["value"]
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
function parseDocument(arazzo) {
|
|
205
|
+
if (!isRecord$1(arazzo)) return "Arazzo document is not an object";
|
|
206
|
+
const version = arazzo["arazzo"];
|
|
207
|
+
if (typeof version !== "string" || !VERSION.test(version)) return "arazzo must be a 1.0.x or 1.1.x version";
|
|
208
|
+
if (!isRecord$1(arazzo["info"])) return "info is required";
|
|
209
|
+
const rawSources = arazzo["sourceDescriptions"];
|
|
210
|
+
if (!Array.isArray(rawSources) || rawSources.length === 0) return "sourceDescriptions needs at least one entry";
|
|
211
|
+
const sources = [];
|
|
212
|
+
for (const source of rawSources) {
|
|
213
|
+
const name = isRecord$1(source) ? asString(source["name"]) : void 0;
|
|
214
|
+
if (!isRecord$1(source) || name === void 0 || asString(source["url"]) === void 0) return "a sourceDescription needs a name and a url";
|
|
215
|
+
if (sources.some((item) => item.name === name)) return `duplicate sourceDescription ${name}`;
|
|
216
|
+
sources.push({
|
|
217
|
+
name,
|
|
218
|
+
type: asString(source["type"]) ?? "openapi"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const components = isRecord$1(arazzo["components"]) && isRecord$1(arazzo["components"]["parameters"]) ? arazzo["components"]["parameters"] : {};
|
|
222
|
+
const rawWorkflows = arazzo["workflows"];
|
|
223
|
+
if (!Array.isArray(rawWorkflows) || rawWorkflows.length === 0) return "workflows needs at least one entry";
|
|
224
|
+
const workflows = [];
|
|
225
|
+
for (const item of rawWorkflows) {
|
|
226
|
+
const workflowId = isRecord$1(item) ? asString(item["workflowId"]) : void 0;
|
|
227
|
+
if (!isRecord$1(item) || workflowId === void 0) return "a workflow needs a workflowId";
|
|
228
|
+
if (workflows.some((workflow) => workflow.workflowId === workflowId)) return `duplicate workflowId ${workflowId}`;
|
|
229
|
+
const parameters = parseParameters(item["parameters"], components, workflowId);
|
|
230
|
+
if (typeof parameters === "string") return parameters;
|
|
231
|
+
const rawSteps = item["steps"];
|
|
232
|
+
if (!Array.isArray(rawSteps) || rawSteps.length === 0) return `${workflowId}: steps needs at least one entry`;
|
|
233
|
+
const steps = [];
|
|
234
|
+
for (const step of rawSteps) {
|
|
235
|
+
const stepId = isRecord$1(step) ? asString(step["stepId"]) : void 0;
|
|
236
|
+
if (!isRecord$1(step) || stepId === void 0) return `${workflowId}: a step needs a stepId`;
|
|
237
|
+
if (steps.some((other) => other.stepId === stepId)) return `${workflowId}: duplicate stepId ${stepId}`;
|
|
238
|
+
const targets = STEP_TARGETS.filter((field) => asString(step[field]) !== void 0);
|
|
239
|
+
if (targets.length !== 1 && !(targets.length === 2 && targets.includes("operationId") && targets.includes("channelPath"))) return `${workflowId}.${stepId}: a step needs exactly one of operationId, operationPath or workflowId`;
|
|
240
|
+
const stepParameters = parseParameters(step["parameters"], components, `${workflowId}.${stepId}`);
|
|
241
|
+
if (typeof stepParameters === "string") return stepParameters;
|
|
242
|
+
steps.push(compact({
|
|
243
|
+
stepId,
|
|
244
|
+
operationId: asString(step["operationId"]),
|
|
245
|
+
operationPath: asString(step["operationPath"]),
|
|
246
|
+
workflowId: asString(step["workflowId"]),
|
|
247
|
+
channelPath: asString(step["channelPath"]),
|
|
248
|
+
parameters: stepParameters
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
workflows.push({
|
|
252
|
+
workflowId,
|
|
253
|
+
parameters,
|
|
254
|
+
steps
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
sources,
|
|
259
|
+
workflows
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function unescapePointer(token) {
|
|
263
|
+
return token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
264
|
+
}
|
|
265
|
+
function readPointer(doc, pointer) {
|
|
266
|
+
if (!pointer.startsWith("#/")) return;
|
|
267
|
+
let current = doc;
|
|
268
|
+
for (const raw of pointer.slice(2).split("/")) {
|
|
269
|
+
if (!isRecord$1(current)) return;
|
|
270
|
+
let token;
|
|
271
|
+
try {
|
|
272
|
+
token = unescapePointer(decodeURIComponent(raw));
|
|
273
|
+
} catch {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!Object.hasOwn(current, token)) return;
|
|
277
|
+
current = current[token];
|
|
278
|
+
}
|
|
279
|
+
return current;
|
|
280
|
+
}
|
|
281
|
+
function findOperation(openapi, operationId) {
|
|
282
|
+
if (!isRecord$1(openapi) || !isRecord$1(openapi["paths"])) return;
|
|
283
|
+
for (const pathItem of Object.values(openapi["paths"])) {
|
|
284
|
+
if (!isRecord$1(pathItem)) continue;
|
|
285
|
+
for (const [method, operation] of Object.entries(pathItem)) if (!method.startsWith("x-") && isRecord$1(operation) && operation["operationId"] === operationId) return operation;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** One description, or a map of descriptions keyed by source `name`. */
|
|
289
|
+
function descriptionFor(openapi, name) {
|
|
290
|
+
if (isRecord$1(openapi) && !("openapi" in openapi) && !("paths" in openapi) && isRecord$1(openapi[name])) return openapi[name];
|
|
291
|
+
return openapi;
|
|
292
|
+
}
|
|
293
|
+
function permissionKeysOf(node) {
|
|
294
|
+
if (!isRecord$1(node)) return [];
|
|
295
|
+
const raw = node["x-permdock-permissions"];
|
|
296
|
+
if (!Array.isArray(raw)) return [];
|
|
297
|
+
return raw.filter((item) => typeof item === "string");
|
|
298
|
+
}
|
|
299
|
+
/** `$inputs.a.b` against the frame's inputs; anything else that is a runtime expression is unknown. */
|
|
300
|
+
function valueOf(value, inputs) {
|
|
301
|
+
if (typeof value !== "string" || !value.includes("$")) return {
|
|
302
|
+
known: true,
|
|
303
|
+
value
|
|
304
|
+
};
|
|
305
|
+
const match = /^\$inputs\.([^#{}]+)$/u.exec(value);
|
|
306
|
+
if (match?.[1] === void 0) return value.startsWith("$") || value.includes("{$") ? { known: false } : {
|
|
307
|
+
known: true,
|
|
308
|
+
value
|
|
309
|
+
};
|
|
310
|
+
let current = inputs;
|
|
311
|
+
for (const part of match[1].split(".")) {
|
|
312
|
+
if (!isRecord$1(current) || !Object.hasOwn(current, part)) return { known: false };
|
|
313
|
+
current = current[part];
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
known: true,
|
|
317
|
+
value: current
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function bind(parameters, inputs) {
|
|
321
|
+
const record = {};
|
|
322
|
+
let provisional = false;
|
|
323
|
+
for (const parameter of parameters) {
|
|
324
|
+
const bound = valueOf(parameter.value, inputs);
|
|
325
|
+
if (!bound.known) {
|
|
326
|
+
provisional = true;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
Object.defineProperty(record, parameter.name, {
|
|
330
|
+
value: bound.value,
|
|
331
|
+
enumerable: true,
|
|
332
|
+
writable: true,
|
|
333
|
+
configurable: true
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
record,
|
|
338
|
+
provisional
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function flatten(doc, workflowId, inputs, seen, out) {
|
|
342
|
+
if (seen.has(workflowId)) return "workflowId cycle";
|
|
343
|
+
const workflow = doc.workflows.find((item) => item.workflowId === workflowId);
|
|
344
|
+
if (workflow === void 0) return `unknown workflowId ${workflowId}`;
|
|
345
|
+
seen.add(workflowId);
|
|
346
|
+
for (const step of workflow.steps) {
|
|
347
|
+
if (step.workflowId === void 0 || step.workflowId.startsWith("$sourceDescriptions.")) {
|
|
348
|
+
out.push({
|
|
349
|
+
step,
|
|
350
|
+
workflow,
|
|
351
|
+
inputs
|
|
352
|
+
});
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const nested = flatten(doc, step.workflowId, bind(step.parameters, inputs).record, seen, out);
|
|
356
|
+
if (nested !== void 0) return nested;
|
|
357
|
+
}
|
|
358
|
+
seen.delete(workflowId);
|
|
359
|
+
}
|
|
360
|
+
function locate(doc, openapi, step) {
|
|
361
|
+
if (step.workflowId !== void 0) return {
|
|
362
|
+
reason: "unsupported",
|
|
363
|
+
detail: "workflows in another Arazzo document are not resolved"
|
|
364
|
+
};
|
|
365
|
+
let sourceName;
|
|
366
|
+
let operationId = step.operationId;
|
|
367
|
+
let pointer;
|
|
368
|
+
if (step.operationPath !== void 0) {
|
|
369
|
+
const match = /^\{?\$sourceDescriptions\.([^.}#]+)(?:\.url)?\}?(#.*)?$/u.exec(step.operationPath);
|
|
370
|
+
if (match?.[1] === void 0) return {
|
|
371
|
+
reason: "undocumented",
|
|
372
|
+
detail: "operationPath is not a $sourceDescriptions reference"
|
|
373
|
+
};
|
|
374
|
+
sourceName = match[1];
|
|
375
|
+
pointer = match[2] ?? "";
|
|
376
|
+
} else if (operationId !== void 0) {
|
|
377
|
+
const match = /^\$sourceDescriptions\.([^.]+)\.(.+)$/u.exec(operationId);
|
|
378
|
+
if (match?.[1] !== void 0 && match[2] !== void 0) {
|
|
379
|
+
sourceName = match[1];
|
|
380
|
+
operationId = match[2];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const candidates = sourceName === void 0 ? doc.sources.filter((source) => source.type !== "arazzo") : doc.sources.filter((source) => source.name === sourceName);
|
|
384
|
+
if (candidates.length === 0) return {
|
|
385
|
+
reason: "undocumented",
|
|
386
|
+
detail: sourceName === void 0 ? "no API sourceDescription" : `unknown sourceDescription ${sourceName}`
|
|
387
|
+
};
|
|
388
|
+
if (step.channelPath !== void 0) return {
|
|
389
|
+
reason: "unsupported",
|
|
390
|
+
detail: "AsyncAPI steps are not evaluated"
|
|
391
|
+
};
|
|
392
|
+
const found = [];
|
|
393
|
+
for (const source of candidates) {
|
|
394
|
+
const description = descriptionFor(openapi, source.name);
|
|
395
|
+
const node = pointer === void 0 ? operationId === void 0 ? void 0 : findOperation(description, operationId) : readPointer(description, pointer);
|
|
396
|
+
if (node !== void 0 || candidates.length === 1) found.push({
|
|
397
|
+
source,
|
|
398
|
+
node
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (found.length > 1) return {
|
|
402
|
+
reason: "undocumented",
|
|
403
|
+
detail: "operationId matches several sources; qualify it with $sourceDescriptions"
|
|
404
|
+
};
|
|
405
|
+
const [hit] = found;
|
|
406
|
+
if (hit?.source.type === "asyncapi") return {
|
|
407
|
+
reason: "unsupported",
|
|
408
|
+
detail: "AsyncAPI steps are not evaluated"
|
|
409
|
+
};
|
|
410
|
+
if (hit === void 0 || hit.source.type === "arazzo" || !isRecord$1(hit.node)) return {
|
|
411
|
+
reason: "undocumented",
|
|
412
|
+
detail: "operation not found"
|
|
413
|
+
};
|
|
414
|
+
return compact({
|
|
415
|
+
node: hit.node,
|
|
416
|
+
operationId: asString(hit.node["operationId"]) ?? operationId
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function resolve(doc, openapi, tree, flat) {
|
|
420
|
+
const { step, workflow, inputs } = flat;
|
|
421
|
+
const base = {
|
|
422
|
+
stepId: step.stepId,
|
|
423
|
+
workflowId: workflow.workflowId
|
|
424
|
+
};
|
|
425
|
+
const located = locate(doc, openapi, step);
|
|
426
|
+
const { record, provisional } = bind([...workflow.parameters.filter((parameter) => !step.parameters.some((own) => own.name === parameter.name)), ...step.parameters], inputs);
|
|
427
|
+
const data = Object.keys(inputs).length + Object.keys(record).length === 0 ? void 0 : {
|
|
428
|
+
...inputs,
|
|
429
|
+
...record
|
|
430
|
+
};
|
|
431
|
+
if ("reason" in located) return {
|
|
432
|
+
...base,
|
|
433
|
+
...compact({ operationId: step.operationId }),
|
|
434
|
+
keys: [],
|
|
435
|
+
finding: located,
|
|
436
|
+
data,
|
|
437
|
+
provisional
|
|
438
|
+
};
|
|
439
|
+
const keys = permissionKeysOf(located.node);
|
|
440
|
+
const unknown = tree === void 0 ? void 0 : keys.find((key) => findPermission(tree, key) === void 0);
|
|
441
|
+
return {
|
|
442
|
+
...base,
|
|
443
|
+
...compact({ operationId: located.operationId }),
|
|
444
|
+
keys,
|
|
445
|
+
...keys.length === 0 ? { finding: {
|
|
446
|
+
reason: "undocumented",
|
|
447
|
+
detail: "operation has no x-permdock-permissions"
|
|
448
|
+
} } : unknown === void 0 ? {} : { finding: {
|
|
449
|
+
reason: "unknown-key",
|
|
450
|
+
detail: unknown
|
|
451
|
+
} },
|
|
452
|
+
data,
|
|
453
|
+
provisional
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function resolveAll(input, tree) {
|
|
457
|
+
const doc = parseDocument(input.arazzo);
|
|
458
|
+
if (typeof doc === "string") return {
|
|
459
|
+
workflowId: input.workflowId ?? "",
|
|
460
|
+
error: doc
|
|
461
|
+
};
|
|
462
|
+
const workflowId = input.workflowId ?? doc.workflows[0]?.workflowId ?? "";
|
|
463
|
+
const flat = [];
|
|
464
|
+
const error = flatten(doc, workflowId, input.inputs ?? {}, /* @__PURE__ */ new Set(), flat);
|
|
465
|
+
if (error !== void 0) return {
|
|
466
|
+
workflowId,
|
|
467
|
+
error
|
|
468
|
+
};
|
|
469
|
+
return {
|
|
470
|
+
workflowId,
|
|
471
|
+
steps: flat.map((item) => resolve(doc, input.openapi, tree, item))
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function arazzoFindings(input, tree) {
|
|
475
|
+
const resolved = resolveAll(input, tree);
|
|
476
|
+
if ("error" in resolved) return [{
|
|
477
|
+
stepId: "document",
|
|
478
|
+
workflowId: resolved.workflowId,
|
|
479
|
+
reason: "validation",
|
|
480
|
+
detail: resolved.error
|
|
481
|
+
}];
|
|
482
|
+
return resolved.steps.flatMap((step) => step.finding === void 0 ? [] : [{
|
|
483
|
+
stepId: step.stepId,
|
|
484
|
+
workflowId: step.workflowId,
|
|
485
|
+
reason: step.finding.reason,
|
|
486
|
+
detail: step.finding.detail
|
|
487
|
+
}]);
|
|
488
|
+
}
|
|
489
|
+
function worst(outcomes) {
|
|
490
|
+
if (outcomes.includes("denied")) return "denied";
|
|
491
|
+
if (outcomes.includes("approval-required")) return "approval-required";
|
|
492
|
+
return "granted";
|
|
493
|
+
}
|
|
494
|
+
function combine(decisions) {
|
|
495
|
+
const outcome = worst(decisions.map((item) => item.outcome));
|
|
496
|
+
return decisions.find((item) => item.outcome === outcome) ?? denied("undocumented");
|
|
497
|
+
}
|
|
498
|
+
function simulateArazzo(input, tree, decide) {
|
|
499
|
+
const resolved = resolveAll(input, tree);
|
|
500
|
+
if ("error" in resolved) return freezeDeep({
|
|
501
|
+
workflowId: resolved.workflowId === "" ? "unknown" : resolved.workflowId,
|
|
502
|
+
outcome: "denied",
|
|
503
|
+
steps: [{
|
|
504
|
+
stepId: "document",
|
|
505
|
+
permissions: [],
|
|
506
|
+
decision: denied("validation")
|
|
507
|
+
}]
|
|
508
|
+
});
|
|
509
|
+
const steps = resolved.steps.map((step) => {
|
|
510
|
+
const owner = step.workflowId === resolved.workflowId ? void 0 : step.workflowId;
|
|
511
|
+
if (step.finding !== void 0) return compact({
|
|
512
|
+
stepId: step.stepId,
|
|
513
|
+
workflowId: owner,
|
|
514
|
+
operationId: step.operationId,
|
|
515
|
+
permissions: [],
|
|
516
|
+
decision: denied(step.finding.reason === "unsupported" ? "unsupported" : "undocumented")
|
|
517
|
+
});
|
|
518
|
+
const permissions = step.keys.flatMap((key) => {
|
|
519
|
+
const leaf = tree === void 0 ? void 0 : findPermission(tree, key);
|
|
520
|
+
return leaf === void 0 ? [] : [leaf];
|
|
521
|
+
});
|
|
522
|
+
return compact({
|
|
523
|
+
stepId: step.stepId,
|
|
524
|
+
workflowId: owner,
|
|
525
|
+
operationId: step.operationId,
|
|
526
|
+
permissions,
|
|
527
|
+
decision: combine(permissions.map((permission) => decide(permission, step.data))),
|
|
528
|
+
provisional: step.provisional ? true : void 0
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
return freezeDeep({
|
|
532
|
+
workflowId: resolved.workflowId,
|
|
533
|
+
outcome: worst(steps.map((step) => step.decision.outcome)),
|
|
534
|
+
steps
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function isArazzoSimulateInput(value) {
|
|
538
|
+
return isRecord$1(value) && "arazzo" in value;
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/core/delegation.ts
|
|
542
|
+
/** The permission's scope and any scope it was renamed from: tokens issued before a rename keep working. */
|
|
543
|
+
function scopeNames(permission) {
|
|
544
|
+
return [permission.scope, ...formerScopes(permission)];
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Whether a delegated caller may use a permission its principal holds, through
|
|
548
|
+
* OAuth `scopes`, RFC 9396 `authorizationDetails` or GNAP `access`.
|
|
549
|
+
* `undefined` means covered; otherwise the denial reason `decide` would add.
|
|
550
|
+
*/
|
|
551
|
+
function coveredByDelegation(permission, delegation, resourceId, hasActor = false) {
|
|
552
|
+
const unscoped = hasActor ? "no-delegation" : void 0;
|
|
553
|
+
if (delegation === void 0) return unscoped;
|
|
554
|
+
const hasScopes = delegation.scopes !== void 0;
|
|
555
|
+
const hasDetails = delegation.authorizationDetails !== void 0;
|
|
556
|
+
const hasAccess = delegation.access !== void 0;
|
|
557
|
+
if (!hasScopes && !hasDetails && !hasAccess) return unscoped;
|
|
558
|
+
const emptyScopes = hasScopes && (delegation.scopes?.length ?? 0) === 0;
|
|
559
|
+
const emptyAccess = hasAccess && (delegation.access?.length ?? 0) === 0;
|
|
560
|
+
if (emptyScopes && !hasDetails && !hasAccess) return "no-delegation";
|
|
561
|
+
if (emptyAccess && !hasScopes && !hasDetails) return "no-delegation";
|
|
562
|
+
if (emptyScopes && emptyAccess && !hasDetails) return "no-delegation";
|
|
563
|
+
const scopes = scopeNames(permission);
|
|
564
|
+
const scopeOk = delegation.scopes?.some((scope) => scopes.includes(scope)) ?? false;
|
|
565
|
+
const detailOk = delegation.authorizationDetails?.some((detail) => {
|
|
566
|
+
if (detail.type !== permission.resource) return false;
|
|
567
|
+
const identifier = detail["identifier"];
|
|
568
|
+
if (identifier !== void 0 && (typeof identifier !== "string" || identifier !== resourceId)) return false;
|
|
569
|
+
const actions = detail.actions;
|
|
570
|
+
if (actions === void 0) return true;
|
|
571
|
+
return Array.isArray(actions) && actions.includes(permission.action);
|
|
572
|
+
}) ?? false;
|
|
573
|
+
const accessOk = accessCovers(permission, delegation.access, resourceId);
|
|
574
|
+
if (scopeOk || detailOk || accessOk) return;
|
|
575
|
+
return "not-delegated";
|
|
576
|
+
}
|
|
577
|
+
function accessCovers(permission, access, resourceId) {
|
|
578
|
+
if (access === void 0) return false;
|
|
579
|
+
return access.some((entry) => {
|
|
580
|
+
if (typeof entry === "string") return scopeNames(permission).includes(entry);
|
|
581
|
+
if (entry === null || typeof entry !== "object") return false;
|
|
582
|
+
const type = entry["type"];
|
|
583
|
+
if (typeof type !== "string" || !typeMatches(type, permission.resource)) return false;
|
|
584
|
+
const actions = entry["actions"];
|
|
585
|
+
if (actions !== void 0 && (!Array.isArray(actions) || !actions.includes(permission.action))) return false;
|
|
586
|
+
const identifier = entry["identifier"];
|
|
587
|
+
if (identifier !== void 0 && (typeof identifier !== "string" || identifier !== resourceId)) return false;
|
|
588
|
+
return true;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function typeMatches(type, resource) {
|
|
592
|
+
if (type === resource) return true;
|
|
593
|
+
return type.endsWith(`/${resource}`);
|
|
594
|
+
}
|
|
595
|
+
function resourceIdOf(data) {
|
|
596
|
+
if (data === null || typeof data !== "object" || !("id" in data)) return;
|
|
597
|
+
const id = data.id;
|
|
598
|
+
return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Whether the subject's principal is a `from` of the delegation: every item
|
|
602
|
+
* matches, with roles checked against the roles the principal holds here
|
|
603
|
+
* (`matchGrantee` leaves role holding to the caller) and nothing that needs a
|
|
604
|
+
* row.
|
|
605
|
+
*/
|
|
606
|
+
function handsOver(delegation, subject, heldRoles, now) {
|
|
607
|
+
const items = flattenGrantee(delegation.from);
|
|
608
|
+
if (items.length === 0 || subject.principal === null) return false;
|
|
609
|
+
return items.every((item) => {
|
|
610
|
+
if (item.kind === "role") return heldRoles.has(item.role);
|
|
611
|
+
if (item.kind === "relation") return false;
|
|
612
|
+
const match = matchGrantee(item, subject, now, void 0);
|
|
613
|
+
return match.matched && match.where === void 0;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* The permission keys the policy's delegations let `subject.actor` use for
|
|
618
|
+
* `subject.principal` right now: the union over every active delegation whose
|
|
619
|
+
* `to` matches the actor and whose `from` the principal holds. `undefined`
|
|
620
|
+
* when none applies, so the call falls back to the token delegation alone.
|
|
621
|
+
* An actor with `readOnly: true` gets only each delegation's read-only keys.
|
|
622
|
+
* Attenuation only: a key here still needs a matching allow and no deny.
|
|
623
|
+
*/
|
|
624
|
+
function delegatedPermissions(delegations, subject, heldRoles, now) {
|
|
625
|
+
const actor = subject.actor;
|
|
626
|
+
if (actor === void 0 || delegations === void 0) return;
|
|
627
|
+
let keys;
|
|
628
|
+
for (const delegation of delegations) {
|
|
629
|
+
if (delegation.to.kind !== actor.kind || delegation.to.id !== void 0 && delegation.to.id !== actor.id || !isActive(delegation.validity, now) || !handsOver(delegation, subject, heldRoles, now)) continue;
|
|
630
|
+
keys ??= /* @__PURE__ */ new Set();
|
|
631
|
+
for (const key of actor.readOnly === true ? delegation.readOnly : delegation.permissions) keys.add(key);
|
|
632
|
+
}
|
|
633
|
+
return keys;
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/core/canonical-json.ts
|
|
637
|
+
function isRecord(value) {
|
|
638
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* JSON with sorted keys and `undefined` members dropped, so equal values hash
|
|
642
|
+
* equally. Dates become ISO strings, bigints `"<n>n"`, and a repeated object
|
|
643
|
+
* on the current path `"[Circular]"`, so any validated row serialises.
|
|
644
|
+
*/
|
|
645
|
+
function canonicalJson(value, path = /* @__PURE__ */ new Set()) {
|
|
646
|
+
if (typeof value === "bigint") return JSON.stringify(`${value}n`);
|
|
647
|
+
if (value instanceof Date) return JSON.stringify(Number.isNaN(value.getTime()) ? null : value.toISOString());
|
|
648
|
+
if (value !== null && typeof value === "object") {
|
|
649
|
+
if (path.has(value)) return "\"[Circular]\"";
|
|
650
|
+
path.add(value);
|
|
651
|
+
try {
|
|
652
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item, path)).join(",")}]`;
|
|
653
|
+
if (isRecord(value)) return `{${Object.keys(value).toSorted().filter((key) => value[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key], path)}`).join(",")}}`;
|
|
654
|
+
} finally {
|
|
655
|
+
path.delete(value);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return JSON.stringify(value) ?? "null";
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/core/token.ts
|
|
662
|
+
/**
|
|
663
|
+
* Only stable identity goes into the token, so refreshing a session (new
|
|
664
|
+
* roles, assurance or claims) does not orphan an outstanding approval.
|
|
665
|
+
*/
|
|
666
|
+
function principalIdentity(principal) {
|
|
667
|
+
if (principal === null) return null;
|
|
668
|
+
return {
|
|
669
|
+
id: principal.id,
|
|
670
|
+
tenant: principal.tenant ?? null,
|
|
671
|
+
issuer: principal.issuer ?? null
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/** A row's `version` field as token input; `null` when it is missing or not a scalar. */
|
|
675
|
+
function versionOf(row, field) {
|
|
676
|
+
if (row === null || typeof row !== "object" || !Object.hasOwn(row, field)) return null;
|
|
677
|
+
const value = row[field];
|
|
678
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
679
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" ? String(value) : null;
|
|
680
|
+
}
|
|
681
|
+
/** The digest that binds a token to the call's data when no row id does. */
|
|
682
|
+
function payloadDigest(data) {
|
|
683
|
+
return bytesToBase64Url(sha256(canonicalJson(data)));
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* `version` is set only for an approval that goes stale on a resource
|
|
687
|
+
* change; without it the payload, and so every other token, is unchanged.
|
|
688
|
+
* `payload` is set when `resourceId` is `*` (a collection action or a row
|
|
689
|
+
* without an id), so an approval covers the arguments it was given and no
|
|
690
|
+
* others.
|
|
691
|
+
*/
|
|
692
|
+
function decisionToken(input) {
|
|
693
|
+
const payload = JSON.stringify({
|
|
694
|
+
key: input.key,
|
|
695
|
+
resourceId: input.resourceId,
|
|
696
|
+
principal: principalIdentity(input.principal),
|
|
697
|
+
actor: input.actor === void 0 ? null : {
|
|
698
|
+
id: input.actor.id,
|
|
699
|
+
kind: input.actor.kind
|
|
700
|
+
},
|
|
701
|
+
fingerprint: input.fingerprint,
|
|
702
|
+
...input.version === void 0 ? {} : { version: input.version },
|
|
703
|
+
...input.payload === void 0 ? {} : { payload: input.payload }
|
|
704
|
+
});
|
|
705
|
+
return `pd1.${bytesToBase64Url(sha256(payload))}`;
|
|
706
|
+
}
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region src/conditions/bind.ts
|
|
709
|
+
function isPrimitive(value) {
|
|
710
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
711
|
+
}
|
|
712
|
+
function bindValue(value, subject, only) {
|
|
713
|
+
if (isConditionRef(value) && only !== void 0 && !only(value.ref)) return value;
|
|
714
|
+
if (isConditionRef(value)) {
|
|
715
|
+
const resolved = resolveConditionRef(value.ref, subject);
|
|
716
|
+
if (Array.isArray(resolved)) return resolved.filter((item) => isPrimitive(item));
|
|
717
|
+
return isPrimitive(resolved) ? resolved : null;
|
|
718
|
+
}
|
|
719
|
+
if (Array.isArray(value)) return value.map((item) => bindValue(item, subject, only));
|
|
720
|
+
return value;
|
|
721
|
+
}
|
|
722
|
+
function bindArg(arg, subject, only) {
|
|
723
|
+
return arg !== null && typeof arg === "object" && "field" in arg && !isConditionRef(arg) ? arg : bindValue(arg, subject, only);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Replaces every `principal.*` / `context.*` ref with the subject's value, so
|
|
727
|
+
* a `where()` result compiles the same without the subject. A ref the subject
|
|
728
|
+
* does not hold binds to `null` (or `[]` in a list), which never matches, as
|
|
729
|
+
* in `evaluateCondition`. With `only`, refs it rejects stay refs.
|
|
730
|
+
*/
|
|
731
|
+
function bindConditionRefs(condition, subject, only) {
|
|
732
|
+
switch (condition.op) {
|
|
733
|
+
case "and":
|
|
734
|
+
case "or": return {
|
|
735
|
+
op: condition.op,
|
|
736
|
+
conditions: condition.conditions.map((child) => bindConditionRefs(child, subject, only))
|
|
737
|
+
};
|
|
738
|
+
case "not": return {
|
|
739
|
+
op: "not",
|
|
740
|
+
condition: bindConditionRefs(condition.condition, subject, only)
|
|
741
|
+
};
|
|
742
|
+
case "in":
|
|
743
|
+
case "notIn": {
|
|
744
|
+
const value = condition.value;
|
|
745
|
+
if (isConditionRef(value) && only !== void 0 && !only(value.ref)) return condition;
|
|
746
|
+
const bound = bindValue(value, subject, only);
|
|
747
|
+
return {
|
|
748
|
+
...condition,
|
|
749
|
+
value: Array.isArray(bound) ? bound : []
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
case "eq":
|
|
753
|
+
case "ne":
|
|
754
|
+
case "gt":
|
|
755
|
+
case "gte":
|
|
756
|
+
case "lt":
|
|
757
|
+
case "lte":
|
|
758
|
+
case "contains": return {
|
|
759
|
+
...condition,
|
|
760
|
+
value: bindValue(condition.value, subject, only)
|
|
761
|
+
};
|
|
762
|
+
case "sqlFunction": return {
|
|
763
|
+
...condition,
|
|
764
|
+
args: condition.args.map((arg) => bindArg(arg, subject, only)),
|
|
765
|
+
twin: bindConditionRefs(condition.twin, subject, only)
|
|
766
|
+
};
|
|
767
|
+
case "isNull":
|
|
768
|
+
case "memberOf":
|
|
769
|
+
case "related":
|
|
770
|
+
case "opaque": return condition;
|
|
771
|
+
default: return condition;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/core/where-scope.ts
|
|
776
|
+
const ALWAYS = {
|
|
777
|
+
op: "eq",
|
|
778
|
+
field: "_",
|
|
779
|
+
value: true
|
|
780
|
+
};
|
|
781
|
+
function eq(field, value) {
|
|
782
|
+
return value === void 0 ? {
|
|
783
|
+
op: "or",
|
|
784
|
+
conditions: []
|
|
785
|
+
} : {
|
|
786
|
+
op: "eq",
|
|
787
|
+
field,
|
|
788
|
+
value
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
/** One equality per partitioning scope key on the membership's chain, outermost first. */
|
|
792
|
+
function namedFilters(membership, scope) {
|
|
793
|
+
if (findScope(scope.scopes, membership.scope ?? "") === void 0 || !inTeam(membership, scope.scopes, scope.team) || !activeFor(membership, scope.scopes, scope.tenant) || membership.scope === void 0) return null;
|
|
794
|
+
const filters = [];
|
|
795
|
+
for (const name of scopeChain(scope.scopes, membership.scope).toReversed()) {
|
|
796
|
+
const key = findScope(scope.scopes, name)?.key;
|
|
797
|
+
if (key !== void 0 && scope.partitioned(name, key)) filters.push(eq(key, scopeIdOf(membership, name)));
|
|
798
|
+
}
|
|
799
|
+
return filters;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* The row filters a grant's membership implies, or `null` when the grant
|
|
803
|
+
* reaches no row in this scope (another tenant, no active tenant, expired).
|
|
804
|
+
*/
|
|
805
|
+
function scopeFilters(grant, scope) {
|
|
806
|
+
if (grant.scope === void 0) return [];
|
|
807
|
+
const membership = grant.membership;
|
|
808
|
+
if (membership === void 0 || isMembershipExpired(membership, scope.now)) return null;
|
|
809
|
+
if (typeof grant.scope === "string") return membership.scope === grant.scope ? namedFilters(membership, scope) : null;
|
|
810
|
+
const on = membership.on;
|
|
811
|
+
if (on === void 0) return null;
|
|
812
|
+
if (scope.resources === void 0) return on.resource === grant.scope.resource && on.resource === scope.resource ? [eq("id", on.id)] : null;
|
|
813
|
+
const roleResource = scope.resources.get(grant.scope.resource);
|
|
814
|
+
if (on.resource !== grant.scope.resource && membershipField(roleResource, on.resource, scope.resources) === void 0) return null;
|
|
815
|
+
const walked = scope.graph === true ? resourceRoleCondition(scope.resources.get(scope.resource), on.resource, [on.id], scope.resources) : void 0;
|
|
816
|
+
if (walked !== void 0) return [walked];
|
|
817
|
+
const field = membershipField(scope.resources.get(scope.resource), on.resource, scope.resources);
|
|
818
|
+
return field === void 0 ? null : [eq(field, on.id)];
|
|
819
|
+
}
|
|
820
|
+
function all(conditions) {
|
|
821
|
+
if (conditions.length === 0) return ALWAYS;
|
|
822
|
+
return sole(conditions) ?? {
|
|
823
|
+
op: "and",
|
|
824
|
+
conditions: [...conditions]
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function scopedCondition(grant, scope) {
|
|
828
|
+
const filters = scopeFilters(grant, scope);
|
|
829
|
+
if (filters === null) return null;
|
|
830
|
+
const parts = grant.where === void 0 ? filters : [...filters, grant.where];
|
|
831
|
+
return parts.length === 0 ? "always" : all(parts);
|
|
832
|
+
}
|
|
833
|
+
/** Whether an unconditional `deny` removes every row `allow` could reach. */
|
|
834
|
+
function covers(deny, allow, scopes) {
|
|
835
|
+
if (deny.where !== void 0) return false;
|
|
836
|
+
const denied = deny.membership;
|
|
837
|
+
if (deny.scope === void 0 || denied === void 0) return deny.scope === void 0;
|
|
838
|
+
const allowed = allow.membership;
|
|
839
|
+
if (allowed === void 0) return false;
|
|
840
|
+
if (typeof deny.scope === "object") return typeof allow.scope === "object" && denied.on?.resource === allowed.on?.resource && denied.on?.id === allowed.on?.id;
|
|
841
|
+
if (typeof allow.scope !== "string" || allowed.scope === void 0) return false;
|
|
842
|
+
const chain = scopeChain(scopes, deny.scope);
|
|
843
|
+
return scopeChain(scopes, allowed.scope).includes(deny.scope) && chain.every((name) => scopeIdOf(allowed, name) === scopeIdOf(denied, name));
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* The portable filter for one permission's grants: allows OR together, each
|
|
847
|
+
* narrowed to its membership's tenant, team or resource; any applicable
|
|
848
|
+
* portable deny is subtracted, and an unconditional one empties the result.
|
|
849
|
+
*/
|
|
850
|
+
function collect(grants, scope) {
|
|
851
|
+
const none = {
|
|
852
|
+
op: "or",
|
|
853
|
+
conditions: []
|
|
854
|
+
};
|
|
855
|
+
const partial = grants.some((grant) => grant.portable === false);
|
|
856
|
+
const denies = [];
|
|
857
|
+
for (const grant of grants) {
|
|
858
|
+
if (grant.effect !== "deny" || grant.portable === false || !isActive(grant.validity, scope.now)) continue;
|
|
859
|
+
const condition = scopedCondition(grant, scope);
|
|
860
|
+
if (condition === "always") return {
|
|
861
|
+
condition: none,
|
|
862
|
+
partial
|
|
863
|
+
};
|
|
864
|
+
if (condition !== null) denies.push({
|
|
865
|
+
grant,
|
|
866
|
+
condition
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
const parts = [];
|
|
870
|
+
for (const grant of grants) {
|
|
871
|
+
if (grant.effect !== "allow" || grant.portable === false || !isActive(grant.validity, scope.now)) continue;
|
|
872
|
+
const condition = scopedCondition(grant, scope);
|
|
873
|
+
if (condition === null || denies.some((deny) => covers(deny.grant, grant, scope.scopes))) continue;
|
|
874
|
+
parts.push(all([...condition === "always" ? [] : [condition], ...denies.map((deny) => ({
|
|
875
|
+
op: "not",
|
|
876
|
+
condition: deny.condition
|
|
877
|
+
}))]));
|
|
878
|
+
}
|
|
879
|
+
if (parts.length === 0) return {
|
|
880
|
+
condition: none,
|
|
881
|
+
partial
|
|
882
|
+
};
|
|
883
|
+
return {
|
|
884
|
+
condition: sole(parts) ?? {
|
|
885
|
+
op: "or",
|
|
886
|
+
conditions: parts
|
|
887
|
+
},
|
|
888
|
+
partial
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* `collect`, with every subject ref bound to `scope.subject` and the subject
|
|
893
|
+
* attached (not enumerable) for `memberOf`, which needs its memberships.
|
|
894
|
+
*/
|
|
895
|
+
function whereFromGrants(grants, scope) {
|
|
896
|
+
const { condition, partial } = collect(grants, scope);
|
|
897
|
+
const result = {
|
|
898
|
+
condition: bindConditionRefs(condition, scope.subject),
|
|
899
|
+
partial
|
|
900
|
+
};
|
|
901
|
+
Object.defineProperty(result, "subject", {
|
|
902
|
+
value: scope.subject,
|
|
903
|
+
enumerable: false
|
|
904
|
+
});
|
|
905
|
+
Object.defineProperty(result, "scopes", {
|
|
906
|
+
value: scope.scopes,
|
|
907
|
+
enumerable: false
|
|
908
|
+
});
|
|
909
|
+
if (scope.resources !== void 0) Object.defineProperty(result, "resources", {
|
|
910
|
+
value: scope.resources,
|
|
911
|
+
enumerable: false
|
|
912
|
+
});
|
|
913
|
+
return freezeDeep(result);
|
|
914
|
+
}
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/core/snapshot.ts
|
|
917
|
+
function snapshotGrant(grant, membership) {
|
|
918
|
+
const scope = grant.scope === "global" ? void 0 : typeof grant.scope === "string" ? grant.scope : { resource: grant.scope.resource };
|
|
919
|
+
const entry = compact({
|
|
920
|
+
permission: grant.permission.key,
|
|
921
|
+
effect: grant.effect,
|
|
922
|
+
role: grant.role,
|
|
923
|
+
to: grant.to,
|
|
924
|
+
where: grant.portable ? grant.where : void 0,
|
|
925
|
+
check: grant.portable ? grant.check : void 0,
|
|
926
|
+
approval: grant.approval,
|
|
927
|
+
scope,
|
|
928
|
+
membership,
|
|
929
|
+
portable: grant.portable ? void 0 : false,
|
|
930
|
+
fields: grant.fields,
|
|
931
|
+
validity: grant.validity
|
|
932
|
+
});
|
|
933
|
+
return freezeDeep(entry);
|
|
934
|
+
}
|
|
935
|
+
/** Claim refs: the snapshot principal does not carry `claims`, so they are bound to the subject's values. */
|
|
936
|
+
function isClaimRef(ref) {
|
|
937
|
+
return ref.startsWith("principal.claim.") || ref.startsWith("principal.claims.");
|
|
938
|
+
}
|
|
939
|
+
function bindClaims(grant, subject) {
|
|
940
|
+
if (grant.where === void 0 && grant.check === void 0) return grant;
|
|
941
|
+
return freezeDeep(compact({
|
|
942
|
+
...grant,
|
|
943
|
+
where: grant.where === void 0 ? void 0 : bindConditionRefs(grant.where, subject, isClaimRef),
|
|
944
|
+
check: grant.check === void 0 ? void 0 : bindConditionRefs(grant.check, subject, isClaimRef)
|
|
945
|
+
}));
|
|
946
|
+
}
|
|
947
|
+
function notEntitledOf(items) {
|
|
948
|
+
const seen = /* @__PURE__ */ new Map();
|
|
949
|
+
for (const { grant } of items) {
|
|
950
|
+
const key = `${grant.permission.key}\u0000${grant.role ?? ""}`;
|
|
951
|
+
if (!seen.has(key)) seen.set(key, {
|
|
952
|
+
permission: grant.permission.key,
|
|
953
|
+
role: grant.role,
|
|
954
|
+
to: grant.to
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
return seen.size === 0 ? void 0 : [...seen.values()];
|
|
958
|
+
}
|
|
959
|
+
function buildSnapshot(input) {
|
|
960
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
961
|
+
const principal = input.subject.principal;
|
|
962
|
+
const allTenants = tenantsOf(principal, scopeList(input.scopes));
|
|
963
|
+
const tenants = input.tenants === "all" ? allTenants : principal?.tenant === void 0 ? [] : [principal.tenant];
|
|
964
|
+
const include = input.include;
|
|
965
|
+
const included = (permission) => include === void 0 || include.length === 0 || include.some((prefix) => permission.key === prefix || permission.key.startsWith(`${prefix}.`) || permission.resource === prefix);
|
|
966
|
+
const grants = input.grants.filter((item) => included(item.grant.permission)).map((item) => bindClaims(snapshotGrant(item.grant, item.membership), input.subject));
|
|
967
|
+
const assignableFor = input.assignable;
|
|
968
|
+
const assignable = assignableFor === void 0 ? [] : tenants.map((tenant) => {
|
|
969
|
+
const entry = assignableFor(tenant);
|
|
970
|
+
return {
|
|
971
|
+
...entry,
|
|
972
|
+
permissions: entry.permissions.filter(included)
|
|
973
|
+
};
|
|
974
|
+
});
|
|
975
|
+
return freezeDeep(compact({
|
|
976
|
+
v: 1,
|
|
977
|
+
issuedAt: now,
|
|
978
|
+
subject: compact({
|
|
979
|
+
principal: principal === null ? null : compact({
|
|
980
|
+
id: principal.id,
|
|
981
|
+
roles: principal.roles ?? [],
|
|
982
|
+
plans: principal.plans,
|
|
983
|
+
tenant: principal.tenant,
|
|
984
|
+
memberships: principal.memberships
|
|
985
|
+
}),
|
|
986
|
+
delegation: snapshotDelegation(input.subject, input.delegated !== void 0),
|
|
987
|
+
context: input.subject.context
|
|
988
|
+
}),
|
|
989
|
+
roles: input.roles,
|
|
990
|
+
audiences: input.audiences,
|
|
991
|
+
grants,
|
|
992
|
+
tenants,
|
|
993
|
+
include,
|
|
994
|
+
simulated: input.simulated === true ? true : void 0,
|
|
995
|
+
expiresAt: input.subject.expiresAt,
|
|
996
|
+
scopes: input.scopes,
|
|
997
|
+
assignable: assignable.length > 0 ? assignable : void 0,
|
|
998
|
+
delegated: input.delegated === void 0 ? void 0 : [...input.delegated].toSorted(),
|
|
999
|
+
notEntitled: notEntitledOf((input.notEntitled ?? []).filter((item) => included(item.grant.permission))),
|
|
1000
|
+
vocabulary: input.vocabulary === void 0 ? void 0 : compact({
|
|
1001
|
+
roles: listRoles(input.vocabulary.roles).length === 0 ? void 0 : Object.fromEntries(listRoles(input.vocabulary.roles).map((leaf) => [leaf.key, leaf])),
|
|
1002
|
+
plans: listPlans(input.vocabulary.plans).length === 0 ? void 0 : Object.fromEntries(listPlans(input.vocabulary.plans).map((leaf) => [leaf.key, leaf]))
|
|
1003
|
+
})
|
|
1004
|
+
}));
|
|
1005
|
+
}
|
|
1006
|
+
async function signSnapshot(snapshot, signer, audience) {
|
|
1007
|
+
const payload = { snapshot };
|
|
1008
|
+
if (snapshot.subject.principal !== null) payload["sub"] = snapshot.subject.principal.id;
|
|
1009
|
+
return await signer.sign(payload, compact({
|
|
1010
|
+
typ: "permdock-snapshot+jwt",
|
|
1011
|
+
audience,
|
|
1012
|
+
expiresAt: snapshot.expiresAt
|
|
1013
|
+
}));
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* The token delegation as the client sees it. An actor with no token
|
|
1017
|
+
* delegation gets an empty `scopes` list so the client ceiling agrees with
|
|
1018
|
+
* `no-delegation`, unless a policy delegation applies (`delegated`), which
|
|
1019
|
+
* then is the ceiling.
|
|
1020
|
+
*/
|
|
1021
|
+
function snapshotDelegation(subject, delegated) {
|
|
1022
|
+
const delegation = subject.delegation;
|
|
1023
|
+
if (subject.actor === void 0 || delegated) return delegation;
|
|
1024
|
+
if (delegation?.scopes === void 0 && delegation?.authorizationDetails === void 0 && delegation?.access === void 0) return {
|
|
1025
|
+
...delegation,
|
|
1026
|
+
scopes: []
|
|
1027
|
+
};
|
|
1028
|
+
return delegation;
|
|
1029
|
+
}
|
|
1030
|
+
function rejectUnsafe(value, path) {
|
|
1031
|
+
if (value === null || typeof value !== "object") return;
|
|
1032
|
+
if (Array.isArray(value)) {
|
|
1033
|
+
for (const [index, item] of value.entries()) rejectUnsafe(item, `${path}[${index}]`);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
for (const key of Object.keys(value)) {
|
|
1037
|
+
if (isForbiddenKey(key)) throw new Error(`PermDock: unsafe snapshot key '${key}' at ${path}`);
|
|
1038
|
+
rejectUnsafe(value[key], `${path}.${key}`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function parseSnapshot(json) {
|
|
1042
|
+
const input = typeof json === "string" ? JSON.parse(json) : json;
|
|
1043
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("PermDock: snapshot must be an object");
|
|
1044
|
+
const value = typeof json === "string" || Object.isFrozen(input) ? input : JSON.parse(JSON.stringify(input));
|
|
1045
|
+
rejectUnsafe(value, "$");
|
|
1046
|
+
const version = value["v"];
|
|
1047
|
+
if (version !== 1) throw new Error(`PermDock: unsupported snapshot version '${String(version)}'`);
|
|
1048
|
+
return freezeDeep(value);
|
|
1049
|
+
}
|
|
1050
|
+
//#endregion
|
|
1051
|
+
export { whereFromGrants as a, versionOf as c, delegatedPermissions as d, resourceIdOf as f, evaluateCondition as g, simulateArazzo as h, snapshotGrant as i, canonicalJson as l, isArazzoSimulateInput as m, parseSnapshot as n, decisionToken as o, arazzoFindings as p, signSnapshot as r, payloadDigest as s, buildSnapshot as t, coveredByDelegation as u };
|