Skip to Content
ReferenceServicesAdmin Console

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

ControllerRoutePurpose
AdminAuthControllerPOST /auth/login, /auth/demo-loginAdmin login; demo-login is feature-flagged
AdminUserControllerGET/POST /users, GET/PUT /{id}, PUT /{id}/role, DELETE /{id}CRUD on admin users + role changes
SystemConfigControllerGET /config, GET/PUT /{key}Key-value system configuration (upsert)
AuditLogControllerGET /audit-logs, GET /{id}, POST /nl-searchQuery platform audit logs; /nl-search is LLM-backed
NetworkStatsControllerGET /network-stats, /network-stats/insightDashboard stat aggregates + LLM one-sentence insight, both proxied
SystemHealthControllerGET /system-healthPolls api-gateway and core-engine directly; core-engine reports the gRPC microservices via its own health check
SystemLogControllerGET /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-ID only 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 a role claim 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 require ADMIN or OPERATOR — VIEWER is read-only, per-request.
  • /auth/**, /actuator/**, /v3/api-docs, /swagger-ui are 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:

ControllerProxies to
NetworkStatsControllerGET {core-engine}/api/v1/network-stats (+ /insight, Core Engine’s LLM one-sentence highlight)
AuditLogController.nlSearchPOST {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
SystemHealthControllerapi-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).