This repository was archived by the owner on Aug 6, 2026. It is now read-only.
feat: add browser console UI with session auth and CSRF protection - #4
Merged
Conversation
Adds a server-rendered admin console UI under /admin/ui/*, alongside the existing bearer-token JSON API under /admin/api/* (unchanged). Covers dashboard, players, matches, matchmaker, leaderboards, economy, tournaments, chat, notifications, and system - one erlydtl page per domain on top of the existing JSON controllers' business logic. Auth: single-operator session (ETS-backed, hashed at rest, invalidated on admin-token rotation), HMAC-derived CSRF tokens bound to a live session, security response headers (CSP frame-ancestors/X-Frame-Options/ nosniff/no-store), per-IP login rate limiting, and a request-body size cap on every unauthenticated route (including the pre-existing bearer API, which had the same unbounded-buffering gap). Went through three rounds of architecture-guardian + beam-security-reviewer plus a final erlang-code-reviewer pass; all findings fixed and re-verified via live execution testing (compiled harnesses, real cowboy listeners, adversarial payloads), including a P0 static-file path traversal, a structurally-bypassable CSRF exclusion, and the unauthenticated body-buffering DoS. elp eqwalize-all sits at 91 errors vs main's pre-existing baseline of 82 - both are the same systemic cowboy_req-binding-imprecision pattern already present throughout the codebase; the delta here is closure wrapping from consolidating the auth guard into one implementation (asobi_admin_ui_auth:guarded/2) rather than ten hand-rolled copies. Dialyzer, which reasons about actual runtime types rather than static narrowing, stays clean throughout.
CI's Audit job flagged cowboy 2.15.0 (GHSA-w4f7-4cxr-rv3c, HTTP request/response splitting) - pre-existing, unrelated to this branch's actual changes, but blocking merge. Adding {cowboy, "~> 2.18"} alone wasn't enough: Nova's own rebar.config carries a stale {overrides, [{override, cowboy, [{deps, [{cowlib, "2.16.1"}, ...]}]}]}, added to work around a parsing quirk in cowboy's hex-published rebar.config. rebar3 overrides apply tree-wide regardless of which dependency declares them, so that pin won over the plain version constraint and cowboy 2.18 ended up paired with a cowlib that doesn't ship a compatible cow_cookie API for it (cow_cookie:parse_cookie/2 didn't exist, breaking every cookie-parsing test). Superseding the override in this repo's own rebar.config with the matching cowlib 2.19.0 (and ranch 2.2.1, cowboy's own transitive pin) resolves it correctly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a server-rendered admin console UI under
/admin/ui/*, on top of the existing bearer-token JSON API under/admin/api/*(unchanged, but see the body-size-limit note below). Covers dashboard, players, matches, matchmaker, leaderboards, economy, tournaments, chat, notifications, and system — one erlydtl page per domain, backed by the existing JSON controllers' business logic (refactored to expose shareddo_*functions rather than duplicating it).Origin: a Discord forum poster requested this, explicitly flagged "not a priority."
asobi-architecture-guardianscoped it first — verdict was accept the UI, reject a plugin-contribution model in favor of a static route table, fold it into this repo, and treat session+CSRF auth as the real precondition since a browser form in front of the shared admin token is a materially different attack surface than the existing bearer API.What's here
ASOBI_ADMIN_TOKENrotates.asobi_admin_csrf_plugin's moduledoc for why the pre-session login route is its own route group entirely rather than a path-string exclusion.frame-ancestors 'none'), X-Frame-Options, nosniff, no-store — this console can ban players and grant currency, so clickjacking is a real concern CSRF tokens don't address.trusted_proxyconfig knob for deployments behind a reverse proxy./admin/api/*bearer routes — Nova's plugin pipeline runs before thesecurityfun, so an unbounded body would otherwise buffer fully before auth ever runs.Review process
Went through three rounds of
asobi-architecture-guardian+beam-security-reviewer, then a finalerlang-code-reviewerpass. All findings fixed and re-verified via live execution testing (compiled harnesses, real cowboy listeners, adversarial payloads) rather than diff review alone. Headline findings caught and fixed along the way:%2F-encoded path traversal in the static asset controller (rewritten as an exact-filename allowlist — no path left to sanitize)asobi_admin_body_limit_plugin, applied to both new and pre-existing route groups)Secure, scoped too broadly (Path=/→Path=/admin/ui), and Nova's own unused default session cookie left with no security attributesonline/offlineplayer-status atom rendering as always-truthy in erlydtl (real bug, not security — fixed by reducing to a boolean before it reaches the template)Toolchain
rebar3 fmt/xref/dialyzerclean.elp lintclean.rebar3 eunit: 113/113 passing.elp eqwalize-all: 91 errors vsmain's pre-existing baseline of 82 — both are the same systemiccowboy_req-binding-imprecision pattern already present throughout the codebase (confirmed by diffing againstmainbefore any changes); the delta here is closure-wrapping from consolidating the auth guard into one implementation (asobi_admin_ui_auth:guarded/2) instead of ten hand-rolled copies. Dialyzer, which reasons about actual runtime types rather than static narrowing, stays clean throughout.Test plan
ASOBI_ADMIN_SESSION_SECRETis set before deploying (documented in README, fails closed likeASOBI_ADMIN_TOKENif missing)