hosted-workspace-resolve.mjs
91 lines 4.2 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 /**
2 * Hosted Hub: resolve which canister partition (X-User-Id) a JWT actor uses.
3 * Shared by bridge (hosted-context, index/search/sync); workspace semantics in docs/MULTI-VAULT-AND-SCOPED-ACCESS.md.
4 */
5
6 /** Roles that participate in workspace delegation (read owner's canister partition). Must match bridge VALID_ROLES. */
7 export const HOSTED_VALID_ROLES = new Set(['admin', 'editor', 'viewer', 'evaluator']);
8
9 /**
10 * @param {object} p
11 * @param {string} p.actorSub - JWT sub (e.g. google:123)
12 * @param {string|null|undefined} p.workspaceOwnerId - bridge hub_workspace.owner_user_id
13 * @param {Record<string, string>} p.storedRoles - hub_roles map
14 * @param {Set<string>} p.adminUserIdsSet - HUB_ADMIN_USER_IDS
15 * @returns {{ effective: string, delegate: boolean }}
16 */
17 export function resolveEffectiveCanisterUser({ actorSub, workspaceOwnerId, storedRoles, adminUserIdsSet }) {
18 if (!actorSub) return { effective: '', delegate: false };
19 const owner =
20 workspaceOwnerId && String(workspaceOwnerId).trim() ? String(workspaceOwnerId).trim() : null;
21 if (!owner) return { effective: actorSub, delegate: false };
22 if (actorSub === owner) return { effective: actorSub, delegate: false };
23 const stored = storedRoles && storedRoles[actorSub];
24 const inTeamRoles = Boolean(stored && HOSTED_VALID_ROLES.has(stored));
25 const envAdmin = Boolean(adminUserIdsSet && adminUserIdsSet.has(actorSub));
26 if (inTeamRoles || envAdmin) return { effective: owner, delegate: true };
27 return { effective: actorSub, delegate: false };
28 }
29
30 /**
31 * Same semantics as hub/hub_vault_access.mjs getAllowedVaultIds.
32 * @param {Record<string, string[]>} accessMap
33 * @param {string} userId
34 * @returns {string[]}
35 */
36 export function getAllowedVaultIdsFromAccessMap(accessMap, userId) {
37 const access = accessMap && typeof accessMap === 'object' ? accessMap : {};
38 const allowed = access[userId];
39 return allowed && Array.isArray(allowed) && allowed.length > 0 ? [...allowed] : ['default'];
40 }
41
42 /**
43 * Same semantics as hub/hub_scope.mjs getScopeForUserVault (object in memory, not file).
44 * @param {Record<string, Record<string, { projects?: string[], folders?: string[] }>>} scopeMap
45 * @param {string} userId
46 * @param {string} vaultId
47 * @returns {{ projects: string[], folders: string[] } | null}
48 */
49 export function getScopeForUserVaultFromScopeMap(scopeMap, userId, vaultId) {
50 const vid = vaultId && String(vaultId).trim() ? String(vaultId).trim() : 'default';
51 const scope = scopeMap && typeof scopeMap === 'object' ? scopeMap : {};
52 const userScope = scope[userId];
53 if (!userScope || typeof userScope[vid] !== 'object') return null;
54 const r = userScope[vid];
55 const projects = Array.isArray(r.projects) ? r.projects : [];
56 const folders = Array.isArray(r.folders) ? r.folders : [];
57 if (projects.length === 0 && folders.length === 0) return null;
58 return { projects, folders };
59 }
60
61 /**
62 * Intersect canister vault ids with allowlist; preserve canister order.
63 * @param {string[]} canisterIds
64 * @param {string[]} allowedRaw
65 * @returns {string[]}
66 */
67 export function intersectVaultIds(canisterIds, allowedRaw) {
68 const allow = new Set(allowedRaw && allowedRaw.length ? allowedRaw : ['default']);
69 const list = Array.isArray(canisterIds) && canisterIds.length ? canisterIds : ['default'];
70 return list.filter((id) => allow.has(id));
71 }
72
73 /**
74 * Vault-access map restricts **delegating** team members. Users acting on their **own** canister
75 * partition (owner or solo, not delegating) get every vault returned by the canister unless they
76 * have an explicit access row (then that row applies).
77 *
78 * @param {{ delegate: boolean, actorUid: string, accessMap: Record<string, string[]>, canisterIds: string[] }} p
79 * @returns {string[]}
80 */
81 export function resolveAllowedVaultIdsForHostedContext({ delegate, actorUid, accessMap, canisterIds }) {
82 const access = accessMap && typeof accessMap === 'object' ? accessMap : {};
83 const explicit = access[actorUid];
84 const explicitList =
85 explicit && Array.isArray(explicit) && explicit.length > 0
86 ? explicit.map((x) => String(x))
87 : null;
88 const ids = Array.isArray(canisterIds) && canisterIds.length ? canisterIds.map(String) : ['default'];
89 const allowedRaw = delegate ? explicitList ?? ['default'] : explicitList ?? ids;
90 return intersectVaultIds(ids, allowedRaw);
91 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago