/** * MI Access Control — validates caller access to a space before * serving MI data (knowledge index, memory, query-content). * * Reuses existing auth primitives from spaces.ts / community-store. */ import { resolveCallerRole } from "./spaces"; import type { SpaceRoleString } from "./spaces"; import { loadCommunity, getDocumentData } from "./community-store"; import type { EncryptIDClaims } from "./auth"; export interface MiAccessResult { allowed: boolean; role: SpaceRoleString; reason?: string; } /** * Check whether the caller (identified by claims) may access MI for * the given space. Returns the effective role if allowed. */ export async function validateMiSpaceAccess( space: string, claims: EncryptIDClaims | null, minRole: SpaceRoleString = "viewer", ): Promise { if (!space) { return { allowed: false, role: "viewer", reason: "Space parameter required" }; } await loadCommunity(space); const data = getDocumentData(space); if (!data) { return { allowed: false, role: "viewer", reason: "Space not found" }; } const visibility = data.meta?.visibility || "public"; // Private and permissioned spaces require authentication if ((visibility === "private" || visibility === "permissioned") && !claims) { return { allowed: false, role: "viewer", reason: "Authentication required for this space" }; } // Resolve the caller's role const resolved = await resolveCallerRole(space, claims); const role: SpaceRoleString = resolved?.role || "viewer"; // For private spaces, non-members (viewer default) are denied if (visibility === "private" && role === "viewer" && !resolved?.isOwner) { // Check if the caller is actually a member if (!claims) { return { allowed: false, role: "viewer", reason: "Authentication required for this space" }; } // resolveCallerRole returns viewer for non-members — check membership explicitly const callerDid = (claims.did as string) || `did:key:${(claims.sub as string).slice(0, 32)}`; const isMember = data.members?.[claims.sub] || data.members?.[callerDid]; const isOwner = data.meta?.ownerDID === claims.sub || data.meta?.ownerDID === callerDid; if (!isMember && !isOwner) { return { allowed: false, role: "viewer", reason: "You don't have access to this space" }; } } return { allowed: true, role }; }