Skip to content

feat(audit-log): add system-wide audit trail - #123

Merged
mpge merged 1 commit into
mainfrom
feat/system-audit-log
Aug 2, 2026
Merged

feat(audit-log): add system-wide audit trail#123
mpge merged 1 commit into
mainfrom
feat/system-audit-log

Conversation

@mpge

@mpge mpge commented Aug 2, 2026

Copy link
Copy Markdown
Member

Problem

Audit logging was limited to per-ticket ticket_activities; there was no system-wide audit trail. Admin/config/security/user actions that happen outside a ticket were not recorded.

What this does

Ports the Laravel reference (AuditLog model, Auditable trait, Admin\AuditLogController) to the AdonisJS backend.

Storage

  • AuditLog Lucid model + escalated_audit_logs migration (0063): actor (user_id), action, polymorphic target (auditable_type/auditable_id), old_values/new_values JSON, ip_address, user_agent, created_at. Append-only — no updated_at. Indexed on the morph pair, user_id, action, and created_at, matching the Laravel schema. auditable_id is a string so int/uuid/string host keys round-trip.

The reusable seam

  • AuditService centralizes every write (mirrors how the Auditable trait centralizes AuditLog::create), pulling the actor from auth.user and IP + user-agent from the request. It never throws — a failed audit insert cannot break the mutation that triggered it (same contract as WebhookDispatcher).
  • Framework-free logic lives in support/audit_events.ts (row build, attribute diff, list filter/sort) so it is unit-testable without Lucid/HTTP, mirroring support/webhook_events.ts.

Audited actions (wired at the mutation sites)

Surface Actions
Settings settings.updated (secrets redacted), settings.public_tickets_updated
Users / roles user.role_updated (old/new admin+agent flags)
Webhooks webhook.created / updated / deleted
API tokens (security) api_token.created / updated / revoked
Automations automation.created / updated / deleted
Two-factor (security) two_factor.enabled / disabled / recovery_codes_regenerated

Admin surface

  • AdminAuditLogsController + GET /support/admin/audit-logs (admin-gated): newest-first, paginated list filtered by user_id, action, auditable_type, and date_from/date_to, with actor-name resolution and filter-dropdown data. Matches the Laravel controller and returns the shared Laravel-paginator shape (extracted into a reusable support/pagination.ts).

Tests

TDD-first. New tests/unit/audit_log.spec.ts (16 assertions) proves recorded actions produce a row with the right actor/action/target, the attribute diff excludes timestamp noise, and the admin list filters + sorts correctly. Full suite green: 595 node-test + 21 Japa, 0 failures, no regressions. eslint + prettier@3.9.6 (CI-locked) clean on all changed files.

Audit logging was limited to per-ticket ticket_activities; there was no
system-wide trail for admin/config/security/user actions that happen
outside a ticket. Port the Laravel reference (AuditLog model, Auditable
trait, Admin\AuditLogController).

- AuditLog Lucid model + escalated_audit_logs migration: actor (user_id),
  action, polymorphic target (auditable_type/id), old/new values JSON,
  ip_address, user_agent, created_at (append-only, no updated_at)
- AuditService: reusable seam centralizing every AuditLog write, pulling
  the actor/ip/user-agent from the request; never throws so an audit
  failure can't break the mutation that triggered it
- Wire records at admin CRUD + security sites: settings + public-tickets,
  user role grant/revoke, webhooks (create/update/delete), API tokens
  (create/update/revoke), automations (create/update/delete), and
  two-factor enable/disable/recovery-code regeneration
- AdminAuditLogsController + route: filtered (user/action/type/date range),
  paginated, newest-first list with actor-name resolution
- Framework-free helpers in support/audit_events.ts (row build, attribute
  diff, list filter/sort) + support/pagination.ts, with 16 unit tests
@mpge
mpge merged commit bfc9bdc into main Aug 2, 2026
4 checks passed
@mpge
mpge deleted the feat/system-audit-log branch August 2, 2026 17:05
mpge added a commit that referenced this pull request Aug 2, 2026
Audit logging was limited to per-ticket ticket_activities; there was no
system-wide trail for admin/config/security/user actions that happen
outside a ticket. Port the Laravel reference (AuditLog model, Auditable
trait, Admin\AuditLogController).

- AuditLog Lucid model + escalated_audit_logs migration: actor (user_id),
  action, polymorphic target (auditable_type/id), old/new values JSON,
  ip_address, user_agent, created_at (append-only, no updated_at)
- AuditService: reusable seam centralizing every AuditLog write, pulling
  the actor/ip/user-agent from the request; never throws so an audit
  failure can't break the mutation that triggered it
- Wire records at admin CRUD + security sites: settings + public-tickets,
  user role grant/revoke, webhooks (create/update/delete), API tokens
  (create/update/revoke), automations (create/update/delete), and
  two-factor enable/disable/recovery-code regeneration
- AdminAuditLogsController + route: filtered (user/action/type/date range),
  paginated, newest-first list with actor-name resolution
- Framework-free helpers in support/audit_events.ts (row build, attribute
  diff, list filter/sort) + support/pagination.ts, with 16 unit tests

Co-authored-by: Matt Gros <mpge@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant