Files
Ferrous-Solitaire/solitaire_server/src/main.rs
T
funman300 ccfeb055e5 fix(server): load JWT_SECRET at startup, add auth logging, fix challenge race
- Introduce AppState { pool, jwt_secret } so JWT_SECRET is loaded once in
  main() and any missing value is a fatal startup error rather than a 500
  on the first request.  All four env::var("JWT_SECRET") call sites in
  auth.rs and middleware.rs are replaced with state.jwt_secret.
- build_test_router embeds the fixed test secret so integration tests do
  not need to set JWT_SECRET in the environment.
- Add tracing::warn! in login (invalid password) and register (username
  taken) to surface brute-force attempts in production logs.
- Fix daily-challenge race condition: after INSERT OR IGNORE, re-SELECT
  the persisted row so concurrent requests both return the winner's data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 22:35:46 +00:00

65 lines
2.2 KiB
Rust

//! Solitaire Quest sync server entry point.
//!
//! Reads configuration from environment variables (via `dotenvy`), initialises
//! the SQLite database, runs migrations, then starts the Axum HTTP server.
//!
//! ## Required environment variables
//!
//! | Variable | Description |
//! |----------------|---------------------------------------------------|
//! | `DATABASE_URL` | SQLite connection string, e.g. `sqlite://sol.db` |
//! | `JWT_SECRET` | HS256 signing secret (min 32 chars recommended) |
//!
//! ## Optional
//!
//! | Variable | Default | Description |
//! |---------------|---------|-------------------------------|
//! | `SERVER_PORT` | `8080` | TCP port to listen on |
use solitaire_server::{build_router, AppState};
use sqlx::SqlitePool;
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
// Load .env file if present (silently ignored when absent).
dotenvy::dotenv().ok();
// Initialise structured logging.
tracing_subscriber::fmt::init();
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
// Load JWT_SECRET once at startup — a missing secret is a fatal configuration error.
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
let port: u16 = std::env::var("SERVER_PORT")
.unwrap_or_else(|_| "8080".into())
.parse()
.expect("SERVER_PORT must be a valid port number");
// Connect to SQLite and run pending migrations.
let pool = SqlitePool::connect(&db_url)
.await
.expect("failed to connect to database");
sqlx::migrate!("./migrations")
.run(&pool)
.await
.expect("database migration failed");
tracing::info!("database ready at {db_url}");
let state = AppState { pool, jwt_secret };
let app = build_router(state);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
tracing::info!("listening on {addr}");
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect("failed to bind TCP listener");
axum::serve(listener, app)
.await
.expect("server error");
}