/** * @file Which HOME folder a user's File Explorer opens (`/api/homes/*`): * - S3: their own Linux account's home (`/srv/agenthub/users/`); * - server mode without S3: their own folder under AGENTHUB_USERS_ROOT; * - local mode, or an admin with no Linux account of their own: the host * account's home. * `me` is always the caller's. An admin may also open another user's home — * read-only (the routes refuse every write, `EREADONLY`) and written to the * audit log — the "view other users' homes" option. A home is never a * workspace (the S3 rule stands); it is browsed with `file-explorer.js`'s * `inHome`, which keeps every dot entry at its top out of reach. * @author Nguyễn Ngọc Trí Vĩ */ const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const auth = require("./auth"); const linux = require("./linux-accounts"); const runAs = require("./run-as"); const { ownFolder } = require("./scope"); const isDir = (p) => { try { return fs.statSync(p).isDirectory(); } catch { return false; } }; /** The home of AgentHub user `user` (a users row or req.user), or null. */ function homeOf(user) { if (!auth.isServerMode()) return os.homedir(); if (!user) return null; if (linux.s3Enabled()) { let acct = null; try { acct = runAs.accountForUser(user.username); } catch { acct = null; // disabled or not provisioned: no home to open } if (acct) return acct.home; return user.role === "admin" ? os.homedir() : null; } const own = ownFolder({ user }); if (own) return isDir(own) ? own : null; return user.role === "admin" ? os.homedir() : null; } /** * The homes `req` may open: `{ id: "me" | , label, readOnly }`. * Others' only for an admin, and only those that resolve to a folder. */ function listHomes(req) { const out = []; const me = req.user || null; if (homeOf(me)) out.push({ id: "me", label: me?.displayName || me?.username || "", readOnly: false }); if (me?.role === "admin" && auth.isServerMode()) { for (const u of auth.listUsers()) { if (u.id === me.id || u.disabled) continue; const home = homeOf(u); if (home && home !== homeOf(me)) { out.push({ id: u.username, label: u.displayName || u.username, readOnly: true }); } } } return out; } /** * `{ dir, readOnly, ownerId }` for home `who` of `req`, or a thrown ENOHOME * (404) — never another user's for a non-admin. */ function resolveHome(req, who) { const none = () => Object.assign(new Error("no such home"), { code: "ENOHOME" }); const me = req.user || null; if (who === "me") { const dir = homeOf(me); if (!dir) throw none(); return { dir: path.resolve(dir), readOnly: false, ownerId: me?.id ?? null }; } if (me?.role !== "admin" || !auth.isServerMode()) throw none(); const u = auth.listUsers().find((x) => x.username === String(who) && !x.disabled); const dir = u && homeOf(u); if (!dir) throw none(); return { dir: path.resolve(dir), readOnly: true, ownerId: u.id }; } module.exports = { homeOf, listHomes, resolveHome };