feat: double audit — impersonated_by in audit_log
- db.js: add impersonated_by column to audit_log - queries.js: logAudit accepts impersonatedBy, all CRUD pass it - api/routes/entries.js: pass req.user.originalUserEmail as impersonatedBy - При имперсонации: created_by=импактируемый, impersonated_by=админ
This commit is contained in:
@@ -121,7 +121,7 @@ function createEntriesRouter({ q }) {
|
||||
if (comment && comment.length > 255) return res.status(400).json({ error: 'Комментарий слишком длинный (максимум 255 символов)' });
|
||||
try {
|
||||
const company = await resolveCompany(req);
|
||||
const { entry, wasNormalized } = await q.createEntry(company.id, value, comment || '', req.user.email);
|
||||
const { entry, wasNormalized } = await q.createEntry(company.id, value, comment || '', req.user.email, req.user.originalUserEmail);
|
||||
res.status(201).json({ entry, wasNormalized });
|
||||
} catch (e) {
|
||||
if (e.status) return res.status(e.status).json({ error: e.message });
|
||||
@@ -136,7 +136,7 @@ function createEntriesRouter({ q }) {
|
||||
if (comment && comment.length > 255) return res.status(400).json({ error: 'Комментарий слишком длинный (максимум 255 символов)' });
|
||||
try {
|
||||
const company = await resolveCompany(req);
|
||||
const { entry, wasNormalized } = await q.updateEntry(req.params.id, company.id, value, comment ?? '', req.user.email);
|
||||
const { entry, wasNormalized } = await q.updateEntry(req.params.id, company.id, value, comment ?? '', req.user.email, req.user.originalUserEmail);
|
||||
res.json({ entry, wasNormalized });
|
||||
} catch (e) {
|
||||
if (e.status) return res.status(e.status).json({ error: e.message });
|
||||
@@ -148,7 +148,7 @@ function createEntriesRouter({ q }) {
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const company = await resolveCompany(req);
|
||||
await q.deleteEntry(req.params.id, company.id, req.user.email);
|
||||
await q.deleteEntry(req.params.id, company.id, req.user.email, req.user.originalUserEmail);
|
||||
res.status(204).send();
|
||||
} catch (e) {
|
||||
if (e.status) return res.status(e.status).json({ error: e.message });
|
||||
|
||||
@@ -56,6 +56,7 @@ async function ensureSchema() {
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
entry_id INTEGER,
|
||||
impersonated_by VARCHAR(255) DEFAULT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_company_time ON audit_log(company_id, created_at DESC);
|
||||
|
||||
+10
-10
@@ -30,7 +30,7 @@ async function listEntries(companyId, includeDeleted = false) {
|
||||
return (await pool.query(sql, [companyId])).rows;
|
||||
}
|
||||
|
||||
async function createEntry(companyId, rawValue, comment, userEmail) {
|
||||
async function createEntry(companyId, rawValue, comment, userEmail, impersonatedBy) {
|
||||
const { cidr, wasNormalized } = validate(rawValue);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
@@ -64,7 +64,7 @@ async function createEntry(companyId, rawValue, comment, userEmail) {
|
||||
VALUES ($1, $2, $3, $4) RETURNING *`,
|
||||
[companyId, cidr, comment || null, userEmail]
|
||||
);
|
||||
await logAudit(userEmail, companyId, 'CREATE', null, cidr, res.rows[0].id, client);
|
||||
await logAudit(userEmail, companyId, 'CREATE', null, cidr, res.rows[0].id, impersonatedBy, client);
|
||||
await client.query('COMMIT');
|
||||
return { entry: res.rows[0], wasNormalized };
|
||||
} catch (e) {
|
||||
@@ -75,7 +75,7 @@ async function createEntry(companyId, rawValue, comment, userEmail) {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateEntry(entryId, companyId, rawValue, comment, userEmail) {
|
||||
async function updateEntry(entryId, companyId, rawValue, comment, userEmail, impersonatedBy) {
|
||||
const { cidr, wasNormalized } = validate(rawValue);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
@@ -105,7 +105,7 @@ async function updateEntry(entryId, companyId, rawValue, comment, userEmail) {
|
||||
WHERE id = $4 AND company_id = $5 RETURNING *`,
|
||||
[cidr, comment || old.comment, userEmail, entryId, companyId]
|
||||
);
|
||||
await logAudit(userEmail, companyId, 'UPDATE', old.value_cidr, cidr, entryId, client);
|
||||
await logAudit(userEmail, companyId, 'UPDATE', old.value_cidr, cidr, entryId, impersonatedBy, client);
|
||||
await client.query('COMMIT');
|
||||
return { entry: res.rows[0], wasNormalized };
|
||||
} catch (e) {
|
||||
@@ -116,7 +116,7 @@ async function updateEntry(entryId, companyId, rawValue, comment, userEmail) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteEntry(entryId, companyId, userEmail) {
|
||||
async function deleteEntry(entryId, companyId, userEmail, impersonatedBy) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
@@ -132,7 +132,7 @@ async function deleteEntry(entryId, companyId, userEmail) {
|
||||
'UPDATE whitelist_entries SET deleted_by = $1, deleted_at = NOW() WHERE id = $2 AND company_id = $3',
|
||||
[userEmail, entryId, companyId]
|
||||
);
|
||||
await logAudit(userEmail, companyId, 'DELETE', old.value_cidr, null, entryId, client);
|
||||
await logAudit(userEmail, companyId, 'DELETE', old.value_cidr, null, entryId, impersonatedBy, client);
|
||||
await client.query('COMMIT');
|
||||
} catch (e) {
|
||||
await client.query('ROLLBACK');
|
||||
@@ -214,11 +214,11 @@ async function getExportCIDRs(companyId = null) {
|
||||
|
||||
// ── Audit ──
|
||||
|
||||
async function logAudit(userEmail, companyId, action, oldValue, newValue, entryId, db = pool) {
|
||||
async function logAudit(userEmail, companyId, action, oldValue, newValue, entryId, impersonatedBy, db = pool) {
|
||||
await db.query(
|
||||
`INSERT INTO audit_log (user_email, company_id, action, old_value, new_value, entry_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[userEmail, companyId, action, oldValue, newValue, entryId || null]
|
||||
`INSERT INTO audit_log (user_email, company_id, action, old_value, new_value, entry_id, impersonated_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||
[userEmail, companyId, action, oldValue, newValue, entryId || null, impersonatedBy || null]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user