1use std::sync::Arc;
6
7use chrono::{DateTime, Utc};
8use sa_token_adapter::storage::SaStorage;
9
10use crate::config::SaTokenConfig;
11use crate::dao::SaTokenDao;
12use crate::distributed::DistributedSessionManager;
13use crate::error::{SaTokenError, SaTokenResult};
14use crate::event::SaTokenEventBus;
15use crate::keys::{AccountNs, LOGIN_TYPE_DEFAULT, LoginId, SaKeyLayout, SaKeys};
16use crate::online::OnlineManager;
17use crate::permission::PermissionMatcher;
18use crate::repository::{GrantRepo, SessionRepo, TokenRepo};
19use crate::service::{AuthService, AuthzService, LoginRequest};
20use crate::session::SaSession;
21use crate::stp_interface::StpInterface;
22use crate::token::{TokenInfo, TokenValue};
23
24#[derive(Clone)]
29pub struct SaTokenManager {
30 pub(crate) storage: Arc<dyn SaStorage>,
38 pub config: Arc<SaTokenConfig>,
43 keys: SaKeys,
45 pub(crate) event_bus: SaTokenEventBus,
47 pub(crate) dao: Arc<SaTokenDao>,
49 token_repo: Arc<TokenRepo>,
51 session_repo: Arc<SessionRepo>,
53 grant_repo: Arc<GrantRepo>,
55 authz_service: Arc<AuthzService>,
59 auth_service: Arc<AuthService>,
61 perm_matcher: Option<Arc<dyn PermissionMatcher>>,
64 role_matcher: Option<Arc<dyn PermissionMatcher>>,
67 online_manager: Option<Arc<OnlineManager>>,
69 distributed_manager: Option<Arc<DistributedSessionManager>>,
71 pub(crate) stp_interface: Option<Arc<dyn StpInterface>>,
73}
74
75impl std::fmt::Debug for SaTokenManager {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.write_str("SaTokenManager { .. }")
78 }
79}
80
81impl SaTokenManager {
82 pub fn new(storage: Arc<dyn SaStorage>, config: SaTokenConfig) -> Self {
87 let config = Arc::new(config);
88 let keys = SaKeys::from_config(&config);
89 let dao = Arc::new(SaTokenDao::new(storage.clone(), config.clone()));
90 let event_bus = SaTokenEventBus::new();
91
92 let token_repo = Arc::new(TokenRepo::new(dao.clone(), config.clone()));
93 let session_repo = Arc::new(SessionRepo::new(dao.clone(), config.clone()));
94 let grant_repo = Arc::new(GrantRepo::new(dao.clone()));
95
96 let authz_service = Arc::new(AuthzService::new(
97 grant_repo.clone(),
98 &config,
99 event_bus.clone(),
100 None,
101 ));
102
103 let auth_service = Arc::new(AuthService::new(
104 dao.clone(),
105 config.clone(),
106 token_repo.clone(),
107 session_repo.clone(),
108 event_bus.clone(),
109 None,
110 None,
111 ));
112
113 Self {
114 storage,
115 config,
116 keys,
117 event_bus,
118 dao,
119 token_repo,
120 session_repo,
121 grant_repo,
122 authz_service,
123 auth_service,
124 perm_matcher: None,
125 role_matcher: None,
126 online_manager: None,
127 distributed_manager: None,
128 stp_interface: None,
129 }
130 }
131
132 fn rebuild_services(&mut self) {
142 self.grant_repo = Arc::new(GrantRepo::new(self.dao.clone()));
143
144 let mut authz = AuthzService::new(
145 self.grant_repo.clone(),
146 &self.config,
147 self.event_bus.clone(),
148 self.stp_interface.clone(),
149 );
150 if let Some(matcher) = self.perm_matcher.clone() {
151 authz = authz.with_permission_matcher(matcher);
152 }
153 if let Some(matcher) = self.role_matcher.clone() {
154 authz = authz.with_role_matcher(matcher);
155 }
156 self.authz_service = Arc::new(authz);
157
158 self.auth_service = Arc::new(AuthService::new(
159 self.dao.clone(),
160 self.config.clone(),
161 self.token_repo.clone(),
162 self.session_repo.clone(),
163 self.event_bus.clone(),
164 self.online_manager.clone(),
165 self.distributed_manager.clone(),
166 ));
167 }
168
169 fn rebuild_config_chain(&mut self) {
172 self.keys = SaKeys::from_config(&self.config);
175 self.dao = Arc::new(SaTokenDao::new(self.storage.clone(), self.config.clone()));
176 self.token_repo = Arc::new(TokenRepo::new(self.dao.clone(), self.config.clone()));
177 self.session_repo = Arc::new(SessionRepo::new(self.dao.clone(), self.config.clone()));
178 self.rebuild_services();
179 }
180
181 pub fn with_key_layout(mut self, layout: SaKeyLayout) -> Self {
183 Arc::make_mut(&mut self.config).key_layout = layout;
184 self.rebuild_config_chain();
185 self
186 }
187
188 pub fn with_serializer(
192 mut self,
193 serializer: sa_token_adapter::serializer::SharedSerializer,
194 ) -> Self {
195 Arc::make_mut(&mut self.config).serializer = serializer;
196 self.rebuild_config_chain();
197 self
198 }
199
200 pub fn with_stp_interface(mut self, iface: Arc<dyn StpInterface>) -> Self {
202 self.stp_interface = Some(iface);
203 self.rebuild_services();
204 self
205 }
206
207 pub fn with_permission_matcher(mut self, matcher: Arc<dyn PermissionMatcher>) -> Self {
210 self.perm_matcher = Some(matcher);
211 self.rebuild_services();
212 self
213 }
214
215 pub fn with_role_matcher(mut self, matcher: Arc<dyn PermissionMatcher>) -> Self {
218 self.role_matcher = Some(matcher);
219 self.rebuild_services();
220 self
221 }
222
223 pub fn with_online_manager(mut self, manager: Arc<OnlineManager>) -> Self {
225 self.online_manager = Some(manager);
226 self.rebuild_services();
227 self
228 }
229
230 pub fn with_distributed_manager(mut self, manager: Arc<DistributedSessionManager>) -> Self {
232 self.distributed_manager = Some(manager);
233 self.rebuild_services();
234 self
235 }
236
237 pub fn with_distributed_online(mut self) -> Self {
240 self.online_manager = Some(Arc::new(OnlineManager::distributed(self.dao.clone())));
241 self.rebuild_services();
242 self
243 }
244
245 pub fn start_background_cleanup(
248 &self,
249 config: crate::cleanup::CleanupConfig,
250 ) -> crate::cleanup::BackgroundCleanupTask {
251 let nonce = Arc::new(crate::nonce::NonceManager::from_dao(
252 self.dao.clone(),
253 if self.config.nonce_timeout > 0 {
254 self.config.nonce_timeout
255 } else {
256 60
257 },
258 ));
259 crate::cleanup::BackgroundCleanupTask::spawn(
260 config,
261 Some(nonce),
262 self.online_manager.clone(),
263 )
264 }
265
266 pub fn with_event_bus(mut self, event_bus: SaTokenEventBus) -> Self {
281 self.event_bus = event_bus.clone();
282 self.rebuild_services();
283 self
284 }
285
286 #[inline]
290 pub fn keys(&self) -> &SaKeys {
291 &self.keys
292 }
293
294 pub fn storage(&self) -> &Arc<dyn SaStorage> {
296 &self.storage
297 }
298
299 pub fn dao(&self) -> &Arc<SaTokenDao> {
301 &self.dao
302 }
303
304 pub fn serializer(&self) -> &sa_token_adapter::serializer::SharedSerializer {
306 &self.config.serializer
307 }
308
309 pub fn auth_service(&self) -> &Arc<AuthService> {
311 &self.auth_service
312 }
313
314 pub fn token_repo(&self) -> &Arc<TokenRepo> {
316 &self.token_repo
317 }
318
319 pub fn session_repo(&self) -> &Arc<SessionRepo> {
321 &self.session_repo
322 }
323
324 pub fn authz_service(&self) -> &Arc<AuthzService> {
326 &self.authz_service
327 }
328
329 #[deprecated(
335 since = "0.2.0",
336 note = "Use SaTokenManager::authz_service() so cache invalidation and StpInterface precedence are honoured"
337 )]
338 pub fn grant_repo(&self) -> &Arc<GrantRepo> {
339 &self.grant_repo
340 }
341
342 pub fn event_bus(&self) -> &SaTokenEventBus {
344 &self.event_bus
345 }
346
347 pub fn online_manager(&self) -> Option<&Arc<OnlineManager>> {
349 self.online_manager.as_ref()
350 }
351
352 pub fn distributed_manager(&self) -> Option<&Arc<DistributedSessionManager>> {
354 self.distributed_manager.as_ref()
355 }
356
357 pub(crate) fn account_ns(&self, login_type: &str, login_id: &str) -> AccountNs {
359 SaKeys::account_ns(login_type, &LoginId::new(login_id))
360 }
361
362 pub async fn login(&self, login_id: impl Into<String>) -> SaTokenResult<TokenValue> {
366 self.auth_service.login(LoginRequest::new(login_id)).await
367 }
368
369 pub async fn login_with_options(
372 &self,
373 login_id: impl Into<String>,
374 login_type: Option<String>,
375 device: Option<String>,
376 extra_data: Option<serde_json::Value>,
377 nonce: Option<String>,
378 expire_time: Option<DateTime<Utc>>,
379 ) -> SaTokenResult<TokenValue> {
380 let mut req = LoginRequest::new(login_id);
381 if let Some(lt) = login_type {
382 req = req.login_type(lt);
383 }
384 if let Some(d) = device {
385 req = req.device(d);
386 }
387 if let Some(e) = extra_data {
388 req = req.extra_data(e);
389 }
390 if let Some(n) = nonce {
391 req = req.nonce(n);
392 }
393 if let Some(t) = expire_time {
394 req = req.expire_time(t);
395 }
396 self.auth_service.login(req).await
397 }
398
399 pub async fn login_with_token_info(&self, token_info: TokenInfo) -> SaTokenResult<TokenValue> {
401 let mut req = LoginRequest::new(token_info.login_id.as_ref())
402 .login_type(token_info.login_type.as_ref());
403 if let Some(d) = token_info.device.clone() {
404 req = req.device(d);
405 }
406 if let Some(e) = token_info.extra_data.clone() {
407 req = req.extra_data(e);
408 }
409 if let Some(n) = token_info.nonce.clone() {
410 req = req.nonce(n);
411 }
412 if let Some(t) = token_info.expire_time {
413 req = req.expire_time(t);
414 }
415 if !token_info.token.as_str().is_empty() {
416 req = req.preset_token(token_info.token.as_str());
417 }
418 self.auth_service.login(req).await
419 }
420
421 pub async fn logout(&self, token: &TokenValue) -> SaTokenResult<()> {
423 self.auth_service
424 .logout(token, self.config.is_logout_keep_token_session)
425 .await
426 }
427
428 pub async fn kick_out_by_token(&self, token: &TokenValue) -> SaTokenResult<()> {
430 self.auth_service
431 .kick_out_by_token(token, self.config.is_logout_keep_token_session)
432 .await
433 }
434
435 pub async fn replaced_by_token(&self, token: &TokenValue) -> SaTokenResult<()> {
437 self.auth_service.logout_replaced(token).await
438 }
439
440 pub async fn logout_by_login_id(&self, login_type: &str, login_id: &str) -> SaTokenResult<()> {
442 self.auth_service
443 .logout_by_login_id(login_type, login_id)
444 .await
445 }
446
447 pub async fn kick_out(&self, login_type: &str, login_id: &str) -> SaTokenResult<()> {
449 self.auth_service.kick_out(login_type, login_id).await
450 }
451
452 pub async fn get_token_info(&self, token: &TokenValue) -> SaTokenResult<TokenInfo> {
454 self.auth_service.get_token_info(token).await
455 }
456
457 pub async fn get_token_by_login_id(
460 &self,
461 login_type: &str,
462 login_id: &str,
463 ) -> SaTokenResult<TokenValue> {
464 match self
465 .token_repo()
466 .get_login_mapping(login_type, login_id)
467 .await?
468 {
469 Some(token_str) => Ok(TokenValue::new(token_str)),
470 None => Err(SaTokenError::NotLogin),
471 }
472 }
473
474 pub async fn get_all_tokens_by_login_id(
477 &self,
478 login_type: &str,
479 login_id: &str,
480 ) -> SaTokenResult<Vec<TokenValue>> {
481 let tokens = self.token_repo().list_tokens(login_type, login_id).await?;
482 Ok(tokens.into_iter().map(TokenValue::new).collect())
483 }
484
485 pub async fn update_extra_data(
488 &self,
489 token: &TokenValue,
490 extra_data: serde_json::Value,
491 ) -> SaTokenResult<()> {
492 let mut token_info = self.get_token_info(token).await?;
493 token_info.extra_data = Some(extra_data);
494 self.token_repo().save_token_info(&token_info).await
495 }
496
497 pub async fn update_active_timeout(
500 &self,
501 token: &TokenValue,
502 seconds: i64,
503 ) -> SaTokenResult<()> {
504 if !self.config.dynamic_active_timeout {
505 return Err(SaTokenError::ConfigError(
506 "dynamic_active_timeout is disabled".into(),
507 ));
508 }
509 let mut info = self.get_token_info(token).await?;
510 info.active_timeout_override = Some(seconds);
511 self.token_repo().save_token_info(&info).await
512 }
513
514 pub fn stp_logic(&self, login_type: &str) -> crate::stp_logic::SaLogic {
517 crate::stp_logic::SaLogic::new(login_type, self.clone())
518 }
519
520 pub async fn is_valid(&self, token: &TokenValue) -> bool {
522 self.auth_service.is_valid(token).await
523 }
524
525 pub async fn renew_timeout(
527 &self,
528 token: &TokenValue,
529 timeout_seconds: i64,
530 ) -> SaTokenResult<()> {
531 self.auth_service
532 .renew_timeout(token, timeout_seconds)
533 .await
534 }
535
536 pub async fn get_session(&self, login_id: &str) -> SaTokenResult<SaSession> {
540 self.session_repo
541 .get_account_session(LOGIN_TYPE_DEFAULT, login_id)
542 .await
543 }
544
545 pub async fn get_session_with_type(
547 &self,
548 login_type: &str,
549 login_id: &str,
550 ) -> SaTokenResult<SaSession> {
551 self.session_repo
552 .get_account_session(login_type, login_id)
553 .await
554 }
555
556 pub async fn save_session(&self, session: &SaSession) -> SaTokenResult<()> {
558 self.session_repo.save_session_object(session).await
559 }
560
561 pub async fn save_session_with_type(
563 &self,
564 login_type: &str,
565 login_id: &str,
566 session: &SaSession,
567 ) -> SaTokenResult<()> {
568 self.session_repo
569 .save_account_session(login_type, login_id, session)
570 .await
571 }
572
573 pub async fn delete_session(&self, login_id: &str) -> SaTokenResult<()> {
575 self.session_repo
576 .delete_account_session(LOGIN_TYPE_DEFAULT, login_id)
577 .await
578 }
579
580 pub async fn delete_session_with_type(
582 &self,
583 login_type: &str,
584 login_id: &str,
585 ) -> SaTokenResult<()> {
586 self.session_repo
587 .delete_account_session(login_type, login_id)
588 .await
589 }
590
591 pub async fn get_terminal_list(
593 &self,
594 login_type: &str,
595 login_id: &str,
596 device_type: Option<&str>,
597 ) -> SaTokenResult<Vec<crate::session::SaTerminalInfo>> {
598 let ns = self.account_ns(login_type, login_id);
599 self.session_repo.get_terminal_list(&ns, device_type).await
600 }
601
602 pub async fn get_token_value_list_by_login_id(
604 &self,
605 login_type: &str,
606 login_id: &str,
607 device_type: Option<&str>,
608 ) -> SaTokenResult<Vec<String>> {
609 let ns = self.account_ns(login_type, login_id);
610 self.session_repo.get_token_list(&ns, device_type).await
611 }
612
613 pub async fn get_terminal_info_by_token(
615 &self,
616 token: &TokenValue,
617 ) -> SaTokenResult<Option<crate::session::SaTerminalInfo>> {
618 let Ok(info) = self.get_token_info(token).await else {
619 return Ok(None);
620 };
621 let ns = self.account_ns(&info.login_type, &info.login_id);
622 self.session_repo.get_terminal(&ns, token.as_str()).await
623 }
624
625 pub async fn get_permissions_with_type(
629 &self,
630 login_type: &str,
631 login_id: &str,
632 ) -> SaTokenResult<Vec<String>> {
633 self.authz_service
634 .get_permissions(login_type, login_id)
635 .await
636 }
637
638 pub async fn set_permissions_with_type(
640 &self,
641 login_type: &str,
642 login_id: &str,
643 permissions: Vec<String>,
644 ) -> SaTokenResult<()> {
645 self.authz_service
646 .set_permissions(login_type, login_id, &permissions)
647 .await
648 }
649
650 pub async fn add_permission_with_type(
653 &self,
654 login_type: &str,
655 login_id: &str,
656 permission: String,
657 ) -> SaTokenResult<()> {
658 self.authz_service
659 .add_permission(login_type, login_id, permission)
660 .await
661 }
662
663 pub async fn remove_permission_with_type(
666 &self,
667 login_type: &str,
668 login_id: &str,
669 permission: &str,
670 ) -> SaTokenResult<()> {
671 self.authz_service
672 .remove_permission(login_type, login_id, permission)
673 .await
674 }
675
676 pub async fn clear_permissions_with_type(
679 &self,
680 login_type: &str,
681 login_id: &str,
682 ) -> SaTokenResult<()> {
683 self.authz_service
684 .clear_permissions(login_type, login_id)
685 .await
686 }
687
688 pub async fn get_roles_with_type(
690 &self,
691 login_type: &str,
692 login_id: &str,
693 ) -> SaTokenResult<Vec<String>> {
694 self.authz_service.get_roles(login_type, login_id).await
695 }
696
697 pub async fn set_roles_with_type(
699 &self,
700 login_type: &str,
701 login_id: &str,
702 roles: Vec<String>,
703 ) -> SaTokenResult<()> {
704 self.authz_service
705 .set_roles(login_type, login_id, &roles)
706 .await
707 }
708
709 pub async fn add_role_with_type(
712 &self,
713 login_type: &str,
714 login_id: &str,
715 role: String,
716 ) -> SaTokenResult<()> {
717 self.authz_service
718 .add_role(login_type, login_id, role)
719 .await
720 }
721
722 pub async fn remove_role_with_type(
725 &self,
726 login_type: &str,
727 login_id: &str,
728 role: &str,
729 ) -> SaTokenResult<()> {
730 self.authz_service
731 .remove_role(login_type, login_id, role)
732 .await
733 }
734
735 pub async fn clear_roles_with_type(
738 &self,
739 login_type: &str,
740 login_id: &str,
741 ) -> SaTokenResult<()> {
742 self.authz_service.clear_roles(login_type, login_id).await
743 }
744
745 pub async fn get_permissions(&self, login_id: &str) -> SaTokenResult<Vec<String>> {
749 self.get_permissions_with_type(LOGIN_TYPE_DEFAULT, login_id)
750 .await
751 }
752
753 pub async fn set_permissions(
755 &self,
756 login_id: &str,
757 permissions: Vec<String>,
758 ) -> SaTokenResult<()> {
759 self.set_permissions_with_type(LOGIN_TYPE_DEFAULT, login_id, permissions)
760 .await
761 }
762
763 pub async fn add_permission(&self, login_id: &str, permission: String) -> SaTokenResult<()> {
765 self.add_permission_with_type(LOGIN_TYPE_DEFAULT, login_id, permission)
766 .await
767 }
768
769 pub async fn remove_permission(&self, login_id: &str, permission: &str) -> SaTokenResult<()> {
771 self.remove_permission_with_type(LOGIN_TYPE_DEFAULT, login_id, permission)
772 .await
773 }
774
775 pub async fn clear_permissions(&self, login_id: &str) -> SaTokenResult<()> {
777 self.clear_permissions_with_type(LOGIN_TYPE_DEFAULT, login_id)
778 .await
779 }
780
781 pub async fn get_roles(&self, login_id: &str) -> SaTokenResult<Vec<String>> {
783 self.get_roles_with_type(LOGIN_TYPE_DEFAULT, login_id).await
784 }
785
786 pub async fn set_roles(&self, login_id: &str, roles: Vec<String>) -> SaTokenResult<()> {
788 self.set_roles_with_type(LOGIN_TYPE_DEFAULT, login_id, roles)
789 .await
790 }
791
792 pub async fn add_role(&self, login_id: &str, role: String) -> SaTokenResult<()> {
794 self.add_role_with_type(LOGIN_TYPE_DEFAULT, login_id, role)
795 .await
796 }
797
798 pub async fn remove_role(&self, login_id: &str, role: &str) -> SaTokenResult<()> {
800 self.remove_role_with_type(LOGIN_TYPE_DEFAULT, login_id, role)
801 .await
802 }
803
804 pub async fn clear_roles(&self, login_id: &str) -> SaTokenResult<()> {
806 self.clear_roles_with_type(LOGIN_TYPE_DEFAULT, login_id)
807 .await
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814 use crate::config::{LogoutMode, TokenStyle};
815 use crate::error::SaTokenError;
816 use chrono::{Duration, Utc};
817 use sa_token_storage_memory::MemoryStorage;
818
819 fn make_manager(is_concurrent: bool, auto_renew: bool, active_timeout: i64) -> SaTokenManager {
820 let config = SaTokenConfig {
821 timeout: 3600,
822 token_style: TokenStyle::Uuid,
823 is_concurrent,
824 auto_renew,
825 active_timeout,
826 ..Default::default()
827 };
828 SaTokenManager::new(Arc::new(MemoryStorage::new()), config)
829 }
830
831 #[tokio::test]
832 async fn test_non_concurrent_login_invalidates_previous_token() {
833 let mgr = make_manager(false, false, -1);
834 let t1 = mgr.login("user_1").await.unwrap();
835 assert!(mgr.is_valid(&t1).await);
836 let t2 = mgr.login("user_1").await.unwrap();
837 assert!(!mgr.is_valid(&t1).await);
838 assert!(mgr.is_valid(&t2).await);
839 }
840
841 #[tokio::test]
842 async fn test_logout_clears_login_token_mapping() {
843 let mgr = make_manager(true, false, -1);
844 let token = mgr.login("user_1").await.unwrap();
845 let map_key = mgr.keys().login_token("default", "user_1");
846 assert!(mgr.storage.get(&map_key).await.unwrap().is_some());
847 mgr.logout(&token).await.unwrap();
848 assert!(mgr.storage.get(&map_key).await.unwrap().is_none());
849 }
850
851 #[tokio::test]
852 async fn test_concurrent_login_appends_token_index() {
853 let mgr = make_manager(true, false, -1);
854 let t1 = mgr.login("user_1").await.unwrap();
855 let t2 = mgr.login("user_1").await.unwrap();
856 let list = mgr
857 .token_repo()
858 .list_tokens("default", "user_1")
859 .await
860 .unwrap();
861 assert_eq!(list.len(), 2);
862 assert!(list.contains(&t1.as_str().to_string()));
863 assert!(list.contains(&t2.as_str().to_string()));
864 }
865
866 #[tokio::test]
867 async fn test_active_timeout_freeze_returns_inactive() {
868 let mgr = make_manager(true, false, 1);
869 let token = mgr.login("user_1").await.unwrap();
870 let key = mgr.keys().token_info(token.as_str());
871 let mut info = mgr.get_token_info(&token).await.unwrap();
872 info.last_active_time = Utc::now() - Duration::seconds(10);
873 mgr.storage
874 .set(
875 &key,
876 &mgr.config.encode(&info).unwrap(),
877 mgr.config.timeout_duration(),
878 )
879 .await
880 .unwrap();
881 let result = mgr.get_token_info(&token).await;
882 assert!(matches!(result, Err(SaTokenError::TokenInactive)));
883 }
884
885 #[tokio::test]
886 async fn test_auto_renew_updates_last_active_time() {
887 let config = SaTokenConfig {
889 timeout: 3600,
890 token_style: TokenStyle::Uuid,
891 is_concurrent: true,
892 auto_renew: true,
893 active_timeout: 3600,
894 renew_threshold: -1,
895 ..Default::default()
896 };
897 let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
898 let token = mgr.login("user_1").await.unwrap();
899 let before = mgr.get_token_info(&token).await.unwrap().last_active_time;
900 tokio::time::sleep(std::time::Duration::from_millis(20)).await;
901 let after_info = mgr.get_token_info(&token).await.unwrap();
902 assert!(
903 after_info.last_active_time > before,
904 "auto_renew must advance last_active_time"
905 );
906 }
907
908 #[tokio::test]
909 async fn test_auto_renew_skipped_when_remaining_above_threshold() {
910 let config = SaTokenConfig {
911 timeout: 3600,
912 auto_renew: true,
913 renew_threshold: 300,
914 active_timeout: -1,
915 token_style: TokenStyle::Uuid,
916 ..Default::default()
917 };
918 let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
919 let token = mgr.login("user_skip").await.unwrap();
920 let before = mgr.get_token_info(&token).await.unwrap().last_active_time;
921 tokio::time::sleep(std::time::Duration::from_millis(20)).await;
922 let after = mgr.get_token_info(&token).await.unwrap().last_active_time;
923 assert_eq!(after, before);
925 }
926
927 #[tokio::test]
928 async fn test_login_with_nonce_when_enabled() {
929 let config = SaTokenConfig {
930 enable_nonce: true,
931 nonce_timeout: 60,
932 auto_renew: false,
933 ..Default::default()
934 };
935 let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
936 let nonce_mgr = crate::nonce::NonceManager::from_dao(mgr.dao().clone(), 60);
937 let nonce = nonce_mgr.generate();
938 let token = mgr
939 .login_with_options("user_1", None, None, None, Some(nonce.clone()), None)
940 .await
941 .unwrap();
942 assert!(mgr.is_valid(&token).await);
943 let result = mgr
944 .login_with_options("user_1", None, None, None, Some(nonce), None)
945 .await;
946 assert!(matches!(result, Err(SaTokenError::NonceAlreadyUsed)));
947 }
948
949 #[tokio::test]
950 async fn test_kickout_token_returns_kicked_out() {
951 let mgr = make_manager(true, false, -1);
952 let token = mgr.login("user_kick").await.unwrap();
953 mgr.kick_out_by_token(&token).await.unwrap();
954 let err = mgr.get_token_info(&token).await.unwrap_err();
955 assert!(matches!(err, SaTokenError::AccountKickedOut));
956 }
957
958 #[tokio::test]
959 async fn test_replaced_token_returns_replaced() {
960 let mgr = make_manager(false, false, -1);
961 let t1 = mgr.login("user_rep").await.unwrap();
962 let _t2 = mgr.login("user_rep").await.unwrap();
963 let err = mgr.get_token_info(&t1).await.unwrap_err();
964 assert!(matches!(err, SaTokenError::AccountReplaced));
965 }
966
967 #[tokio::test]
968 async fn test_is_share_reuses_token() {
969 let config = SaTokenConfig {
970 is_share: true,
971 is_concurrent: true,
972 ..Default::default()
973 };
974 let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
975 let t1 = mgr.login("user_share").await.unwrap();
976 let t2 = mgr.login("user_share").await.unwrap();
977 assert_eq!(t1.as_str(), t2.as_str());
978 }
979
980 #[tokio::test]
981 async fn test_max_login_count_overflow_kickout() {
982 let config = SaTokenConfig {
983 is_concurrent: true,
984 max_login_count: 2,
985 overflow_logout_mode: LogoutMode::KickOut,
986 ..Default::default()
987 };
988 let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
989 let t1 = mgr.login("user_max").await.unwrap();
990 let _t2 = mgr.login("user_max").await.unwrap();
991 let t3 = mgr.login("user_max").await.unwrap();
992 assert!(matches!(
993 mgr.get_token_info(&t1).await,
994 Err(SaTokenError::AccountKickedOut)
995 ));
996 assert!(mgr.is_valid(&t3).await);
997 }
998
999 #[test]
1000 fn test_account_ns_default_unchanged() {
1001 let mgr = make_manager(true, false, -1);
1002 assert_eq!(mgr.account_ns("default", "u1").as_str(), "u1");
1003 assert_eq!(mgr.account_ns("login", "u1").as_str(), "u1");
1004 assert_eq!(mgr.account_ns("", "u1").as_str(), "u1");
1005 assert_eq!(mgr.account_ns("admin", "u1").as_str(), "admin:u1");
1006 }
1007
1008 #[tokio::test]
1009 async fn test_login_writes_terminal_and_logout_removes() {
1010 let mgr = make_manager(true, false, -1);
1011 let token = mgr
1012 .login_with_options("u1", None, Some("PC".to_string()), None, None, None)
1013 .await
1014 .unwrap();
1015 let terminals = mgr.get_terminal_list("default", "u1", None).await.unwrap();
1016 assert_eq!(terminals.len(), 1);
1017 assert_eq!(terminals[0].token_value, token.as_str());
1018 assert_eq!(terminals[0].device_type, "PC");
1019 assert_eq!(terminals[0].index, 1);
1020
1021 mgr.logout(&token).await.unwrap();
1022 let terminals = mgr.get_terminal_list("default", "u1", None).await.unwrap();
1023 assert!(terminals.is_empty());
1024 }
1025
1026 #[tokio::test]
1027 async fn test_terminal_filter_by_device_type() {
1028 let mgr = make_manager(true, false, -1);
1029 mgr.login_with_options("u1", None, Some("PC".to_string()), None, None, None)
1030 .await
1031 .unwrap();
1032 mgr.login_with_options("u1", None, Some("APP".to_string()), None, None, None)
1033 .await
1034 .unwrap();
1035 assert_eq!(
1036 mgr.get_terminal_list("default", "u1", Some("PC"))
1037 .await
1038 .unwrap()
1039 .len(),
1040 1
1041 );
1042 assert_eq!(
1043 mgr.get_token_value_list_by_login_id("default", "u1", None)
1044 .await
1045 .unwrap()
1046 .len(),
1047 2
1048 );
1049 }
1050
1051 #[tokio::test]
1052 async fn test_permissions_isolated_by_login_type() {
1053 let mgr = make_manager(true, false, -1);
1054 mgr.set_permissions_with_type("admin", "u1", vec!["a:read".to_string()])
1055 .await
1056 .unwrap();
1057 mgr.set_permissions_with_type("user", "u1", vec!["u:read".to_string()])
1058 .await
1059 .unwrap();
1060 let admin_perms = mgr.get_permissions_with_type("admin", "u1").await.unwrap();
1061 let user_perms = mgr.get_permissions_with_type("user", "u1").await.unwrap();
1062 assert_eq!(admin_perms, vec!["a:read".to_string()]);
1063 assert_eq!(user_perms, vec!["u:read".to_string()]);
1064 }
1065}