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.
This commit is contained in:
parent
2e0b986e20
commit
6decf8dce2
6 changed files with 55 additions and 10 deletions
|
|
@ -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=
|
||||
|
|
|
|||
26
src/auth.rs
26
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<AppState>,
|
||||
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::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_secret(jwt_secret.as_bytes()),
|
||||
&DecodingKey::from_secret(state.config.jwt_secret.as_bytes()),
|
||||
&Validation::new(Algorithm::HS256),
|
||||
) {
|
||||
Ok(data) => data,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue