66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
/**
|
|
* 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<MiAccessResult> {
|
|
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 };
|
|
}
|