use crate::{TokenSourceConfig, extract_token_with_config};
use axum::{
extract::{Extension, FromRequestParts},
http::{StatusCode, header, request::Parts},
};
use cookie::Cookie;
type AxumRejection = (StatusCode, &'static str);
fn unauthorized(body: &'static str) -> AxumRejection {
(StatusCode::UNAUTHORIZED, body)
}
fn internal(body: &'static str) -> AxumRejection {
(StatusCode::INTERNAL_SERVER_ERROR, body)
}
fn cookie_header_string(parts: &Parts) -> Option<String> {
let mut out = String::new();
for v in parts.headers.get_all(header::COOKIE).iter() {
let Ok(s) = v.to_str() else {
continue;
};
if out.is_empty() {
out.push_str(s);
} else {
out.push_str("; ");
out.push_str(s);
}
}
if out.is_empty() { None } else { Some(out) }
}
fn find_cookie_value(cookie_header: &str, target_name: &str) -> Option<String> {
Cookie::split_parse(cookie_header)
.filter_map(Result::ok)
.find(|cookie| cookie.name() == target_name)
.map(|cookie| cookie.value().to_string())
}
#[axum::async_trait]
impl<S> FromRequestParts<S> for crate::memory::RUser
where
S: Send + Sync,
{
type Rejection = AxumRejection;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Extension(manager) =
Extension::<crate::memory::RTokenManager>::from_request_parts(parts, state)
.await
.map_err(|_| internal("Token manager not found"))?;
let cfg = match Extension::<TokenSourceConfig>::from_request_parts(parts, state).await {
Ok(Extension(cfg)) => cfg,
Err(_) => TokenSourceConfig::default(),
};
let cookie_header = cookie_header_string(parts);
let token = extract_token_with_config(
&cfg,
|name| {
parts
.headers
.get(name)
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
},
|name| {
cookie_header
.as_deref()
.and_then(|h| find_cookie_value(h, name))
},
)
.ok_or_else(|| unauthorized("Unauthorized"))?;
#[cfg(feature = "rbac")]
{
let token_for_check = token.clone();
let manager = manager.clone();
let user_info =
tokio::task::spawn_blocking(move || manager.validate_with_roles(&token_for_check))
.await
.map_err(|_| internal("Mutex poisoned"))?
.map_err(|_| internal("Mutex poisoned"))?;
if let Some((user_id, roles)) = user_info {
return Ok(Self {
id: user_id,
token,
roles,
});
}
return Err(unauthorized("Invalid token"));
}
#[cfg(not(feature = "rbac"))]
{
let token_for_check = token.clone();
let manager = manager.clone();
let user_id = tokio::task::spawn_blocking(move || manager.validate(&token_for_check))
.await
.map_err(|_| internal("Mutex poisoned"))?
.map_err(|_| internal("Mutex poisoned"))?;
if let Some(user_id) = user_id {
return Ok(Self { id: user_id, token });
}
Err(unauthorized("Invalid token"))
}
}
}
#[cfg(feature = "redis")]
#[axum::async_trait]
impl<S> FromRequestParts<S> for crate::redis::RRedisUser
where
S: Send + Sync,
{
type Rejection = AxumRejection;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Extension(manager) =
Extension::<crate::redis::RTokenRedisManager>::from_request_parts(parts, state)
.await
.map_err(|_| internal("Token manager not found"))?;
let cfg = match Extension::<TokenSourceConfig>::from_request_parts(parts, state).await {
Ok(Extension(cfg)) => cfg,
Err(_) => TokenSourceConfig::default(),
};
let cookie_header = cookie_header_string(parts);
let token = extract_token_with_config(
&cfg,
|name| {
parts
.headers
.get(name)
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
},
|name| {
cookie_header
.as_deref()
.and_then(|h| find_cookie_value(h, name))
},
)
.ok_or_else(|| unauthorized("Unauthorized"))?;
#[cfg(feature = "rbac")]
let user_info = manager
.validate_with_roles(&token)
.await
.map_err(|_| internal("Redis error"))?;
#[cfg(not(feature = "rbac"))]
let user_info = manager
.validate(&token)
.await
.map_err(|_| internal("Redis error"))?;
#[cfg(feature = "rbac")]
if let Some((user_id, roles)) = user_info {
return Ok(Self {
id: user_id,
token,
roles,
});
}
#[cfg(not(feature = "rbac"))]
if let Some(user_id) = user_info {
return Ok(Self { id: user_id, token });
}
Err(unauthorized("Invalid token"))
}
}