---
title: Admin Console
url: https://arcana.otnelhq.com/docs/admin-console
---

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: @@B15@@
4. Every admin API re-runs all checks — never trusts client-side "isAdmin" flags

### Allowlist Configuration

```bash
# Site environment variables
ADMIN_ALLOWLIST=ops@otnelhq.com,you@otnelhq.com
# Optional: comma-separated Supabase user UUIDs
ADMIN_USER_IDS=
```

### Authorization Matrix

| Action | Allowlist | AAL2 | Step-up / Confirm | Audit |
| --- | --- | --- | --- | --- |
| View overview | ✓ | ✓ | — | optional |
| Read audit log | ✓ | ✓ | — | — |
| Edit notice | ✓ | ✓ | — | ✓ |
| View usage / user aggregates | ✓ | ✓ | — | optional |
| Reset rate limit (one user) | ✓ | ✓ | confirm modal | ✓ |
| Reset rate limits (ALL) | ✓ | ✓ | re-MFA + type phrase | ✓ |
| Mint license (machine) | admin key only | n/a | n/a | ✓ |
| Delete feedback | ✓ | ✓ | confirm | ✓ |

## 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

| Property | Type | Description |
| --- | --- | --- |
| `enabled` | boolean | Toggle notice on/off |
| `severity` | info \| warn \| critical \| dev | Visual severity level |
| `title` | string (max 80) | Notice headline |
| `body` | string (max 400) | Notice body text (plain text) |
| `link_url` | string (https only) | Optional call-to-action link |
| `link_label` | string (max 40) | Link display text |
| `colors` | { bg, fg, border } | Custom hex colors |
| `starts_at` | ISO 8601 UTC | Schedule start (or null for immediate) |
| `ends_at` | ISO 8601 UTC | Schedule end (or null for indefinite) |
| `surfaces` | marketing \| console \| auth \| all | Where the banner appears |
| `dismissible` | boolean | Whether users can close the banner |

### Default Colors by Severity

| Severity | Background | Foreground | Border |
| --- | --- | --- | --- |
| `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:

```bash
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

| Path | Module |
| --- | --- |
| `/admin` | Overview dashboard |
| `/admin/notices` | Site banner CRUD |
| `/admin/users` | User counts, tiers, activity |
| `/admin/usage` | Usage charts and totals |
| `/admin/map` | Geo choropleth + filters |
| `/admin/rate-limits` | Inspect / reset rate limits |
| `/admin/feedback` | Feedback management |
| `/admin/licenses` | License management (optional) |
| `/admin/audit` | Audit log table |

## Security Design

### Threat Model

| Threat | Impact | Mitigation |
| --- | --- | --- |
| Stolen shared admin token | Full ops access | Replace with person-bound identity |
| XSS on site origin | Session/token theft | Strict CSP; no long-lived secrets in JS |
| Phished operator password | Rate limit wipe | MFA AAL2 required; step-up on destructive actions |
| Insider abuse | Silent mass reset | Audit log; typed confirmation; dual control later |
| Enumeration of /admin | Targeted attacks | noindex; Access; no public links |
| Geo/IP leakage | Privacy/regulatory | Country aggregates only; hash IP at rest |

### Trust Boundaries

| Boundary | Who Crosses | Mechanism |
| --- | --- | --- |
| Browser → public status | Anyone | No auth; rate-limit lightly |
| Browser → admin APIs | Operators only | JWT + allowlist + MFA |
| Site → proxy admin | Site Function only | Short admin key (never in browser) |
| Proxy admin (machine) | CI / device mint | Admin key break-glass |

### Audit Log Schema

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

```sql
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)

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| `GET` | `/api/site-status` | Public | Active notice for clients |
| `GET` | `/api/admin/me` | Admin JWT | Actor profile, AAL, allowlist status |
| `GET` | `/api/admin/notice` | Admin | Full notice including inactive |
| `PUT` | `/api/admin/notice` | Admin | Update notice |
| `GET` | `/api/admin/audit` | Admin | Paginated audit log |
| `GET` | `/api/admin/proxy/*` | Admin | BFF to proxy admin (server secret) |

### Proxy Admin APIs

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| `GET` | `/v1/admin/stats/overview` | Admin key | Users, requests, tiers |
| `GET` | `/v1/admin/stats/usage` | Admin key | Time series data |
| `GET` | `/v1/admin/stats/geo` | Admin key | Country buckets |
| `POST` | `/v1/admin/rate-limits/reset` | Admin key | Reset 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)

```css
: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

| Role | Font | Size | Weight |
| --- | --- | --- | --- |
| Page title | sans | 2xl (1.375rem) | 500–600 |
| Section label | mono | xs (0.625rem) | 400, uppercase |
| Nav item | sans | sm (0.8125rem) | 400 |
| Body | sans | sm–base | 400 |
| Code / IDs | mono | xs–sm | 400 |

## 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

## Related Documentation

- [Trust & Security](/docs/trust-boundaries) — Security model, CSP, and trust boundaries
- [Proxy API](/docs/proxy) — Proxy endpoints, authentication, and rate limits
- [Configuration](/docs/configuration) — Environment variables and config file reference
