use anyhow::Result; use axum::{ extract::FromRequestParts, http::{request::Parts, HeaderMap, StatusCode}, response::{IntoResponse, Response}, }; use chrono::{Duration, Utc}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; use crate::{models::User, AppState}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub sub: Uuid, pub username: String, pub exp: usize, } pub struct AuthUser { pub user: User, } impl FromRequestParts> for AuthUser { type Rejection = Response; async fn from_request_parts( parts: &mut Parts, state: &Arc, ) -> Result { let headers = &parts.headers; let token = extract_token_from_headers(headers) .ok_or_else(|| (StatusCode::UNAUTHORIZED, "Missing authorization header").into_response())?; let claims = verify_jwt(&token, &state.config.jwt_secret) .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token").into_response())?; let user = state .db .get_user_by_id(claims.sub) .await .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response())? .ok_or_else(|| (StatusCode::UNAUTHORIZED, "User not found").into_response())?; Ok(AuthUser { user }) } } pub fn create_jwt(user: &User, secret: &str) -> Result { let expiration = Utc::now() .checked_add_signed(Duration::hours(24)) .expect("valid timestamp") .timestamp(); let claims = Claims { sub: user.id, username: user.username.clone(), exp: expiration as usize, }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()), )?; Ok(token) } pub fn verify_jwt(token: &str, secret: &str) -> Result { let token_data = decode::( token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::default(), )?; Ok(token_data.claims) } fn extract_token_from_headers(headers: &HeaderMap) -> Option { let auth_header = headers.get("authorization")?; let auth_str = auth_header.to_str().ok()?; if auth_str.starts_with("Bearer ") { Some(auth_str.trim_start_matches("Bearer ").to_string()) } else { None } }