Skip to content

[Enhancement] Tamper-evident audit log for financial and security-sensitive actions with an admin query API #66

Description

@zeemscript

Summary

Add a tamper-evident audit log for financial and security-sensitive actions. A platform that moves real money on Stellar and grants content entitlements needs an immutable, queryable record of who did what, when, from where — wallet connect/disconnect, payment initialize/submit/cancel, access grants, refunds (#20), payouts (#28), role changes (#20), and auth events. Today these actions leave only free-text logger.info lines in rotating Winston files: not queryable, not structured, not retained as records, and trivially lost on log rotation.

Current state

  • Security/financial actions are recorded only as log strings, e.g. logger.info(\Wallet connected for user ${userId}: ${publicKey}`) (src/controllers/stellar/walletController.js), logger.info(`Payment initialized: ${transaction._id} ...`)andlogger.info(`Payment successful: ${transactionId}, Stellar TX: ${result.hash}`) (src/controllers/stellar/paymentController.js). These go to Winston (src/config/logger.js`) with daily rotation — not to the database, not queryable, no actor/IP/outcome structure.
  • There is no AuditLog model and no audit collection anywhere in src/models/.
  • requestLogger (src/middlewares/security.js) logs every HTTP request line (method/url/status/ms/IP) but it's generic access logging, not a durable audit trail of business actions, and again only to Winston.
  • Sensitive mutations have no durable "before/after" trail: profile updates (userController.updateUser), wallet bind/unbind, and (incoming) role changes leave nothing an admin could later review for abuse or compliance.
  • The observability issue ([Enhancement] Observability: structured pino logging with request IDs, Prometheus /metrics, and slow-query logging #33) covers pino/Prometheus operational telemetry — counters, latencies, request IDs — not a queryable, retained record of security-relevant business events. This fills that gap.

What to build

  1. Model (src/models/AuditLog.js): { action (enum), actor (ref User, nullable for anonymous), actorIp, actorUserAgent, targetType, targetId, status (success|failure), metadata (mixed, redacted), requestId, createdAt }. Index { actor, createdAt }, { action, createdAt }, { targetType, targetId }. Make it append-only: block updates/deletes at the model layer (pre-hooks throwing), and never expose a mutation route.
  2. Audit service + helper (src/services/audit/auditService.js, recordAudit({...})): non-blocking (never fail the main request if the write fails — log and move on), with a redaction allowlist so secrets/PII (passwords, full wallet balances, tokens) are never persisted. Capture IP/UA from the request.
  3. Instrument the sensitive paths (call the helper, don't rip out existing logger calls):
  4. Admin query API (src/routes/admin/auditRoutes.js, mounted /api/admin/audit, role-gated per [Enhancement] Introduce role-based authorization and fix registration privilege escalation #20): GET /api/admin/audit?actor=&action=&targetType=&from=&to=&status=&page=&limit= — paginated, filterable, read-only. No create/update/delete endpoints.
  5. Retention note: document a retention/TTL policy decision (financial events likely retained long-term; consider a separate longer TTL than the 30-min Transaction pending TTL). Do not auto-expire financial audit rows by default.

Acceptance criteria

  • An append-only AuditLog model exists with the indexes above; attempts to update or delete an audit document are rejected at the model layer.
  • Wallet connect/disconnect, payment initialize/submit/cancel, and auth login/register/password-reset write structured audit rows capturing actor, IP, user-agent, target, and success/failure.
  • Audit writes never block or fail the primary request, and never persist secrets/PII (redaction allowlist enforced — a test asserts a password/token is absent from stored metadata).
  • GET /api/admin/audit is read-only, role-gated (401/403 for non-admins), and supports filtering by actor/action/target/date/status with pagination.
  • No endpoint allows mutating or deleting audit records.
  • Jest + supertest tests cover: an audited action producing a correct row, redaction of a sensitive field, admin-only access, and filter/pagination correctness.

Pointers

Difficulty

Medium — conceptually straightforward but requires careful append-only modeling, non-blocking instrumentation across many controllers, disciplined redaction, and a role-gated query API.


🏆 GrantFox OSS — Official Campaign | FWC26. Apply for this issue through the GrantFox campaign page. The maintainer assigns one contributor before work starts; unassigned PRs may not be reviewed. PRs target the dev branch. Quality bar: CI must stay green.

💬 Questions or need help? Reach the maintainers and other contributors on the DeenBridge Telegram: https://t.me/+nst9lXNj1wc4ZDE0

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions