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>
This commit is contained in:
funman300
2026-04-28 22:35:46 +00:00
parent 8f957d919f
commit ccfeb055e5
5 changed files with 74 additions and 31 deletions
+5 -2
View File
@@ -16,7 +16,7 @@
//! |---------------|---------|-------------------------------|
//! | `SERVER_PORT` | `8080` | TCP port to listen on |
use solitaire_server::build_router;
use solitaire_server::{build_router, AppState};
use sqlx::SqlitePool;
use std::net::SocketAddr;
@@ -29,6 +29,8 @@ async fn main() {
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()
@@ -46,7 +48,8 @@ async fn main() {
tracing::info!("database ready at {db_url}");
let app = build_router(pool);
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}");