- 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>
78 lines
2.1 KiB
Rust
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
|
|
}
|