From 6decf8dce2a45acaacf17d8715218661fde93e73 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Thu, 2 Jul 2026 18:31:35 +0530 Subject: [PATCH] Fix panic-on-missing-env in auth middleware and ticket provider require_auth and NxtgaugeTicketProvider::create_ticket both called env::var(...).expect(...) on every request, crashing instead of returning a proper error when JWT_SECRET/AI_SERVICE_KEY were unset. Both are now loaded once into AppConfig at startup and checked with a graceful 401/500 response. --- .env.example | 7 +++++ src/auth.rs | 26 +++++++++++++++---- src/config/mod.rs | 16 ++++++++++++ src/main.rs | 1 + .../tickets/nxtgauge_ticket_provider.rs | 13 +++++++--- src/routes/mod.rs | 2 +- 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index cc35e6a..edbe9d8 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,10 @@ OLLAMA_EMBED_MODEL=nomic-embed-text HELP_CENTER_SEED_PATH=./seeds/help_articles.json TICKETS_SOURCE=chatbot +NXTGAUGE_USERS_URL=http://localhost:9101 + +# Required: must match the JWT signing secret used by nxtgauge-backend-rust, +# and the AI service key nxtgauge-backend-rust expects on X-AI-Service-Key. +# Requests fail closed (401/500) if these are unset, rather than crashing. +JWT_SECRET= +AI_SERVICE_KEY= diff --git a/src/auth.rs b/src/auth.rs index fedfdf4..999cf14 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,5 +1,5 @@ use axum::{ - extract::Request, + extract::{Request, State}, http::{header::AUTHORIZATION, StatusCode}, middleware::Next, response::{IntoResponse, Response}, @@ -8,6 +8,8 @@ use axum::{ use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use serde::{Deserialize, Serialize}; +use crate::state::AppState; + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { pub sub: String, @@ -25,7 +27,23 @@ pub struct AuthUser { pub claims: Claims, } -pub async fn require_auth(mut request: Request, next: Next) -> Response { +pub async fn require_auth( + State(state): State, + mut request: Request, + next: Next, +) -> Response { + if state.config.jwt_secret.is_empty() { + tracing::error!("rejecting request: JWT_SECRET is not configured"); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({ + "error": "Authentication is misconfigured", + "code": "AUTH_MISCONFIGURED" + })), + ) + .into_response(); + } + let auth_header = request .headers() .get(AUTHORIZATION) @@ -46,11 +64,9 @@ pub async fn require_auth(mut request: Request, next: Next) -> Response { } }; - let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); - let token_data = match decode::( &token, - &DecodingKey::from_secret(jwt_secret.as_bytes()), + &DecodingKey::from_secret(state.config.jwt_secret.as_bytes()), &Validation::new(Algorithm::HS256), ) { Ok(data) => data, diff --git a/src/config/mod.rs b/src/config/mod.rs index e056805..0126fb0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -15,6 +15,8 @@ pub struct AppConfig { pub help_center_seed_path: String, pub tickets_source: String, pub nxtgauge_users_url: String, + pub jwt_secret: String, + pub ai_service_key: String, } impl AppConfig { @@ -47,6 +49,20 @@ impl AppConfig { "NXTGAUGE_USERS_URL", "http://nxtgauge-rust-users:9101", ), + jwt_secret: { + let v = std::env::var("JWT_SECRET").unwrap_or_default(); + if v.is_empty() { + tracing::error!("JWT_SECRET is not set; all authenticated requests will be rejected"); + } + v + }, + ai_service_key: { + let v = std::env::var("AI_SERVICE_KEY").unwrap_or_default(); + if v.is_empty() { + tracing::error!("AI_SERVICE_KEY is not set; ticket creation will fail"); + } + v + }, } } diff --git a/src/main.rs b/src/main.rs index dc88577..91f49a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,6 +85,7 @@ async fn main() { http_client, cfg.nxtgauge_users_url.clone(), cfg.tickets_source.clone(), + cfg.ai_service_key.clone(), )); let state = AppState::new( diff --git a/src/providers/tickets/nxtgauge_ticket_provider.rs b/src/providers/tickets/nxtgauge_ticket_provider.rs index 9450c3d..9a65382 100644 --- a/src/providers/tickets/nxtgauge_ticket_provider.rs +++ b/src/providers/tickets/nxtgauge_ticket_provider.rs @@ -12,6 +12,7 @@ pub struct NxtgaugeTicketProvider { client: Client, base_url: String, source: String, + ai_service_key: String, } #[derive(Debug, Deserialize)] @@ -31,11 +32,12 @@ struct NxtgaugeTicketRequest { } impl NxtgaugeTicketProvider { - pub fn new(client: Client, base_url: String, source: String) -> Self { + pub fn new(client: Client, base_url: String, source: String, ai_service_key: String) -> Self { Self { client, base_url, source, + ai_service_key, } } } @@ -56,13 +58,16 @@ impl TicketProvider for NxtgaugeTicketProvider { user_id: Some(payload.user_id), }; - let ai_service_key = std::env::var("AI_SERVICE_KEY") - .expect("AI_SERVICE_KEY must be set"); + if self.ai_service_key.is_empty() { + return Err(AppError::ExternalService( + "AI_SERVICE_KEY is not configured".to_string(), + )); + } let response = self .client .post(&url) - .header("X-AI-Service-Key", ai_service_key) + .header("X-AI-Service-Key", &self.ai_service_key) .json(&req) .send() .await diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 6e5be13..e19d053 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -44,7 +44,7 @@ pub fn build_router(state: AppState) -> Router { .route("/tickets/create", post(handlers::tickets::create)) .route("/help/search", post(handlers::help::search)) .route("/actions/confirm", post(handlers::confirm_action::confirm_action)) - .layer(middleware::from_fn(require_auth)), + .layer(middleware::from_fn_with_state(state.clone(), require_auth)), ) .layer(cors) .layer(TraceLayer::new_for_http())