mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
- routes/admin-routes.ts: GET/POST /api/admin/users, PATCH/DELETE /api/admin/users/:username, reset-password, logout. Multi-user only (404 otherwise), requireAdmin, last-admin invariants, one-time-password on create / reset (returned once + mustChangePassword), disable/reset/delete revoke cookie sessions, delete kills the user's live sessions first (normal teardown) and can delete their space (guarded). Per-user stats (live/active sessions, case count). - web/admin-audit.ts: append-only ~/.codeman/admin-audit.jsonl (timestamp, acting admin, action, target, IP) for every user-management action. - SSE admin:usersChanged + auth:passwordChangeRequired (sse-events.ts + constants.js). fix(user-store): serialize users.json read-modify-write touchLastLogin fires on every Basic auth (fire-and-forget) and was racing route writes (create/update), clobbering records — a real corruption bug surfaced by the admin tests. All mutators now run under a single write lock, and touchLastLogin is throttled to once/minute per user to bound disk churn. Tests: test/admin-routes.test.ts (8, live server) + user-store lock verified by the existing user-store suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
29 lines
899 B
TypeScript
29 lines
899 B
TypeScript
/**
|
|
* @fileoverview Append-only admin audit log (~/.codeman/admin-audit.jsonl).
|
|
*
|
|
* Every user-management action (create/patch/reset/delete/logout/assign) writes one
|
|
* JSON line: timestamp, acting admin, action, target, request IP. Same idiom as
|
|
* session-lifecycle.jsonl. Best-effort: a write failure never blocks the action.
|
|
*/
|
|
|
|
import fs from 'node:fs/promises';
|
|
import { dataPath } from '../config/instance.js';
|
|
|
|
export interface AdminAuditEntry {
|
|
ts: number;
|
|
admin: string;
|
|
action: string;
|
|
target?: string;
|
|
ip?: string;
|
|
detail?: Record<string, unknown>;
|
|
}
|
|
|
|
export async function appendAdminAudit(entry: Omit<AdminAuditEntry, 'ts'>): Promise<void> {
|
|
try {
|
|
const line = JSON.stringify({ ts: Date.now(), ...entry }) + '\n';
|
|
await fs.appendFile(dataPath('admin-audit.jsonl'), line, { mode: 0o600 });
|
|
} catch {
|
|
/* best-effort audit; never block the action */
|
|
}
|
|
}
|