Exocore-Backend / dist /services /auditStore.js
ChoruYt's picture
Upload 48 files
8b74df2 verified
Raw
History Blame Contribute Delete
3.46 kB
"use strict";
/** 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();
}