use std::sync::Arc;
use sa_token_adapter::context::SaRequest;
use crate::config::SaTokenConfig;
use crate::token_io;
type LoginIdValidator = Arc<dyn Fn(&str) -> bool + Send + Sync>;
pub fn match_path(path: &str, pattern: &str) -> bool {
if pattern == "/**" {
return true;
}
if let Some(prefix) = pattern.strip_suffix("/**") {
return path.starts_with(prefix);
}
if let Some(suffix) = pattern.strip_prefix("*") {
return path.ends_with(suffix);
}
if let Some(prefix) = pattern.strip_suffix("/*") {
if !path.starts_with(prefix) {
return false;
}
let rest = &path[prefix.len()..];
if rest.is_empty() || rest == "/" {
return true;
}
let rest = rest.trim_start_matches('/');
return !rest.contains('/');
}
path == pattern
}
pub fn match_any(path: &str, patterns: &[&str]) -> bool {
patterns.iter().any(|p| match_path(path, p))
}
pub fn need_auth(path: &str, include: &[&str], exclude: &[&str]) -> bool {
match_any(path, include) && !match_any(path, exclude)
}
#[derive(Clone)]
pub struct PathAuthConfig {
include: Vec<String>,
exclude: Vec<String>,
validator: Option<LoginIdValidator>,
}
impl std::fmt::Debug for PathAuthConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("PathAuthConfig { .. }")
}
}
impl PathAuthConfig {
pub fn new() -> Self {
Self {
include: Vec::new(),
exclude: Vec::new(),
validator: None,
}
}
pub fn include(mut self, patterns: Vec<String>) -> Self {
self.include = patterns;
self
}
pub fn exclude(mut self, patterns: Vec<String>) -> Self {
self.exclude = patterns;
self
}
pub fn validator<F>(mut self, f: F) -> Self
where
F: Fn(&str) -> bool + Send + Sync + 'static,
{
self.validator = Some(Arc::new(f));
self
}
pub fn check(&self, path: &str) -> bool {
let inc: Vec<&str> = self.include.iter().map(|s| s.as_str()).collect();
let exc: Vec<&str> = self.exclude.iter().map(|s| s.as_str()).collect();
need_auth(path, &inc, &exc)
}
pub fn validate_login_id(&self, login_id: &str) -> bool {
self.validator.as_ref().is_none_or(|v| v(login_id))
}
}
impl Default for PathAuthConfig {
fn default() -> Self {
Self::new()
}
}
use crate::context::{RequestAuthMeta, SaTokenContext};
use crate::{SaTokenManager, TokenValue, token::TokenInfo};
pub struct AuthResult {
pub need_auth: bool,
pub token: Option<TokenValue>,
pub token_info: Option<TokenInfo>,
pub is_valid: bool,
}
impl std::fmt::Debug for AuthResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AuthResult { .. }")
}
}
impl AuthResult {
pub fn should_reject(&self) -> bool {
self.need_auth && (!self.is_valid || self.token.is_none())
}
pub fn login_id(&self) -> Option<&str> {
self.token_info.as_ref().map(|t| t.login_id.as_ref())
}
}
pub async fn process_auth(
path: &str,
token_str: Option<String>,
config: &PathAuthConfig,
manager: &SaTokenManager,
) -> AuthResult {
let need_auth = config.check(path);
let token = token_str.map(TokenValue::new);
let (is_valid, token_info) = if let Some(ref t) = token {
let valid = manager.is_valid(t).await;
let info = if valid {
manager.get_token_info(t).await.ok()
} else {
None
};
(valid, info)
} else {
(false, None)
};
let is_valid = is_valid
&& if need_auth {
token_info
.as_ref()
.is_some_and(|info| config.validate_login_id(info.login_id.as_ref()))
} else {
true
};
AuthResult {
need_auth,
token,
token_info,
is_valid,
}
}
pub fn create_context(result: &AuthResult, auth_meta: RequestAuthMeta) -> SaTokenContext {
let mut builder = SaTokenContext::builder().auth_meta(auth_meta);
if let (Some(token), Some(info)) = (&result.token, &result.token_info) {
builder = builder
.token(token.clone())
.token_info(Arc::new(info.clone()))
.login_id(info.login_id.as_ref());
}
builder.build()
}
pub fn extract_token<R: SaRequest>(req: &R, token_name: &str) -> Option<String> {
let cfg = SaTokenConfig {
token_name: token_name.to_string(),
..SaTokenConfig::default()
};
token_io::read_token(req, &cfg)
}
pub fn extract_token_from<R: SaRequest>(req: &R, config: &SaTokenConfig) -> Option<String> {
token_io::read_token(req, config)
}
pub struct AuthFlowResult {
pub auth: AuthResult,
pub login_id: Option<String>,
pub token: Option<TokenValue>,
pub context: SaTokenContext,
}
impl std::fmt::Debug for AuthFlowResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AuthFlowResult { .. }")
}
}
impl AuthFlowResult {
pub fn should_reject(&self) -> bool {
self.auth.should_reject()
}
pub async fn run<F, R>(self, fut: F) -> R
where
F: Future<Output = R>,
{
SaTokenContext::scope(self.context, fut).await
}
}
pub async fn run_auth_flow<R: SaRequest>(
req: &R,
manager: &SaTokenManager,
path_config: Option<&PathAuthConfig>,
) -> AuthFlowResult {
let token_str = extract_token_from(req, &manager.config);
let path = req.get_path();
let auth_meta = RequestAuthMeta::from_request(req, manager.config.same_token_header.as_str());
let (auth, ctx) = match path_config {
Some(cfg) => {
let auth = process_auth(path.as_str(), token_str.clone(), cfg, manager).await;
let ctx = create_context(&auth, auth_meta);
(auth, ctx)
}
None => {
let token = token_str.map(TokenValue::new);
let (is_valid, token_info) = if let Some(ref t) = token {
let valid = manager.is_valid(t).await;
let info = if valid {
manager.get_token_info(t).await.ok()
} else {
None
};
(valid, info)
} else {
(false, None)
};
let auth = AuthResult {
need_auth: false,
token: token.clone(),
token_info,
is_valid,
};
let ctx = create_context(&auth, auth_meta);
(auth, ctx)
}
};
let login_id = auth.login_id().map(str::to_string);
let token = auth.token.clone();
AuthFlowResult {
auth,
login_id,
token,
context: ctx,
}
}