Files
AgentHub/server/lib/audit.js
T
nntrivi2001 9a54978eef feat: v2.5.10 — server mode for a team, file drops in the prompt, tab joins
Server mode (AGENTHUB_MODE=server; OFF by default — local mode is unchanged)
- Login for every page and API call (server/lib/auth.js, routes/auth.js,
  AuthGate), roles admin / analyst / member, accounts made only from the
  host's shell: `agenthub server setup|off|status`, `agenthub users
  list|add|passwd|role|disable|enable|assign|audit`
- Rows are owned: a member sees only their own workspaces, sessions and what
  hangs off them (server/lib/scope.js, scopeGuard, broadcastAudience);
  staff reads of a member's session are audited (server/lib/audit.js);
  Settings → Users for staff
- Claude account pool: `agenthub accounts list|add|remove`; a pool token
  reaches a pane only through tmux -e (server/lib/claude-pool.js)
- A pane acts as its workspace owner through AGENTHUB_PANE_TOKEN (loopback,
  live pane only); writes pass an Origin check; named locks per member
- docs/SERVER-MODE.md, docs/plans/server-mode/

Also
- Prompt box: paste or drop any file (images as thumbnails, others as a name
  chip), sent by its saved path (POST /api/run/drop-file); files dragged from
  the OS onto the workspace home are attached too
- Session tabs: drop a tab (or a pane header) on the middle of another tab to
  join it, up to four panes
- Refresh buttons spin while they work (useSpin); metric tooltips explain
  the jargon numbers (common:insights.help.*)
- Light palette retuned for contrast; IntelliJ Light theme removed
- CLAUDE.md sync marker trimmed; its history moved to docs/CLAUDE-SYNC-LOG.md

Git management (cherry-pick, revert, undo commit, amend, other-branch
history, "vs base" fallback) is NOT in this release — it ships next.

Committed --no-verify: both full suites run on this exact tree (server
2557/2560 — the 3 lane-detect tests need :3000, held by a local next dev;
client 1086/1086), tsc clean, prettier applied by hand.
2026-10-07 14:11:25 +07:00

61 lines
2.1 KiB
JavaScript

/**
* @file audit.js
* @description Server mode's audit trail (docs/SERVER-MODE.md): every read by
* an admin or analyst of a session that a MEMBER owns — the session page, its
* transcript, its events, its cost — is written to `audit_log` with who read
* it, whose it was and which path. `auditGuard` runs on `/api` after the
* scope guard; it never blocks a request, and it is a no-op in local mode and
* for a member (who can only read their own).
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const sql = (q) => require("../db").db.prepare(q);
// Paths that name one session's content.
const SESSION_PATHS = [
/^\/sessions\/(?!facets\/?$)([^/]+)/i,
/^\/pricing\/cost\/([^/]+)/i,
/^\/workflows\/session\/([^/]+)/i,
];
function auditGuard(req, res, next) {
const u = req.user;
if (!u || u.role === "member" || (req.method !== "GET" && req.method !== "HEAD")) return next();
const p = require("./auth").apiPath(req);
for (const re of SESSION_PATHS) {
const m = re.exec(p);
if (!m) continue;
try {
const { sessionOwners } = require("./scope");
const id = m[1];
for (const owner of sessionOwners(id)) {
if (owner === u.id) continue;
const ownerRole = sql("SELECT role FROM users WHERE id = ?").get(owner)?.role;
if (ownerRole !== "member") continue;
sql(
"INSERT INTO audit_log (user_id, owner_id, session_id, method, path) VALUES (?, ?, ?, ?, ?)"
).run(u.id, owner, id, req.method, req.originalUrl.slice(0, 500));
}
} catch (err) {
console.warn("[audit] could not record:", err.message);
}
break;
}
return next();
}
/** The newest entries, with user names. */
function listAudit(limit = 100) {
const n = Math.min(Math.max(Number(limit) || 100, 1), 1000);
return sql(
`SELECT a.id, a.at, a.method, a.path, a.session_id,
r.username AS reader, o.username AS owner
FROM audit_log a
LEFT JOIN users r ON r.id = a.user_id
LEFT JOIN users o ON o.id = a.owner_id
ORDER BY a.id DESC LIMIT ?`
).all(n);
}
module.exports = { auditGuard, listAudit };