System Architecture
AuthNexus is composed of three core binaries and one admin frontend, designed for multi-tenant authentication and operations at scale.
Process Overview
client/SDK ──mTLS TCP (custom binary protocol)── server_app
│
│ /cp/v2/* (mTLS HTTP)
▼
admin frontend ──HTTP──> control_plane_app ───> Control DB (SQLite/PG)
(admin + CP unified) Runtime DB (separate)control_plane_app
The unified management process. It hosts two sets of HTTP endpoints on a single server:
/admin/v1/*-- Admin API for the web dashboard (agent management, PKI, users, cards, applications, cloud functions, security, reports, settlement)./cp/v2/*-- Southbound interface for business nodes (checkin, command pull, config sync, SSE events, OCSP).
The admin HTTP API defaults to loopback-only (127.0.0.1:9090). Production deployments expose it through a reverse proxy. The CP southbound endpoint binds to 0.0.0.0:9091 to accept remote node connections over mTLS. A separate plain HTTP server on port 9092 handles OCSP responses.
On first startup, the CP enters setup-only mode until the four CAs are initialized via the web-based setup wizard. In this state, only /admin/v1/setup/* endpoints are available; /cp/* endpoints are not served.
server_app
The business TCP node. It handles SDK connections over a custom binary protocol on top of TLS 1.3 with mutual TLS authentication. All hot-path operations (login, heartbeat, card key validation, cloud functions, push notifications) flow through this process. HTTP is not used on the business data path.
Each server node connects back to the Control Plane via the four communication channels to receive commands, configuration updates, security deltas, and OCSP responses.
authnexus_sdk
A C++ static library for client integration. It provides a synchronous API and internally manages three threads: a reader thread (packet routing), a notify thread (push callback dispatch), and an optional heartbeat thread. See SDK Integration.
admin_frontend
A Vue 3 + TypeScript + Naive UI single-page application. It communicates exclusively through the /admin/v1/* REST API. Supports a demo mode via MSW (Mock Service Worker) that intercepts all API calls in-browser, allowing full feature demonstration without any backend process. Demo mode is activated by the VITE_DEMO_MODE=true environment variable and does not pollute the production build -- MSW, mocks, and seed data are dynamically imported only when enabled.
Database Architecture
AuthNexus uses two separate databases, even when both are SQLite:
| Database | Owner | Purpose |
|---|---|---|
| Control DB | control_plane_app | Agent accounts, applications, card types, card keys, users, PKI state, configurations, audit logs, settlement |
| Runtime DB | server_app | Session state, blacklist cache, auth epoch, runtime security data |
Both SQLite and PostgreSQL backends are supported. Schema files live in schema/:
sqlite_control_plane_schema.sql/postgres_control_plane_schema.sqlsqlite_server_schema.sql/postgres_server_schema.sql
The database abstraction layer (src/core/db/) provides a unified interface through database_interface.h, connection_pool.h, batch_executor.h, and transaction_guard_interface.h. Application code never interacts with SQLite or PostgreSQL APIs directly -- the abstraction layer handles connection pooling, transaction management, and SQL dialect differences.
Schema Management
Schema files are copied next to the compiled binaries via a CMake POST_BUILD step. Both processes auto-apply their schema on startup. The SQLite and PostgreSQL schemas are maintained in parallel and kept in sync.
Thread Domains (server_app)
The server process strictly partitions work into isolated thread domains. No domain shares threads with another.
| Domain | Purpose |
|---|---|
io_threads | Network I/O, connection coroutine scheduling, per-session strand |
logic_threads | Handler business logic (decode, JSON, CPU work). Physically isolated from I/O via LogicDispatcher::dispatch() |
db_threads | Blocking database operations (runtime.db().run(fn)) |
crypto_threads | Password hashing, certificate-intensive CPU work (runtime.crypto().run(fn)) |
cloud_function_threads | Lua sandbox execution, backpressure via cloud_function_max_in_flight |
cp_io_threads | CP HTTP client burst pool (checkin, config pull, OCSP fetch). Does not host SSE long connections |
sse_pool (fixed: 1) | Dedicated to the Channel 2 SSE long connection. Physically isolated from cp_io |
BackgroundRuntime | TaskScheduler, ControlPlaneAgent, DeltaPuller, and other background services. Each IBackgroundService gets an independent asio::strand |
Automatic Thread Scaling
The --auto flag (default) selects thread counts based on detected CPU cores:
| Cores | IO | Logic | DB | Crypto | CP_IO | CloudFunc | BG |
|---|---|---|---|---|---|---|---|
| 1--2 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
| 3--4 | 1 | 2 | 2 | 1 | 1 | 2 | 2 |
| 5--8 | 2 | 4 | 3 | 2 | 1 | 3 | 2 |
| 9+ | n/8 | n/2 | n/4 | n/8 | 2 | n/4 | 3 |
Values are clamped to sensible minimums and maximums. Manual overrides are available via CLI flags (-i, --logic-threads, -d, -c, --cp-io, --bg-threads).
Coroutine Executor Model
AuthNexus uses Asio C++20 coroutines with a strict two-rule executor model:
- Where a coroutine runs is decided at spawn time:
asio::co_spawn(target_executor, body, token). - Mid-coroutine offloading uses
async::run_on:co_await async::run_on(target, fn)runsfnontargetand resumes the caller on its original executor.
There is no "switch executor" primitive. Historical helpers like async::switch_to were removed because co_await asio::post(target, use_awaitable) is a no-op on strand-spawned coroutines. A source-code audit test (SourceCodeAudit.NoRawCoAwaitPostUseAwaitableInSourceTree) scans the entire src/ tree and fails the build if any raw co_await asio::{post|dispatch|defer}(.., use_awaitable) pattern is found.
The practical implication: handler code dispatches work to other pools via typed wrappers like runtime.db().run(fn), runtime.crypto().run(fn), and runtime.cloud_function().run(fn), all of which use async::run_on internally. Direct co_await asio::post is never used in business code.
Configuration Types
The Control Plane manages six configuration types, tracked in the config_registry:
| Config Type | Scope | Description |
|---|---|---|
server_runtime_settings | Per-node only | Server runtime parameters (timeouts, limits) |
app_policy | Per-app, global | Application-level policies |
app_variables | Per-app, global | Cloud variables readable by SDK |
app_client_ca_bundle | Per-app, global | Client CA trust bundle for mTLS |
app_mtls_trust_bundle | Per-app, global | mTLS trust bundle published to nodes |
cp_agent_runtime | Per-node only | CP agent runtime parameters (timeouts, retry, degraded threshold) |
Per-node-only types (server_runtime_settings, cp_agent_runtime) have no global layer. Nodes bootstrap from node_agent.json and receive per-node overrides via config pull.
Staged Startup (StageRunner)
Both server_app and control_plane_app use a StageRunner for phased initialization. Each stage is an independent function with its own logging and error propagation. On failure, stages unwind in reverse order for safe cleanup.
Server startup stages (abbreviated):
- Logging initialization
- Token generation
- Database connection and schema
- Token managers
- Security subsystem
- Caches
- Logic dispatcher
- Push notification system
- Cloud function runtime
- TCP server binding
- Scheduler tasks
- Config manager injection
- Runtime settings and app policies
- CP agent connection
- Runtime security sync
The business TLS port is not opened until the CP Agent has successfully applied critical configurations (fail-closed).
Key Source Directories
| Directory | Role |
|---|---|
src/core/ | Shared library -- DB abstraction, protocol codec, TLS/PKI, token management, caching, password hashing, rate limiting, card key logic, Lua cloud functions, runtime, ZIP, JSON utilities |
src/server/ | Server process -- executor, session management, protocol handlers, network layer, security enforcement, CP client, configuration, performance |
src/control_plane/ | CP process -- admin HTTP routes, CP southbound routes, business services, caching |
src/sdk/ | C++ client SDK -- synchronous API, internal reader/notify/heartbeat threads |
src/admin_frontend/ | Vue 3 admin dashboard with MSW demo mode |
src/website/ | VitePress marketing and documentation site |
tests/ | GoogleTest source files (*_tests.cpp), auto-discovered by CMake GLOB |
schema/ | SQLite and PostgreSQL schema files for both databases |
Multi-Tenant Model
AuthNexus is inherently multi-tenant at the application level. Each application (app_id) has isolated:
- User accounts and sessions
- Card keys and card types
- Cloud functions and cloud variables
- Client certificates and mTLS trust bundles
- Policies and configurations
Platform administrators (kind = 'platform_admin') manage all applications. Application agents (kind = 'app_agent') are scoped to their granted applications via the agent_app_grants table. This M:N relationship allows flexible agent-to-application assignment without coupling agent identity to a single app.
Next Steps
- Four-Channel Communication -- how nodes and the Control Plane communicate
- TLS & PKI -- the four-CA certificate infrastructure
- Deployment Guide -- production configuration and tuning