73 lines
2.3 KiB
JavaScript
73 lines
2.3 KiB
JavaScript
import { pool } from '../pool.js';
|
|
|
|
const REDACT_KEYS = new Set([
|
|
'token','token_hash','password','api_key','secret','authorization'
|
|
]);
|
|
|
|
function isSensitiveKey(k) {
|
|
return REDACT_KEYS.has(String(k).toLowerCase());
|
|
}
|
|
|
|
function redactValueForKey(k, v) {
|
|
if (isSensitiveKey(k)) return '[REDACTED]';
|
|
return redact(v);
|
|
}
|
|
|
|
function redact(obj) {
|
|
if (!obj || typeof obj !== 'object') return obj;
|
|
if (Array.isArray(obj)) return obj.map(redact);
|
|
const out = {};
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
out[k] = redactValueForKey(k, v);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function diff(before, after) {
|
|
if (before === null && after === null) return null;
|
|
if (before === null || before === undefined) return { kind: 'create', after: redact(after) };
|
|
if (after === null || after === undefined) return { kind: 'delete', before: redact(before) };
|
|
const changed = {};
|
|
for (const k of new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
if (JSON.stringify(before[k]) !== JSON.stringify(after[k])) {
|
|
changed[k] = {
|
|
before: redactValueForKey(k, before[k]),
|
|
after: redactValueForKey(k, after[k])
|
|
};
|
|
}
|
|
}
|
|
return Object.keys(changed).length ? { kind: 'update', changes: changed } : null;
|
|
}
|
|
|
|
export async function recordAudit(actor, action, entity_type, entity_id, before, after) {
|
|
const d = diff(before, after);
|
|
await pool.query(
|
|
`INSERT INTO audit_log(actor_kind, actor_id, entity_type, entity_id, action, diff)
|
|
VALUES($1,$2,$3,$4,$5,$6)`,
|
|
[actor?.kind || 'system', actor?.id || null, entity_type, entity_id, action, d]
|
|
);
|
|
}
|
|
|
|
export async function listForEntity(entity_type, entity_id, { limit = 100 } = {}) {
|
|
const { rows } = await pool.query(
|
|
`SELECT * FROM audit_log WHERE entity_type=$1 AND entity_id=$2
|
|
ORDER BY occurred_at DESC LIMIT $3`,
|
|
[entity_type, entity_id, limit]
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
export async function listByActor({ actor_kind, actor_id, limit = 100 } = {}) {
|
|
const where = [], vals = [];
|
|
let i = 1;
|
|
if (actor_kind) { where.push(`actor_kind=$${i++}`); vals.push(actor_kind); }
|
|
if (actor_id) { where.push(`actor_id=$${i++}`); vals.push(actor_id); }
|
|
vals.push(limit);
|
|
const w = where.length ? `WHERE ${where.join(' AND ')}` : '';
|
|
const { rows } = await pool.query(
|
|
`SELECT * FROM audit_log ${w} ORDER BY occurred_at DESC LIMIT $${i}`,
|
|
vals
|
|
);
|
|
return rows;
|
|
}
|