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
+7 -6
View File
@@ -5,7 +5,7 @@
//! can access it via `Extension<AuthenticatedUser>`.
use axum::{
extract::{FromRequestParts, Request},
extract::{FromRequestParts, Request, State},
http::request::Parts,
middleware::Next,
response::Response,
@@ -13,7 +13,7 @@ use axum::{
use jsonwebtoken::{decode, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
use crate::error::AppError;
use crate::{error::AppError, AppState};
/// The claims encoded in our JWT access tokens.
#[derive(Debug, Serialize, Deserialize)]
@@ -37,18 +37,19 @@ pub struct AuthenticatedUser {
/// Axum middleware function that validates the Bearer JWT and injects
/// [`AuthenticatedUser`] into request extensions.
///
/// Reads the JWT secret from [`AppState`] rather than the environment, so a
/// missing secret causes a startup failure rather than a per-request 500.
///
/// Returns `401 Unauthorized` if the token is missing, expired, or invalid.
pub async fn require_auth(
State(state): State<AppState>,
mut req: Request,
next: Next,
) -> Result<Response, AppError> {
let secret = std::env::var("JWT_SECRET")
.map_err(|_| AppError::Internal("JWT_SECRET not set".into()))?;
let token = extract_bearer_token(req.headers())
.ok_or(AppError::Unauthorized)?;
let claims = validate_access_token(&token, &secret)?;
let claims = validate_access_token(&token, &state.jwt_secret)?;
req.extensions_mut().insert(AuthenticatedUser {
user_id: claims.sub,