Skip to content

Audit: admin-gated web endpoint for the audit log - #32

Merged
CaYatur merged 2 commits into
mainfrom
feat/web-audit-endpoint
Jul 24, 2026
Merged

Audit: admin-gated web endpoint for the audit log#32
CaYatur merged 2 commits into
mainfrom
feat/web-audit-endpoint

Conversation

@CaYatur

@CaYatur CaYatur commented Jul 24, 2026

Copy link
Copy Markdown
Owner

What & why

Item #7 (part of #1): expose the audit log over the web panel. Because entries carry player IPs — personal data — the endpoint is owner-only, a stricter gate than any per-server scope.

How

  • GET /api/audit (admin-panel listener, owner-only): mirrors the desktop audit:query. All of from, to, sources, actions, serverId, actor, ip, text, ok, limit, offset ride the query string; returns the same AuditPage (entries newest-first, total, bySource). Non-owner → 403, no token → 401.
  • Panel: an owner-only "Audit" tab (hidden client-side when role !== 'owner', but the server gate is the real boundary), with a text search + source + outcome filter and a compact table. Panel strings stay English, matching the rest of the non-i18n admin panel.

Privacy

Owner-only by design; the client-side tab hiding is cosmetic, the user.role === 'owner' server check is the enforcement. IPs never reach a lesser role.

Verification

  • typecheck (node + web) ✓ · build ✓
  • MSMS_SMOKE_WEB: owner GET 200 newest-first + sources/text/ok filters, non-owner token 403, no token 401 — all green by exit code.

Verification gap (disclosed)

The panel loadAudit JS consumer is inspection-only — the admin panel is served as a static HTML string with no headless DOM harness, so panel JS isn't exercised by a smoke (same as the rest of the panel). The endpoint it calls is fully covered.

Closes #7.

Expose the global audit log over the admin panel listener as
GET /api/audit — owner-only, because entries carry player IPs (personal
data). The route mirrors the desktop audit:query: from/to, sources,
actions, serverId, actor, ip, text, ok, limit, offset all ride the query
string, and it returns the same AuditPage (entries newest-first + total
+ bySource counts).

Panel gets an owner-only "Audit" tab (hidden client-side for non-owners,
enforced server-side regardless) with a searchable, source/outcome-
filtered table. Panel strings stay English, consistent with the rest of
the (non-i18n) admin panel.

WEB smoke: owner GET 200 newest-first + source/text/ok filters, a
non-owner token 403, no token 401. typecheck + build + WEB smoke green.
The panel JS consumer is inspection-only (no headless DOM harness for
the served panel); the endpoint it calls is fully smoke-covered.

Closes #7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CaYatur CaYatur added this to the Stage 15 — Audit trail milestone Jul 24, 2026
Copilot AI review requested due to automatic review settings July 24, 2026 10:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Surfaced by the PR #32 self-review. The old parse was
`okRaw == null ? undefined : okRaw === 'true'`, so a present-but-empty or
garbage `?ok=` fell through to `false` and silently filtered to failures
only. Now an empty/unrecognised value is ignored (undefined); only the
literal true/false apply. WEB smoke (which uses ok=false explicitly)
still green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CaYatur

CaYatur commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Code review

Overview

Exposes the global audit log over the admin-panel listener as owner-only GET /api/audit, mirroring the desktop audit:query, plus an owner-only "Audit" tab in the panel. Correct instinct to make this owner-only rather than a per-server scope — the entries carry player IPs, and per-server scopes don't cleanly bound a cross-server, IP-bearing log.

Correctness / security

  • Enforcement is server-side. The tab is hidden client-side for non-owners, but the real gate is user.role === 'owner' on the route; the WEB smoke proves a non-owner token gets 403 and no token 401. Client hiding is correctly treated as cosmetic.
  • Not on the public listener. The route lives in the authenticated admin handler, after session resolution — handlePublic (separate port) has no /api/audit. IPs can't leak to the public site.
  • Read-only, no injection surface; rendered cells go through esc().

Findings

  1. (fixed in 8416eaa) Loose ok parse — the old okRaw == null ? undefined : okRaw === 'true' mapped a present-but-empty or garbage ?ok= to false, silently filtering to failures only. Now only the literal true/false filter; anything else is ignored. WEB smoke still green.
  2. (low, disclosed) Panel consumer inspection-onlyloadAudit isn't exercised by a smoke because the admin panel is a served static-HTML string with no headless DOM harness (same as the rest of the panel). The endpoint it calls is fully covered.

Conventions

  • Follows the existing global-admin-route pattern (/api/site), the panel api() helper, and the section/tab structure. Panel stays English, matching the non-i18n admin panel (public site i18n is separate and untouched).

Verification

typecheck (node + web) ✓ · build ✓ · MSMS_SMOKE_WEB — owner 200 newest-first + sources/text/ok filters, non-owner 403, no token 401 ✓ — green by exit code, re-run after the fix.

Verdict: ready to merge. One real nit found + fixed; no blockers.

@CaYatur
CaYatur merged commit 8dfb940 into main Jul 24, 2026
1 check passed
@CaYatur
CaYatur deleted the feat/web-audit-endpoint branch July 24, 2026 10:49
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.

Audit: admin-gated web endpoint for the audit log

2 participants