← back to AbramsOS

routes/audit.js

73 lines

// /audit — read-only viewer for audit_log + auth_event.
// Provides transparency over every state-changing operation.

const express = require('express');
const db = require('../lib/db');

const router = express.Router();
const DEV_USER_ID = 'user_steve';

// Combined timeline query: union of both audit tables, newest first.
async function getEvents({ limit = 200, eventType = null, since = null } = {}) {
  const params = [limit];
  let whereAudit = '';
  let whereAuth = '';
  if (eventType) {
    params.push(eventType);
    whereAudit += ` AND event_type = $${params.length}`;
    whereAuth += ` AND event_type = $${params.length}`;
  }
  if (since) {
    params.push(since);
    whereAudit += ` AND occurred_at >= $${params.length}`;
    whereAuth += ` AND occurred_at >= $${params.length}`;
  }
  const sql = `
    SELECT 'audit_log'::text AS source, occurred_at, actor_type, actor_id,
           object_type, object_id, event_type, metadata_jsonb
      FROM audit_log
     WHERE 1=1 ${whereAudit}
    UNION ALL
    SELECT 'auth_event'::text AS source, occurred_at, 'user'::text AS actor_type, user_id::text AS actor_id,
           'auth_session'::text AS object_type, session_id AS object_id, event_type, metadata_jsonb
      FROM auth_event
     WHERE 1=1 ${whereAuth}
    ORDER BY occurred_at DESC
    LIMIT $1
  `;
  const r = await db.query(sql, params);
  return r.rows;
}

router.get('/audit', async (req, res) => {
  const eventType = req.query.event_type || null;
  const sinceDays = parseInt(req.query.since_days || '30', 10);
  const since = new Date(Date.now() - sinceDays * 86400e3);
  const events = await getEvents({ limit: 200, eventType, since });

  // Aggregate counts by event_type for the filter chips
  const counts = await db.query(`
    SELECT event_type, count(*)::int AS n FROM (
      SELECT event_type FROM audit_log
      UNION ALL SELECT event_type FROM auth_event
    ) e GROUP BY event_type ORDER BY n DESC LIMIT 20
  `);

  res.render('audit', {
    events,
    counts: counts.rows,
    activeFilter: eventType,
    sinceDays,
  });
});

router.get('/api/audit/events', async (req, res) => {
  const limit = Math.min(500, parseInt(req.query.limit || '200', 10));
  const eventType = req.query.event_type || null;
  const sinceDays = parseInt(req.query.since_days || '30', 10);
  const since = new Date(Date.now() - sinceDays * 86400e3);
  res.json(await getEvents({ limit, eventType, since }));
});

module.exports = router;