use axum::{http::HeaderMap, http::header::AUTHORIZATION}; use chrono::{Utc, Duration as ChronoDuration}; use jsonwebtoken::{EncodingKey, DecodingKey, Header, Validation}; use serde::{Serialize, Deserialize}; use crate::error::AppError; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { pub sub: String, pub uid: i64, pub iss: String, pub exp: usize, pub typ: String, // access or refresh } pub fn encode_token(claims: &Claims, secret: &str) -> Result { let key = EncodingKey::from_secret(secret.as_bytes()); Ok(jsonwebtoken::encode(&Header::default(), claims, &key)?) } pub fn decode_token(token: &str, secret: &str) -> Result { let key = DecodingKey::from_secret(secret.as_bytes()); let data = jsonwebtoken::decode::(token, &key, &Validation::default())?; Ok(data.claims) } #[derive(Clone, Debug)] pub struct AuthUser { pub uid: i64, pub username: String } impl axum::extract::FromRequestParts for AuthUser where S: Send + Sync + 'static { type Rejection = AppError; async fn from_request_parts(parts: &mut axum::http::request::Parts, _state: &S) -> Result { let headers: &HeaderMap = &parts.headers; let auth = headers.get(AUTHORIZATION).ok_or(AppError::Unauthorized)?; let auth = auth.to_str().map_err(|_| AppError::Unauthorized)?; let token = auth.strip_prefix("Bearer ").ok_or(AppError::Unauthorized)?; let secret = std::env::var("JWT_SECRET").map_err(|_| AppError::Unauthorized)?; let claims = decode_token(token, &secret)?; if claims.typ != "access" { return Err(AppError::Unauthorized); } Ok(AuthUser { uid: claims.uid, username: claims.sub }) } } pub fn new_access_claims(uid: i64, username: &str) -> Claims { let iss = std::env::var("JWT_ISS").unwrap_or_else(|_| "udmin".into()); let exp_secs: i64 = std::env::var("JWT_ACCESS_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1800); let exp = (Utc::now() + ChronoDuration::seconds(exp_secs)).timestamp() as usize; Claims { sub: username.to_string(), uid, iss, exp, typ: "access".into() } } pub fn new_refresh_claims(uid: i64, username: &str) -> Claims { let iss = std::env::var("JWT_ISS").unwrap_or_else(|_| "udmin".into()); let exp_secs: i64 = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600); let exp = (Utc::now() + ChronoDuration::seconds(exp_secs)).timestamp() as usize; Claims { sub: username.to_string(), uid, iss, exp, typ: "refresh".into() } }