56 lines
2.5 KiB
Rust
56 lines
2.5 KiB
Rust
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<String, AppError> {
|
|
let key = EncodingKey::from_secret(secret.as_bytes());
|
|
Ok(jsonwebtoken::encode(&Header::default(), claims, &key)?)
|
|
}
|
|
|
|
pub fn decode_token(token: &str, secret: &str) -> Result<Claims, AppError> {
|
|
let key = DecodingKey::from_secret(secret.as_bytes());
|
|
let data = jsonwebtoken::decode::<Claims>(token, &key, &Validation::default())?;
|
|
Ok(data.claims)
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct AuthUser { pub uid: i64, pub username: String }
|
|
|
|
impl<S> axum::extract::FromRequestParts<S> for AuthUser where S: Send + Sync + 'static {
|
|
type Rejection = AppError;
|
|
async fn from_request_parts(parts: &mut axum::http::request::Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
|
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() }
|
|
} |