Clears the 124 CODE_SMELL findings from the SonarQube scan (server, client,
and bot). All changes are behaviour-preserving refactors — no route, protocol,
schema, or config changes — verified against the full server (381) and client
(43) test suites plus a clean client build.
By rule:
- S3776 (20, cognitive complexity): extract helpers/handlers so each function
drops under the threshold — shard model upsert builders, page/wiki update,
block validation, notification stream mapping (dispatch table), SSO mobile
login, shard ingest deps, uo-link socket backfill/connect, the bot slash-
command dispatchers + discord manager, and the Shard/UserDetail/HeroEditor/
CharacterStats React components.
- S4624 (34, nested template literals): pull inner templates into locals /
a withQs() helper; rewrite shardEvents.describe() as a formatter table.
- S3358 (35, nested ternaries): lift to if/else vars, lookup maps, small
components, or guarded JSX expressions.
- S6479 (12, array-index React keys): key by stable content instead of index
(two in-editor lists left as-is; index matches their by-index edit model).
- S6353 (6): [0-9]/[^0-9] -> \d/\D. S125 (5): reword state-shape comments that
parsed as code. S3800/S3782 (botScore): JSDoc-type PATH_WEIGHTS tuples.
- S6481 (2): memoize Auth/Site context values (and SiteContext brand).
- S4144: dedupe HeroEditor upload handler into useImageUpload().
- S1126 (2), S6035, S5869 (redundant A-Z under /i), S5843 (town-name regex ->
prefix list): assorted one-liners.
Co-Authored-By: Claude <noreply@anthropic.com>
Expose the botScore middleware's in-memory scoring/ban state to admins.
Previously state lived only in the store Map with no persistence or API — the
only visibility was tailing container logs.
- botScore: bounded ring buffer (300) recording scan/login-fail/honeypot and
ban events (most-recent-first); listState() snapshot of all scored IPs;
unban() to clear a single IP.
- New admin-only endpoints GET /admin/bot-activity and
POST /admin/bot-activity/unban (RBAC admin gate, IP validated). Unban is
activity-logged with the admin username.
- Bot Activity tab: currently-banned table with Unban, plus a recent-events
feed, following the existing admin table patterns.
- Tests for the buffer, listState, and unban (guard lets an unbanned IP back
through). README updated.
Read + emergency-unban only — no ban-add or weight-editing surface. Buffer is
in-memory, matching the store; not persisted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a layered set of protections around the admin login and the app edge.
Trust proxy (server/src/utils/trustProxy.js)
- Configurable via TRUST_PROXY; pin to the newt agent ("ptero") LAN IP so
X-Forwarded-For is trusted ONLY from that peer. A blanket "true" is
rejected (coerced to 1) to prevent XFF spoofing that would dodge every
IP-based control. DEBUG_TRUST_PROXY logs peer/XFF/req.ip to re-verify the
proxy IP without a redeploy. Documents the Omada static-reservation
assumption.
Login throttling (server/src/middleware/loginProtection.js, rateLimit.js)
- express-slow-down progressive delay + the existing hard rate cap + a
separate per-IP exponential backoff that persists across the rate window.
All failures return one generic message (no user/pass disclosure).
Honeypot (login form + auth.controller)
- Hidden, plausibly-named field ("company"); a filled value fails
generically and is scored as an unambiguous bot.
Optional per-user TOTP 2FA (speakeasy/qrcode)
- totp_secret/totp_enabled columns (+ idempotent migration). Self-service
Account page: enroll via QR, confirm a code to enable, code-gated disable.
- Login is two-step for enrolled users: after the password, a short-lived
signed challenge (stage:'totp', not a session) is required before the
real session is issued.
Bot / scanner scoring + IP ban (server/src/middleware/botScore.js)
- Weighted CMS-scanner paths (this app uses none). Junk paths 404 FIRST,
unconditionally — independent of score/ban state, so a scanner rotating
through fresh Cloudflare IPs gets no free pass. /wp-admin/install.php is
the top-weighted near-1-hit ban (worst offender in prod logs). Per-IP
score with quiet-period decay temp-bans an IP from ALL routes once past a
(deliberately low) threshold, to protect /admin from credential stuffing.
Failed logins and honeypot hits feed the same score.
- Periodic sweep evicts stale, unbanned, quiet entries so the in-memory
store can't grow unbounded; the interval is unref'd and cleared on
graceful shutdown.
Tests: node --test suite (40) covering trust-proxy parsing + live req.ip
(incl. pinned-IP), rate limiter + exponential backoff, honeypot rejection,
TOTP verify (enabled/disabled) + challenge-isn't-a-session, bot-score
threshold/decay/ban + junk-404-independence + install.php + store sweep.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>