Skip to main content

Architecture: Admin Audit Logs

Decision​

AlternativeProsConsDecision
Read-only view over existing collections (chosen)No new collections, no data duplication, simple queriesCoupled to conversation schemaSelected
Separate audit log collection with write-ahead loggingImmutable audit trail, decoupledStorage overhead, write amplificationRejected (over-engineered)
External audit system (ELK/Splunk)Enterprise-grade, existing toolingInfrastructure dependency, complex setupDeferred

Solution Architecture​

Triple-Gated Access Control​

Request ──▶ AUDIT_LOGS_ENABLED env var?
│
├── false ──▶ 403 "Audit logs are not enabled"
└── true ──▶ requireAdmin(session)?
│
├── Not admin ──▶ 403
└── Admin ──▶ Process request
│
└── UI: auditLogsEnabled config?
├── false ──▶ Tab hidden
└── true ──▶ Tab visible

The feature is gated at three levels:

  1. Environment variable: AUDIT_LOGS_ENABLED=true must be set (disabled by default)
  2. Server-side auth: requireAdmin middleware (full admin only, not read-only viewers)
  3. UI visibility: auditLogsEnabled config hides the tab when disabled

API Endpoints​

EndpointMethodDescription
/api/admin/audit-logsGETList conversations with filters and pagination
/api/admin/audit-logs/[id]/messagesGETPaginated messages for a specific conversation
/api/admin/audit-logs/exportGETDownload filtered conversations as CSV
/api/admin/audit-logs/ownersGETSearch distinct owner emails (typeahead)

MongoDB Aggregation Pipeline​

The list endpoint uses a MongoDB aggregation pipeline with $lookup to enrich conversation documents:

conversations collection
│
├── $match: filters (owner_email, search, date_range, status)
├── $lookup: messages collection ──▶ last_message_at, message_count
├── $sort: created_at descending
├── $skip / $limit: pagination
└── $project: AuditConversation shape

CSV Export​

The export endpoint generates CSV with:

  • Proper field escaping (double-quote wrapping, escaped internal quotes)
  • Content-Type: text/csv; charset=utf-8
  • Content-Disposition: attachment; filename="audit-logs-YYYY-MM-DD.csv"
  • Cache-Control: no-store to prevent caching of sensitive data
  • Maximum 10,000 rows to prevent memory exhaustion
  • Respects active filters (same as list endpoint)

Owner Search (Typeahead)​

GET /api/admin/audit-logs/owners?q=john
│
└── db.conversations.aggregate([
{ $group: { _id: "$owner_id" } },
{ $match: { _id: { $regex: /john/i } } },
{ $limit: 20 }
])

Components Changed​

FileDescription
ui/src/lib/config.tsAdded auditLogsEnabled to Config interface and getServerConfig()
ui/src/types/mongodb.tsAdded AuditConversation and AuditLogFilters types
ui/src/app/api/admin/audit-logs/route.tsList conversations with aggregation pipeline
ui/src/app/api/admin/audit-logs/[id]/messages/route.tsPaginated messages for conversation detail
ui/src/app/api/admin/audit-logs/export/route.tsCSV export with escaping and Cache-Control
ui/src/app/api/admin/audit-logs/owners/route.tsDistinct owner email search with typeahead
ui/src/app/(app)/admin/page.tsxConditional Audit Logs tab rendering
ui/src/components/admin/AuditLogsTab.tsxFilter/search/table component with export and owner search
ui/src/components/admin/ConversationDetailDialog.tsxMessage viewer dialog with scrolling