Arcana ARCANA
New in v0.4.0

This feature is part of the incoming v0.4.0 release from the arcanagov branch.

Admin Console

The Arcana Admin Console is a unified operations surface for platform administrators. It provides site-wide notice management, user analytics, usage monitoring, rate limit controls, and a full audit trail — all secured behind identity-based authentication with MFA.

Status: Design complete. Implementation is phased — see Implementation Phases for rollout timeline.

Overview

The Admin Console consolidates all operator tasks into a single secured surface at /admin. It replaces ad-hoc shared-token access with person-bound identity, allowlist enforcement, and multi-factor authentication.

Goals

  • Site-wide notices — Schedule-aware, color-customizable banners for all users without redeploying.
  • Unified admin shell — One surface for notices, users, usage, rate limits, feedback, and audit.
  • Real security — Person-bound identity, allowlist, MFA, audit trail, step-up for destructive actions.
  • Design consistency — Same density, type, color, and layout language as the console.
  • Privacy-aware analytics — Aggregates for maps and usage; no gratuitous raw PII.

Authentication

Admin access requires a multi-layer authentication pipeline:

  1. Operator opens /admin
  2. If no Supabase session → redirect to /auth?next=/admin
  3. After sign-in, the server validates:
    • Valid JWT (signature, expiration, audience)
    • Email (normalized) is in ADMIN_ALLOWLIST or app_metadata.admin === true
    • MFA level: currentLevel === 'aal2' (if aal1, force MFA challenge)
  4. Every admin API re-runs all checks — never trusts client-side "isAdmin" flags

Allowlist Configuration

# Site environment variables
[email protected],[email protected]
# Optional: comma-separated Supabase user UUIDs
ADMIN_USER_IDS=

Authorization Matrix

ActionAllowlistAAL2Step-up / ConfirmAudit
View overviewoptional
Read audit log
Edit notice
View usage / user aggregatesoptional
Reset rate limit (one user)confirm modal
Reset rate limits (ALL)re-MFA + type phrase
Mint license (machine)admin key onlyn/an/a
Delete feedbackconfirm

Site Notices

Site notices are schedule-aware banners that appear across the marketing site, auth pages, and console. They can be configured with custom colors, severity levels, and dismissibility.

Notice Properties

PropertyTypeDescription
enabledbooleanToggle notice on/off
severityinfo | warn | critical | devVisual severity level
titlestring (max 80)Notice headline
bodystring (max 400)Notice body text (plain text)
link_urlstring (https only)Optional call-to-action link
link_labelstring (max 40)Link display text
colors{ bg, fg, border }Custom hex colors
starts_atISO 8601 UTCSchedule start (or null for immediate)
ends_atISO 8601 UTCSchedule end (or null for indefinite)
surfacesmarketing | console | auth | allWhere the banner appears
dismissiblebooleanWhether users can close the banner

Default Colors by Severity

SeverityBackgroundForegroundBorder
info#1e3a5f#e8eef8#3b82f6
warn#422006#fef3c7#eab308
critical#450a0a#fecaca#dc2626
dev#1a1c20#f5f0e8#1d4ed8

Public Status API

Clients fetch active notices from a public endpoint:

GET /api/site-status

Returns the active notice (if any) with a server_time field for clock skew correction. Cached with Cache-Control: public, max-age=30, stale-while-revalidate=60.

Console Modules

Overview Dashboard

The landing page after admin login. Shows:

  • Active notice status (on/off + schedule window)
  • User counts: total, active 24h, free/pro split
  • Usage: 24h requests, error rate
  • Rate limit events: 24h count
  • Last 10 audit entries

Users

Aggregate user analytics — counts, tiers, activity windows. No raw PII dump; user IDs shown as prefixes unless in support mode.

Usage

Time-series charts for requests, tokens, and cost. Server-side aggregation only — no raw request logs downloadable in v1.

Geo Map

Country-level choropleth with sortable table. Filters for date range, tier, and free/paid split. No individual user location pins — privacy-first design with country aggregates only.

Rate Limits

Inspect and reset rate limits per user or globally. Global resets require re-MFA and typing the confirmation phrase RESET ALL RATE LIMITS.

Feedback

Existing feedback admin UI migrated under the admin shell. Token-based login replaced with identity-based admin session.

Audit Log

Append-only log of all admin actions. Filterable by action type, actor, and date. Each entry includes actor email, action, target, metadata JSON, hashed IP, and request ID.

Routes

PathModule
/adminOverview dashboard
/admin/noticesSite banner CRUD
/admin/usersUser counts, tiers, activity
/admin/usageUsage charts and totals
/admin/mapGeo choropleth + filters
/admin/rate-limitsInspect / reset rate limits
/admin/feedbackFeedback management
/admin/licensesLicense management (optional)
/admin/auditAudit log table

Security Design

Threat Model

ThreatImpactMitigation
Stolen shared admin tokenFull ops accessReplace with person-bound identity
XSS on site originSession/token theftStrict CSP; no long-lived secrets in JS
Phished operator passwordRate limit wipeMFA AAL2 required; step-up on destructive actions
Insider abuseSilent mass resetAudit log; typed confirmation; dual control later
Enumeration of /adminTargeted attacksnoindex; Access; no public links
Geo/IP leakagePrivacy/regulatoryCountry aggregates only; hash IP at rest

Trust Boundaries

BoundaryWho CrossesMechanism
Browser → public statusAnyoneNo auth; rate-limit lightly
Browser → admin APIsOperators onlyJWT + allowlist + MFA
Site → proxy adminSite Function onlyShort admin key (never in browser)
Proxy admin (machine)CI / device mintAdmin key break-glass

Audit Log Schema

All admin mutations are logged to an append-only D1 table:

CREATE TABLE admin_audit (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  created_at INTEGER NOT NULL,           -- unix ms
  actor_id TEXT,                          -- supabase uid
  actor_email TEXT NOT NULL,
  action TEXT NOT NULL,                   -- e.g. notice.update
  target TEXT,                            -- e.g. user_id, 'global'
  meta_json TEXT,                         -- JSON: before/after, counts
  ip_hash TEXT,                           -- sha256(ip + server_pepper)
  user_agent TEXT,
  request_id TEXT                         -- CF ray id
);

Application may INSERT + SELECT. No delete API. Retention: 2 years.

API Contracts

Site APIs (Pages Functions)

MethodPathAuthPurpose
GET/api/site-statusPublicActive notice for clients
GET/api/admin/meAdmin JWTActor profile, AAL, allowlist status
GET/api/admin/noticeAdminFull notice including inactive
PUT/api/admin/noticeAdminUpdate notice
GET/api/admin/auditAdminPaginated audit log
GET/api/admin/proxy/*AdminBFF to proxy admin (server secret)

Proxy Admin APIs

MethodPathAuthPurpose
GET/v1/admin/stats/overviewAdmin keyUsers, requests, tiers
GET/v1/admin/stats/usageAdmin keyTime series data
GET/v1/admin/stats/geoAdmin keyCountry buckets
POST/v1/admin/rate-limits/resetAdmin keyReset rate limits

The site acts as a BFF (Backend for Frontend) — browser calls site Functions with JWT, site Functions call proxy with admin key. The browser never holds the proxy admin key.

Design Tokens

The Admin Console inherits the console design system and adds admin-specific semantic tokens. Full dark and light theme support.

Color Tokens (Dark)

:root {
  --admin-bg: #0a0a0a;
  --admin-surface: #191919;
  --admin-surface-2: #1a1c20;
  --admin-border: #212327;
  --admin-text: #ffffff;
  --admin-text-muted: #7d8187;
  --admin-primary: #1d4ed8;       /* cobalt */
  --admin-primary-hover: #3b82f6;
  --admin-success: #22c55e;
  --admin-warning: #eab308;
  --admin-error: #dc2626;
  --admin-danger-bg: rgba(220, 38, 38, 0.08);
  --admin-danger-border: rgba(220, 38, 38, 0.35);
  --admin-danger-text: #fca5a5;
}

Typography

RoleFontSizeWeight
Page titlesans2xl (1.375rem)500–600
Section labelmonoxs (0.625rem)400, uppercase
Nav itemsanssm (0.8125rem)400
Bodysanssm–base400
Code / IDsmonoxs–sm400

Implementation Phases

Phase 1 — Admin Shell + Auth

  • /admin layout with sidebar navigation
  • Supabase login + allowlist + AAL2 gate
  • /api/admin/me endpoint
  • admin_audit D1 table
  • Migrate feedback under admin shell
  • Design tokens in admin-console.css

Exit criteria: Only allowlisted MFA users can enter; feedback still works; unauthenticated users blocked.

Phase 2 — Notices

  • KV notice storage
  • GET public status + PUT admin notice
  • Notices UI with live preview
  • Banner injection on marketing + console
  • Audit logging on notice updates

Exit criteria: Schedule a banner for a 10-minute window; all surfaces show/hide correctly.

Phase 3 — Ops Metrics + Rate Limits

  • Overview stats BFF
  • Users and usage pages
  • Rate limit inspect + per-user reset
  • Mass reset with step-up authentication
  • Audit all mutations

Phase 4 — Map + Hardening

  • Geo buckets at edge
  • Map UI with choropleth
  • Admin subdomain and/or Cloudflare Access
  • Privacy review

Operations Runbooks

Publish a Maintenance Banner

  1. Sign in to admin (MFA required)
  2. Navigate to Notices → enable → set UTC window → choose colors → set surfaces to all
  3. Save → verify banner appears on marketing + console within 60 seconds
  4. After the window expires, banner auto-hides; disable manually if needed

Emergency Rate-Limit Wipe

  1. MFA admin → Rate limits → Danger zone
  2. Enter reason + type RESET ALL RATE LIMITS
  3. Confirm step-up if prompted
  4. Verify audit row; spot-check that a free user can request again

Offboard an Operator

  1. Remove email from ADMIN_ALLOWLIST (and Cloudflare Access if enabled)
  2. Revoke Supabase sessions if needed
  3. Rotate machine admin keys if that operator had environment access
Last updated: Jul 30, 2026