import type { Context } from "koa"; import type { BackendDbInterface } from "../db/types/DbInterface.js"; import type { ClientUser } from "../db/types/User.js"; import type { ExportFilter } from "../db/types/ExportImport.js"; /** * Compute the export scope that must be *forced* on a caller, independent of * anything they requested. Returns `null` when the caller is unrestricted (a * global admin whose credential imposes no narrowing) — their own filter, if * any, is then honored verbatim as a voluntary narrowing. * * Otherwise returns `{ abodes, users, apikeys }`: the abodes the caller resides * in, the users needed to keep that data referentially whole (the caller plus * every co-resident of those abodes), and — scoped tighter than `users` — * apikeys limited to the caller alone, so a non-admin never exports another * user's apikey metadata even though that user's record is included. This is * the maximum a non-admin may export; the route intersects it with any * caller-supplied filter (never a union). */ export async function computeForcedExportFilter( db: BackendDbInterface, ctx: { user: ClientUser; session: NonNullable }, ): Promise { const { user, session } = ctx; if (user.flags.admin) { if (session.source !== "apikey") return null; const p = session.key.permissions; const unrestricted = !!p.admin && !!p.all && !p.restrict_users?.length && !p.restrict_abodes?.length; if (unrestricted) return null; } const residencies = await db.listResidentsByUserId(user.uid); const abodeSet = new Set(residencies.map((r) => r.aid)); const userSet = new Set([user.uid]); for (const aid of abodeSet) { for (const u of await db.listUsersByAbodeId(aid)) userSet.add(u.uid); } let abodes = [...abodeSet]; let users = [...userSet]; // apikeys are self-only for non-admins, regardless of co-residency. let apikeys = [user.uid]; // An apikey can only narrow what its owning user could otherwise export. if (session.source === "apikey") { const p = session.key.permissions; if (p.restrict_abodes?.length) { const allow = new Set(p.restrict_abodes); abodes = abodes.filter((a) => allow.has(a)); } if (p.restrict_users?.length) { const allow = new Set(p.restrict_users); users = users.filter((u) => allow.has(u)); apikeys = apikeys.filter((u) => allow.has(u)); } } return { abodes, users, apikeys }; }