1use std::sync::Arc;
7
8use sa_token_adapter::context::SaRequest;
9
10use crate::config::SaTokenConfig;
11use crate::token_io;
12
13type LoginIdValidator = Arc<dyn Fn(&str) -> bool + Send + Sync>;
14
15pub fn match_path(path: &str, pattern: &str) -> bool {
37 if pattern == "/**" {
38 return true;
39 }
40 if let Some(prefix) = pattern.strip_suffix("/**") {
41 return path.starts_with(prefix);
42 }
43 if let Some(suffix) = pattern.strip_prefix("*") {
44 return path.ends_with(suffix);
45 }
46 if let Some(prefix) = pattern.strip_suffix("/*") {
49 if !path.starts_with(prefix) {
50 return false;
51 }
52 let rest = &path[prefix.len()..];
53 if rest.is_empty() || rest == "/" {
54 return true;
55 }
56 let rest = rest.trim_start_matches('/');
57 return !rest.contains('/');
58 }
59 path == pattern
60}
61
62pub fn match_any(path: &str, patterns: &[&str]) -> bool {
65 patterns.iter().any(|p| match_path(path, p))
66}
67
68pub fn need_auth(path: &str, include: &[&str], exclude: &[&str]) -> bool {
74 match_any(path, include) && !match_any(path, exclude)
75}
76
77#[derive(Clone)]
83pub struct PathAuthConfig {
84 include: Vec<String>,
87 exclude: Vec<String>,
90 validator: Option<LoginIdValidator>,
93}
94
95impl std::fmt::Debug for PathAuthConfig {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.write_str("PathAuthConfig { .. }")
98 }
99}
100
101impl PathAuthConfig {
102 pub fn new() -> Self {
105 Self {
106 include: Vec::new(),
107 exclude: Vec::new(),
108 validator: None,
109 }
110 }
111
112 pub fn include(mut self, patterns: Vec<String>) -> Self {
115 self.include = patterns;
116 self
117 }
118
119 pub fn exclude(mut self, patterns: Vec<String>) -> Self {
122 self.exclude = patterns;
123 self
124 }
125
126 pub fn validator<F>(mut self, f: F) -> Self
129 where
130 F: Fn(&str) -> bool + Send + Sync + 'static,
131 {
132 self.validator = Some(Arc::new(f));
133 self
134 }
135
136 pub fn check(&self, path: &str) -> bool {
139 let inc: Vec<&str> = self.include.iter().map(|s| s.as_str()).collect();
140 let exc: Vec<&str> = self.exclude.iter().map(|s| s.as_str()).collect();
141 need_auth(path, &inc, &exc)
142 }
143
144 pub fn validate_login_id(&self, login_id: &str) -> bool {
147 self.validator.as_ref().is_none_or(|v| v(login_id))
148 }
149}
150
151impl Default for PathAuthConfig {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157use crate::context::{RequestAuthMeta, SaTokenContext};
158use crate::{SaTokenManager, TokenValue, token::TokenInfo};
159
160pub struct AuthResult {
163 pub need_auth: bool,
166 pub token: Option<TokenValue>,
169 pub token_info: Option<TokenInfo>,
172 pub is_valid: bool,
175}
176
177impl std::fmt::Debug for AuthResult {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 f.write_str("AuthResult { .. }")
180 }
181}
182
183impl AuthResult {
184 pub fn should_reject(&self) -> bool {
187 self.need_auth && (!self.is_valid || self.token.is_none())
188 }
189
190 pub fn login_id(&self) -> Option<&str> {
193 self.token_info.as_ref().map(|t| t.login_id.as_ref())
194 }
195}
196
197pub async fn process_auth(
210 path: &str,
211 token_str: Option<String>,
212 config: &PathAuthConfig,
213 manager: &SaTokenManager,
214) -> AuthResult {
215 let need_auth = config.check(path);
216
217 let token = token_str.map(TokenValue::new);
218
219 let (is_valid, token_info) = if let Some(ref t) = token {
220 let valid = manager.is_valid(t).await;
221 let info = if valid {
222 manager.get_token_info(t).await.ok()
223 } else {
224 None
225 };
226 (valid, info)
227 } else {
228 (false, None)
229 };
230
231 let is_valid = is_valid
232 && if need_auth {
233 token_info
234 .as_ref()
235 .is_some_and(|info| config.validate_login_id(info.login_id.as_ref()))
236 } else {
237 true
238 };
239
240 AuthResult {
241 need_auth,
242 token,
243 token_info,
244 is_valid,
245 }
246}
247
248pub fn create_context(result: &AuthResult, auth_meta: RequestAuthMeta) -> SaTokenContext {
252 let mut builder = SaTokenContext::builder().auth_meta(auth_meta);
253 if let (Some(token), Some(info)) = (&result.token, &result.token_info) {
254 builder = builder
255 .token(token.clone())
256 .token_info(Arc::new(info.clone()))
257 .login_id(info.login_id.as_ref());
258 }
259 builder.build()
260}
261
262pub fn extract_token<R: SaRequest>(req: &R, token_name: &str) -> Option<String> {
265 let cfg = SaTokenConfig {
266 token_name: token_name.to_string(),
267 ..SaTokenConfig::default()
268 };
269 token_io::read_token(req, &cfg)
270}
271
272pub fn extract_token_from<R: SaRequest>(req: &R, config: &SaTokenConfig) -> Option<String> {
275 token_io::read_token(req, config)
276}
277
278pub struct AuthFlowResult {
281 pub auth: AuthResult,
283 pub login_id: Option<String>,
285 pub token: Option<TokenValue>,
287 pub context: SaTokenContext,
289}
290
291impl std::fmt::Debug for AuthFlowResult {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 f.write_str("AuthFlowResult { .. }")
294 }
295}
296
297impl AuthFlowResult {
298 pub fn should_reject(&self) -> bool {
301 self.auth.should_reject()
302 }
303
304 pub async fn run<F, R>(self, fut: F) -> R
313 where
314 F: Future<Output = R>,
315 {
316 SaTokenContext::scope(self.context, fut).await
317 }
318}
319
320pub async fn run_auth_flow<R: SaRequest>(
326 req: &R,
327 manager: &SaTokenManager,
328 path_config: Option<&PathAuthConfig>,
329) -> AuthFlowResult {
330 let token_str = extract_token_from(req, &manager.config);
331 let path = req.get_path();
332 let auth_meta = RequestAuthMeta::from_request(req, manager.config.same_token_header.as_str());
333
334 let (auth, ctx) = match path_config {
335 Some(cfg) => {
336 let auth = process_auth(path.as_str(), token_str.clone(), cfg, manager).await;
339 let ctx = create_context(&auth, auth_meta);
340 (auth, ctx)
341 }
342 None => {
343 let token = token_str.map(TokenValue::new);
346 let (is_valid, token_info) = if let Some(ref t) = token {
347 let valid = manager.is_valid(t).await;
348 let info = if valid {
349 manager.get_token_info(t).await.ok()
350 } else {
351 None
352 };
353 (valid, info)
354 } else {
355 (false, None)
356 };
357 let auth = AuthResult {
358 need_auth: false,
359 token: token.clone(),
360 token_info,
361 is_valid,
362 };
363 let ctx = create_context(&auth, auth_meta);
364 (auth, ctx)
365 }
366 };
367
368 let login_id = auth.login_id().map(str::to_string);
369 let token = auth.token.clone();
370 AuthFlowResult {
371 auth,
372 login_id,
373 token,
374 context: ctx,
375 }
376}