Skip to content

Commit 4bd5dd1

Browse files
author
claude
committed
admin: read-only dashboard for Cloud accounts + drill-down
Problem: once real customers exist on Cloud, we'll need visibility into who's signed up, how much storage they're using, when they last backed up, and whether their account is in a weird state (past_due, canceled). Doing that via Railway SQL console every time someone emails in is painful. Building the dashboard now, while there are zero customers, means it exists the day it's needed and lets us sanity-check the Cloud chain with live data during the first-customer dry run. Scope (deliberately narrow): GET /admin/ — table of all accounts, newest first, with tier, status, site count, backup count, total storage, last-backup timestamp, and signup date GET /admin/account/{id} — drill-down: summary stats, per-site breakdown (LEFT JOIN so sites with zero backups still appear), recent backups (100 cap), recent web sessions (session tokens shown truncated to 8 chars so screenshotted pages don't leak them) GET /admin/json — same shape as index, JSON format, for scripting or future CLI integration Auth: HTTP basic auth against STOCKYARD_ADMIN_PASSWORD env var. Constant-time password compare to avoid timing leaks. When the env var is empty, every admin endpoint returns 404 rather than 401 — an attacker probing /admin/ on a server without admin configured gets no signal the endpoint exists. Fail-closed. Read-only by design. No mutation endpoints. Granting tiers, revoking accounts, deleting backups — all still require SSHing into Railway and writing SQL. That friction is the biggest protection against a leaked admin cookie, and keeping the admin surface small means the footprint to audit is small. Security headers on every admin response: Cache-Control: no-store, max-age=0 X-Robots-Tag: noindex, nofollow HTML rendering uses html/template (auto-escaping), so email addresses, site slugs, and the like can't inject scripts. JSON endpoint uses json.NewEncoder(w).Encode. Session token display: only first 8 chars + ellipsis. An admin who needs the full token can query the DB; shoulder-surfing or casual screenshots won't leak it. Tests (admin_test.go, 9 new, all passing): - DisabledReturns404 (fail-closed on empty env var) - UnauthorizedReturns401 (correct 401 + WWW-Authenticate header) - AuthorizedIndexReturnsHTML (seeded accounts render, HTML type) - EmptyStateRendersCleanly (no accounts still 200s with empty-state) - JSONEndpoint (parseable JSON with accounts array) - AccountDrillDown (account data, sites, backups, version all render) - AccountDrillDown_NotFound (unknown ID returns 404) - AccountDrillDown_BadID (non-numeric/negative/zero return 400) - SecurityHeaders (Cache-Control + X-Robots-Tag set correctly) Deployment: Set STOCKYARD_ADMIN_PASSWORD on Railway. Dashboard live immediately. If the env var is unset, the routes 404 and nothing changes for customers. Rotate by changing the env var — no code push needed. Flagged for future: - Add a refund helper (narrow mutation: mark account refunded + revoke) - Add search/filter on the index page when the list grows past ~100 - Add a 'resend magic link' button for customer support scenarios - Add storage-over-time chart once there's enough data to plot
1 parent 5932f6d commit 4bd5dd1

3 files changed

Lines changed: 955 additions & 0 deletions

File tree

0 commit comments

Comments
 (0)