Spaces:
Sleeping
Sleeping
| ; | |
| /** Phase 6 — Owner-only audit log. | |
| * Persisted to local-db/audit/audit.enc (AES-256-GCM + XChaCha20-Poly1305 | |
| * via SecureStore). Legacy audit.json is migrated on first load. */ | |
| var __importDefault = (this && this.__importDefault) || function (mod) { | |
| return (mod && mod.__esModule) ? mod : { "default": mod }; | |
| }; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.appendAudit = appendAudit; | |
| exports.listAudit = listAudit; | |
| const fs_1 = __importDefault(require("fs")); | |
| const path_1 = __importDefault(require("path")); | |
| const secureStore_1 = require("./secureStore"); | |
| const LOCAL_DIR = path_1.default.join(__dirname, "../../local-db"); | |
| const DATA_DIR = path_1.default.join(LOCAL_DIR, "audit"); | |
| const FILE = path_1.default.join(DATA_DIR, "audit.enc"); | |
| const LEGACY = path_1.default.join(LOCAL_DIR, "audit.json"); | |
| const RING_LIMIT = 5000; | |
| const FLUSH_MS = 5000; | |
| let buffer = []; | |
| let loaded = false; | |
| let dirty = false; | |
| let secure = null; | |
| function getSecure() { | |
| if (!secure) { | |
| fs_1.default.mkdirSync(LOCAL_DIR, { recursive: true }); | |
| secure = new secureStore_1.SecureStore(LOCAL_DIR); | |
| } | |
| return secure; | |
| } | |
| function persistNow() { | |
| try { | |
| fs_1.default.mkdirSync(DATA_DIR, { recursive: true }); | |
| getSecure().encryptToFile(JSON.stringify(buffer.slice(-RING_LIMIT)), FILE); | |
| } | |
| catch (e) { | |
| console.warn("[audit] persist failed:", e?.message); | |
| } | |
| } | |
| function ensureLoaded() { | |
| if (loaded) | |
| return; | |
| loaded = true; | |
| try { | |
| fs_1.default.mkdirSync(DATA_DIR, { recursive: true }); | |
| // One-time migration from legacy audit.json | |
| if (fs_1.default.existsSync(LEGACY) && !fs_1.default.existsSync(FILE)) { | |
| try { | |
| const arr = JSON.parse(fs_1.default.readFileSync(LEGACY, "utf-8")); | |
| if (Array.isArray(arr)) | |
| buffer = arr.slice(-RING_LIMIT); | |
| persistNow(); | |
| fs_1.default.unlinkSync(LEGACY); | |
| console.log(`[audit] migrated audit.json → audit/audit.enc (${buffer.length} entries)`); | |
| return; | |
| } | |
| catch (e) { | |
| console.warn("[audit] migration failed:", e?.message); | |
| } | |
| } | |
| if (fs_1.default.existsSync(FILE)) { | |
| const json = getSecure().decryptFromFile(FILE); | |
| const arr = JSON.parse(json); | |
| if (Array.isArray(arr)) | |
| buffer = arr.slice(-RING_LIMIT); | |
| } | |
| } | |
| catch (e) { | |
| console.warn("[audit] load failed:", e?.message); | |
| } | |
| } | |
| // Eagerly migrate / load at module startup so the audit/ dir exists before | |
| // the first appendAudit or listAudit call. | |
| ensureLoaded(); | |
| setInterval(() => { | |
| if (!dirty) | |
| return; | |
| dirty = false; | |
| persistNow(); | |
| }, FLUSH_MS).unref?.(); | |
| function appendAudit(e) { | |
| ensureLoaded(); | |
| const entry = { | |
| id: `aud_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, | |
| at: e.at ?? Date.now(), | |
| by: e.by, | |
| action: e.action, | |
| target: e.target, | |
| meta: e.meta, | |
| }; | |
| buffer.push(entry); | |
| if (buffer.length > RING_LIMIT) | |
| buffer = buffer.slice(-RING_LIMIT); | |
| dirty = true; | |
| return entry; | |
| } | |
| function listAudit(limit = 200, action) { | |
| ensureLoaded(); | |
| const filtered = action ? buffer.filter(e => e.action.startsWith(action)) : buffer; | |
| return filtered.slice(-limit).reverse(); | |
| } | |