---
name: Add server binary
overview: Add a `server` Cargo binary target that loads config from the environment, verifies PostgreSQL connectivity before listening, builds `AthenaAuth` with `SqlxAdapter` and a broad plugin set (matching the repo’s “full stack” test harness), mounts the Axum router under `AuthConfig::base_path`, and optionally serves `static/admin/`. Align docs/Docker later if desired.
todos:
  - id: cargo-bin-feature
    content: Add `server` bin entry, composite `server` feature, optional deps (dotenvy, tracing, tracing-subscriber, tower-http)
    status: completed
  - id: impl-server-main
    content: "Implement `src/bin/server.rs`: env parsing, DB ping, full AuthBuilder plugin stack + UserManagementPlugin, nest router, optional `/admin` static, graceful errors"
    status: completed
  - id: docs-env
    content: Update AGENTS.md, README run instructions, and `.env.example` for new vars / `cargo run --bin server`
    status: completed
  - id: docker-optional
    content: (Optional) Fix Dockerfile to build `server` with workspace sources and correct CMD
    status: completed
isProject: false
---

# Add `server` binary for full Axum + Postgres auth

## Context

- The root package [`Cargo.toml`](c:\Users\floris\Documents\GitHub\athena-auth\Cargo.toml) defines the `athena-auth` **library** plus two **utility** bins (`sync_better_auth_openapi`, `openapi_alignment_report`). There is currently **no** HTTP server binary — so `cargo run`, AGENTS.md, and [`Dockerfile`](c:\Users\floris\Documents\GitHub\athena-auth\Dockerfile) (which expects `target/release/athena-auth`) are out of sync with the code.
- PostgreSQL wiring already exists: [`SqlxAdapter`](c:\Users\floris\Documents\GitHub\athena-auth\crates\core\src\adapters\database.rs) has `new` / `with_config` and **`test_connection()`** (`SELECT 1`) — ideal for startup checks.
- The framework expects HTTP paths under a configurable prefix (default **`/api/auth`** in [`AuthConfig` default](c:\Users\floris\Documents\GitHub\athena-auth\crates\core\src\config.rs)); handlers strip `base_path` internally ([`AthenaAuth::handle_request_inner`](c:\Users\floris\Documents\GitHub\athena-auth\src\core\auth.rs)). The Axum integration registers routes at **relative** paths like `/health`, `/sign-in/email` ([`AxumIntegration`](c:\Users\floris\Documents\GitHub\athena-auth\src\handlers\axum.rs)), so the app must **`nest(&config.base_path, auth.axum_router())`** (same pattern as docs under “Mounting Auth Routes”).
- **Plugin set for “full auth server”**: Mirror [`create_test_auth`](c:\Users\floris\Documents\GitHub\athena-auth\tests\compat\helpers.rs) (Email + Session + Password + Account + Email verification + API keys + OAuth + 2FA + Organization + Passkey + Admin). **Also add** [`UserManagementPlugin`](c:\Users\floris\Documents\GitHub\athena-auth\crates\api\src\plugins\user_management\mod.rs) with sensible defaults so change-email / delete-user **plugin** flows work end-to-end (core already exposes related paths; plugins complete the surface area).
- **Schema caveat**: Root [`schema.sql`](c:\Users\floris\Documents\GitHub\athena-auth\schema.sql) is a minimal legacy layout; `SqlxAdapter` and docker-compose docs may not match [`sql/provision.sql`](c:\Users\floris\Documents\GitHub\athena-auth\sql\provision.sql). The binary should **not** auto-migrate; document that operators must apply the schema that matches their deployment (and consider fixing compose/README separately).

## Implementation approach

### 1. Cargo wiring

- Add `[[bin]] name = "server" path = "src/bin/server.rs"`.
- Introduce a composite feature, e.g. `server = ["axum", "sqlx-postgres", ...]`, and set `required-features = ["server"]` on the bin so `cargo build` without features stays unchanged.
- Add **optional** deps used only when `server` is enabled (keeps default library consumers lean):
  - `dotenvy` — load `.env` when present.
  - `tracing` + `tracing-subscriber` — structured startup and request logs (env filter `RUST_LOG` / default `info`).
  - `tower-http` — optional `ServeDir` for [`static/admin/`](c:\Users\floris\Documents\GitHub\athena-auth\static\admin) at `/admin` (README already describes this).

### 2. `src/bin/server.rs` behavior

1. **Tracing**: `tracing_subscriber::fmt().with_env_filter(...)`.
2. **Env**: `dotenvy::dotenv().ok()`; read:
   - `DATABASE_URL` (required)
   - `JWT_SECRET` (required; map into `AuthConfig::new(...)`; enforce minimum length **32** to match docs / crypto expectations)
   - `PORT` (default `3000`), `HOST` (default `0.0.0.0`)
   - `BASE_URL` → `AuthConfig::base_url` (default `http://localhost:{PORT}`)
   - `AUTH_BASE_PATH` → `AuthConfig::base_path` (optional override; default remains **`/api/auth`**)
   - `TRUSTED_ORIGINS` — comma-separated → `trusted_origin` / `trusted_origins`
   - Passkey: e.g. `PASSKEY_RP_ID`, `PASSKEY_RP_NAME`, `PASSKEY_ORIGIN` (defaults derived from `BASE_URL` / `localhost` like tests)
3. **DB**:
   - Build `SqlxAdapter::with_config(&database_url, PoolConfig::default())` (or tuned via env later).
   - **`adapter.test_connection().await`**; on failure print a clear error (“cannot reach PostgreSQL at …”) and exit non-zero.
4. **Auth build**:
   - `AuthBuilder::new(config).database(adapter).plugin(...)` — same order as `create_test_auth`, plus `UserManagementPlugin::new()` with change-email / delete-user enabled as appropriate.
   - `Arc::new(auth.build().await?)`
5. **Axum**:
   - `let inner = auth.clone().axum_router();`
   - `Router::new().nest(base_path, inner)` — normalize `base_path` so `nest` always gets a `/leading/path` without double slashes.
   - If `server` includes static: `.nest_service("/admin", ServeDir::new("static/admin"))` (or `include_dir`/embedded assets if you want a single portable binary — default to filesystem path relative to cwd for simplicity).
   - CORS: rely on built-in `CorsMiddleware` from `AuthBuilder` defaults unless env toggles are added later.
6. **Serve**: `TcpListener::bind((host, port)).await?` then `axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())` (or plain `into_make_service()` if connect info unused).
7. **CLI (optional)**: Light use of existing `clap` dep for `--host` / `--port` overrides env (nice for ops).

### 3. Documentation touchpoints (minimal)

- **AGENTS.md**: Replace “`cargo run`” with `cargo run --bin server --features server` (or document the meta-feature only).
- **README** / **`.env.example`**: Mention `JWT_SECRET`, `BASE_URL`, optional `AUTH_BASE_PATH`, Passkey vars, and that the executable is `server`.

### 4. Docker (optional follow-up)

- [`Dockerfile`](c:\Users\floris\Documents\GitHub\athena-auth\Dockerfile) currently copies only `src/` and builds a non-existent `athena-auth` bin. After this change, update the Dockerfile to copy the full tree (or at least workspace manifests + `crates/`), `cargo build --release --bin server --features server`, and `CMD ["/app/server"]`. **Only if you want Docker in scope for this task.**

## Verification

- `cargo build --bin server --features server`
- `cargo clippy --bin server --features server`
- With Postgres up and schema applied: `cargo run --bin server --features server` and hit `GET {BASE_URL}/api/auth/health` (or your overridden base path).

```mermaid
flowchart LR
  subgraph startup [Startup]
    Env[Env_dotenv]
    Pool[SqlxAdapter_with_config]
    Ping[test_connection_SELECT_1]
    Build[AuthBuilder_plugins_build]
  end
  Env --> Pool --> Ping --> Build
  Build --> Axum[nest_base_path_axum_router]
  Axum --> Listen[TcpListener_serve]
```
