nxtgauge-ai-assistant/src/auth.rs
Tracewebstudio Dev 2e0b986e20 feat: JWT auth middleware, env-var CORS origins, real user_id in confirm_action
- Add src/auth.rs with require_auth middleware extracting user_id from Bearer token
- Wire auth middleware onto all /api/* routes
- Replace CorsLayer::permissive() with env-driven FRONTEND_URL/ADMIN_URL origins
- Pass real auth_user.user_id to confirm_action instead of conversation_id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:51:41 +02:00

78 lines
2.1 KiB
Rust

use axum::{
extract::Request,
http::{header::AUTHORIZATION, StatusCode},
middleware::Next,
response::{IntoResponse, Response},
Json,
};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
pub sub: String,
pub email: String,
pub roles: Option<Vec<String>>,
pub active_role: Option<String>,
pub exp: usize,
pub iat: usize,
}
#[derive(Debug, Clone)]
pub struct AuthUser {
pub user_id: String,
pub email: String,
pub claims: Claims,
}
pub async fn require_auth(mut request: Request, next: Next) -> Response {
let auth_header = request
.headers()
.get(AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.map(|s| s.to_owned());
let token = match auth_header.as_deref().and_then(|h| h.strip_prefix("Bearer ")) {
Some(t) => t.to_owned(),
None => {
return (
StatusCode::UNAUTHORIZED,
Json(serde_json::json!({
"error": "Authorization header required",
"code": "MISSING_TOKEN"
})),
)
.into_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()),
&Validation::new(Algorithm::HS256),
) {
Ok(data) => data,
Err(e) => {
tracing::debug!("JWT validation failed: {}", e);
return (
StatusCode::UNAUTHORIZED,
Json(serde_json::json!({
"error": "Token is invalid or expired",
"code": "INVALID_TOKEN"
})),
)
.into_response();
}
};
let auth_user = AuthUser {
user_id: token_data.claims.sub.clone(),
email: token_data.claims.email.clone(),
claims: token_data.claims,
};
request.extensions_mut().insert(auth_user);
next.run(request).await
}