50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { Router } from 'express';
|
|
import { pool } from '../db.js';
|
|
import { requireAuth } from '../middleware/auth.js';
|
|
|
|
export const auditRouter = Router();
|
|
auditRouter.use(requireAuth);
|
|
|
|
auditRouter.get('/', async (req, res) => {
|
|
const user = req.user!;
|
|
|
|
if (user.role === 'super_admin') {
|
|
// Super admin sees everything.
|
|
const result = await pool.query(
|
|
`SELECT * FROM audit_log ORDER BY created_at DESC LIMIT 200`,
|
|
);
|
|
res.json(result.rows);
|
|
return;
|
|
}
|
|
|
|
// Domain admin: filter to entries that touch one of their domains.
|
|
// We fetch a slightly larger window because filtering happens after the LIMIT
|
|
// would skip relevant rows. 1000 should be plenty for a single domain.
|
|
const allowed = (user.allowed_domains ?? []).map((d) => d.toLowerCase());
|
|
if (allowed.length === 0) {
|
|
res.json([]);
|
|
return;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`SELECT * FROM audit_log ORDER BY created_at DESC LIMIT 1000`,
|
|
);
|
|
|
|
const visible = result.rows
|
|
.filter((row) => {
|
|
const t = String(row.target_type ?? '').toLowerCase();
|
|
const id = String(row.target_id ?? '').toLowerCase();
|
|
if (t === 'domain') return allowed.includes(id);
|
|
if (t === 'mailbox') {
|
|
const at = id.lastIndexOf('@');
|
|
if (at < 0) return false;
|
|
return allowed.includes(id.slice(at + 1));
|
|
}
|
|
// 'node' and unknown types: globally scoped, hidden from domain admins.
|
|
return false;
|
|
})
|
|
.slice(0, 200);
|
|
|
|
res.json(visible);
|
|
});
|