Skip to Content
Deneva MCPComponentsAdmin Routes

Admin routes

Source: src/auth/admin-routes.ts

Routes that operators (not tenants) call to manage tenant credentials and inspect runtime state. Phase 1 shipped one route (API-key rotation); Phase 2 PR-7 adds two more (platform disconnect, cache metrics).

All routes share the same auth gate: X-Admin-Token header, constant-time compared against the Phase 1 placeholder (see § Phase 1 placeholder). 401 on mismatch.

POST /admin/api-keys/rotate

HeaderRequiredNotes
X-Admin-TokenyesConstant-time compared against the dev placeholder (see “Phase 1 placeholder” below).
Content-Typeyesapplication/json

Body:

{ "tenantId": "<uuid>", "description": "Claude Desktop - prod" }

Response (201 Created):

{ "apiKey": "<43-char base64url>", "graceUntil": "<ISO timestamp +24h>" }

The raw apiKey is returned once. There is no endpoint to retrieve it later — the database stores only the HMAC hash.

What the rotation does

In one DB transaction:

  1. Set expires_at = now + 24h on every existing key for the tenant. The 24-hour grace lets clients update before old keys stop working.
  2. INSERT a new key row with expires_at = now + 365d.

After 24h, the old keys naturally expire (the auth middleware’s gt(apiKeys.expiresAt, now) clause). No separate revocation pass is needed.

Phase 1 placeholder

The admin token is currently derived from API_KEY_HMAC_SECRET so we don’t yet need a separate ADMIN_TOKEN secret. This is a deliberate Phase 1 simplification. Phase 5 introduces a real ADMIN_TOKEN credential AND moves these routes behind nginx with an IP allow-list (so only operators on a known IP range can hit them).

If you deploy Phase 1 to the public internet without the Phase 5 nginx allow-list, an attacker who learns API_KEY_HMAC_SECRET (e.g. from a backup leak) can rotate any tenant’s key. Don’t do that.

POST /admin/tenants/:tenantId/disconnect/:platform

Phase 2 (PR-7). Disconnects a tenant from a platform: best-effort upstream OAuth revoke (POST to https://oauth2.googleapis.com/revoke) followed by DELETE FROM platform_credentials WHERE tenant_id = $1 AND platform = $2 inside an RLS-scoped transaction.

Path paramValidatorNotes
tenantIdz.string().uuid() (v4-strict)Production tenant IDs from gen_random_uuid() pass; nil/placeholder UUIDs in tests must use proper v4 form.
platformz.enum(['google','meta','tiktok'])Phase 3 lifts the 501 for meta/tiktok once their adapters land.

Response shapes:

  • 200 { status: 'disconnected', platform } — local row gone; upstream revoke attempted (best-effort).
  • 401 unauthorized — missing or wrong X-Admin-Token.
  • 400 invalid_params — malformed tenant UUID or unknown platform.
  • 501 unsupported_platform — platform validated but no adapter registered yet.
  • 502 revoke_failed — the adapter threw. Local wipe might or might not have happened; audit row carries the failure reason.

Idempotent: returns 200 even when there is no platform_credentials row to delete. Phase 2 leaves the tenant_deks row in place so re-connecting the same platform re-uses the same DEK; call destroyDek(tenantId) separately if you also want to make the tenant’s prior ciphertexts permanently unreadable.

GET /admin/metrics/cache

Phase 2 (PR-7). Returns the process-local cache hit/miss counters as one entry per ${platform}/${reportType}:

{ "google/account_health": { "hit": 3, "miss": 1, "hitRate": 0.75 }, "google/search_term_waste": { "hit": 0, "miss": 2, "hitRate": 0 } }

Empty object {} if no cache activity has happened since process start. Counters reset on restart (the underlying Map lives in src/cache/metrics.ts). Phase 5 replaces this with a Prometheus / OTel exporter.

Phase 1 placeholder

The admin token is currently derived from API_KEY_HMAC_SECRET so we don’t yet need a separate ADMIN_TOKEN secret. This is a deliberate Phase 1 simplification. Phase 5 introduces a real ADMIN_TOKEN credential AND moves these routes behind nginx with an IP allow-list (so only operators on a known IP range can hit them).

If you deploy Phase 1 to the public internet without the Phase 5 nginx allow-list, an attacker who learns API_KEY_HMAC_SECRET (e.g. from a backup leak) can rotate any tenant’s key OR disconnect their platform credentials. Don’t do that.

Audit

RouteSuccess eventFailure event
POST /admin/api-keys/rotateapi_key.rotated/success— (401s aren’t audited in Phase 1)
POST /admin/tenants/:id/disconnect/:platformoauth.token_revoked/successoauth.token_revoked/failure (on adapter throw)
GET /admin/metrics/cache

Failed admin-token checks return 401 silently — they do not write an audit row in Phase 1. Phase 5’s monitoring rule will pick up repeated 401s on /admin/* directly from nginx access logs.

Tests

  • Phase 1 §D4 acceptance — rotation grace window (old key works for ≤24h, new key works immediately, old key 401s after 25h via fake clock).
  • tests/integration/revocation.test.ts — disconnect happy path, idempotency, upstream-unreachable still wipes locally, 401 / 400 / 501 envelopes.
  • tests/integration/cache-metrics.test.ts — empty when no activity, expected shape after 1 miss + 3 hits, 401 without token.