Admin Console — Service Reference
Module: backends/admin-console (com.attestpro.adminservice) · Port: 8084 (REST only) ·
Schema: console (admin_service_user, least-privilege)
Purpose
Platform-admin REST API for the Admin Console MFE (frontends/admin-console, :5175). Its own
Spring Boot module, not behind Core Engine/gRPC — the API Gateway routes /api/v1/admin/**
directly to it (except /api/v1/admin/catalog/**, which routes to Core Engine instead). It
manages admin users, system configuration, audit-log querying, system health/log tailing, and
network-wide stats — it does not own issuer/verifier/catalog data; those remain Core Engine’s
and Program Catalog’s, reached here only via server-to-server HTTP proxying.
Controllers and routes
| Controller | Route | Purpose |
|---|---|---|
AdminAuthController | POST /auth/login, /auth/demo-login | Admin login; demo-login is feature-flagged |
AdminUserController | GET/POST /users, GET/PUT /{id}, PUT /{id}/role, DELETE /{id} | CRUD on admin users + role changes |
SystemConfigController | GET /config, GET/PUT /{key} | Key-value system configuration (upsert) |
AuditLogController | GET /audit-logs, GET /{id}, POST /nl-search | Query platform audit logs; /nl-search is LLM-backed |
NetworkStatsController | GET /network-stats, /network-stats/insight | Dashboard stat aggregates + LLM one-sentence insight, both proxied |
SystemHealthController | GET /system-health | Polls api-gateway and core-engine directly; core-engine reports the gRPC microservices via its own health check |
SystemLogController | GET /system-logs/services, /system-logs/stream (SSE) | Live container log tailing via an optional Docker Engine API sidecar |
All under /api/v1/admin/**. Real endpoints beyond the historical route table:
POST /audit-logs/nl-search, GET /network-stats(/insight), GET /system-health. No
DashboardWidgetController exists — dashboard widgets are documented-but-unbuilt, not a gap in
this reference.
Auth model — deliberately distinct from holder/verifier/issuer auth
Admin auth is its own thing, not reused from Core Engine’s holder/verifier cookies or
issuer-portal JWTs, despite all three sharing the same HS512 secret (JWT_SECRET).
AdminAuthorizationFilter is the load-bearing mechanism:
- Trusts
X-User-Role/X-Token-Scope/X-User-IDonly because the API Gateway’s JWT filter strips any client-supplied copies first — this service has no independent way to tell a gateway-set header from a spoofed one, so it must never be reachable except through the gateway. - Gates on
X-Token-Scope == "admin-console", not role alone. Core Engine’s issuer-portal JWTs carry aroleclaim from an overlapping vocabulary ("Admin"/"Operator"/"Viewer", scoped to that issuer’s own portal) and are signed with the same shared secret. Gating on role alone would let an issuer’s own portal-admin session be presented here and treated as a platform admin —token_scope: "admin-console"prevents this confusable-role attack. - Valid roles:
ADMIN,OPERATOR,VIEWER. Mutating methods (POST/PUT/PATCH/DELETE) additionally requireADMINorOPERATOR—VIEWERis read-only, per-request. /auth/**,/actuator/**,/v3/api-docs,/swagger-uiare exempted.
Bootstrap: AdminUserBootstrap seeds exactly one ADMIN user on first run when
console.admin_users is empty (ADMIN_BOOTSTRAP_EMAIL/ADMIN_BOOTSTRAP_PASSWORD) — so a fresh
environment is never permanently locked out. Leaving the password unset generates and logs one — a
dev/local convenience only.
Demo login: POST /auth/demo-login is passwordless sign-in as the bootstrap admin, gated by
admin.demo-login.enabled (default false) plus a required fixed 6-digit
ADMIN_DEMO_LOGIN_CODE. Off entirely (404) unless both are explicitly set. Not for any deployment
holding real data.
Cross-service proxying pattern
admin_service_user is scoped to console only — no grant on wallet/core/authority. Where
the console needs data it doesn’t own, it proxies a server-to-server HTTP call rather than being
granted broader DB access:
| Controller | Proxies to |
|---|---|
NetworkStatsController | GET {core-engine}/api/v1/network-stats (+ /insight, Core Engine’s LLM one-sentence highlight) |
AuditLogController.nlSearch | POST {core-engine}/api/v1/audit-logs/nl-search — translates plain English to the existing status/search-text filters; the model never queries admin-console data directly |
SystemHealthController | api-gateway and core-engine directly; the 3 gRPC microservices are polled by Core Engine’s own health check and reported through the single core-engine call |
System log tailing
SystemLogController streams container stdout/stderr as SSE (GET /system-logs/stream?services=a,b&tail=n), backed by an optional Docker Engine API sidecar
(DOCKER_PROXY_URL) via DockerLogsClient. Unset → /services reports available: false,
/stream emits a single unavailable SSE event. RemoteLogsClient (app.remote-logs.*)
additionally lets one admin console tail a different deployed environment’s logs — useful for a
local console watching staging/prod without being deployed alongside it.
Data model (console schema)
Three tables: admin_users, system_config, admin_audit_logs. No dashboard_widgets table —
consistent with there being no dashboard-widgets controller.
Chain-of-custody relevance
The Cross-Custodian Convergence doc’s
originally-proposed “admin convergence queue” is not implemented anywhere in this module — no
convergence/escalation controller, entity, or route exists here or in Core Engine. Consistent with
that design’s revision toward autonomous-by-default; the escalation-only admin surface for
genuinely ambiguous matches remains Planned. If built, it belongs here, alongside
AuditLogController/SystemConfigController.
Implementation status
Fully implemented: auth, user management, config, audit-log querying (+ NL search), network stats (+ LLM insight), system health, log tailing. Not implemented: dashboard widgets, and the cross-custodian-convergence admin escalation queue (spec’d, explicitly Planned, no code).