Skip to main content

sa_token_core/
manager.rs

1// Author: 金书记
2//
3//! sa-token 管理器:对外 API 门面,业务逻辑委托给 service / repository 层。
4
5use 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/// sa-token 管理器:对外 API 门面,业务逻辑委托给 service / repository 层。
25///
26/// The sa-token manager: a thin facade delegating to the service and
27/// repository layers.
28#[derive(Clone)]
29pub struct SaTokenManager {
30    /// 底层存储适配器。
31    ///
32    /// 与 `dao` 内部持有的是**同一个 `Arc`**,仅为兼容 crate 内既有的字段访问
33    /// (`manager.rs` 单测与 `nonce.rs` / `refresh.rs` 等模块直接用 `self.storage`)。
34    ///
35    /// Shares the very same `Arc` as `dao`, kept only so existing in-crate field
36    /// accesses keep compiling.
37    pub(crate) storage: Arc<dyn SaStorage>,
38    /// 对外兼容:`manager.config.token_name` 经 Deref 仍可用。
39    /// Public field kept for compatibility; `Arc` derefs to `SaTokenConfig`.
40    /// 构建期只包装一次,Clone Manager 只加引用计数,不再深拷贝配置。
41    /// Wrapped once at construction; cloning the manager is a refcount bump.
42    pub config: Arc<SaTokenConfig>,
43    /// 键构造器:A3 契约要求持有而非每次 from_config(B1-1)
44    keys: SaKeys,
45    /// 事件总线 | Event bus
46    pub(crate) event_bus: SaTokenEventBus,
47    /// 存储访问层 | Storage access layer
48    pub(crate) dao: Arc<SaTokenDao>,
49    /// Token 仓储 | Token repository
50    token_repo: Arc<TokenRepo>,
51    /// Session 仓储 | Session repository
52    session_repo: Arc<SessionRepo>,
53    /// 授权仓储(纯存储;随 dao 变化重建)| Grant repository (storage only)
54    grant_repo: Arc<GrantRepo>,
55    /// 授权服务:权限/角色/封禁回落的唯一入口(随 stp_interface / matcher / 配置重建)
56    /// Authorization service: the single entry point for grants and the ban
57    /// fallback; rebuilt when the data source, matchers or config change.
58    authz_service: Arc<AuthzService>,
59    /// 认证服务(随 online_manager 变化重建)| Auth service, rebuilt with online_manager
60    auth_service: Arc<AuthService>,
61    /// 自定义权限匹配策略;`None` 表示使用默认分段匹配器。
62    /// Custom permission matcher, `None` for the default segment matcher.
63    perm_matcher: Option<Arc<dyn PermissionMatcher>>,
64    /// 自定义角色匹配策略;`None` 表示按 `config.role_wildcard` 选择。
65    /// Custom role matcher, `None` to pick exact/segment matching per config.
66    role_matcher: Option<Arc<dyn PermissionMatcher>>,
67    /// 在线用户管理器 | Online user manager
68    online_manager: Option<Arc<OnlineManager>>,
69    /// 分布式 Session 管理器 | Distributed session manager
70    distributed_manager: Option<Arc<DistributedSessionManager>>,
71    /// 权限/角色数据源回调 | Permission/role data source callback
72    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    /// 创建管理器实例。
83    ///
84    /// 配置只包装一次进 `Arc`,此后各层共享,Clone Manager 不再深拷贝配置。
85    /// Config is wrapped once into `Arc`; cloning the manager no longer deep-copies it.
86    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    /// 重建依赖「后置注入项」的组件(修 B1-10、B2-4)。
133    ///
134    /// 授权链路一并重建:`GrantRepo` 随 `dao`,`AuthzService` 随
135    /// `stp_interface` / matcher / 配置。重建意味着新实例缓存为空,
136    /// 恰好满足「切换数据源后必须失效缓存」的要求。
137    ///
138    /// Rebuilds components that depend on post-construction injection. The
139    /// authorization chain is rebuilt too; a fresh instance starts with an
140    /// empty cache — exactly what "invalidate on data-source swap" requires.
141    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    /// 配置变更后重建 keys → dao → 仓储 → 服务整条链路。
170    /// Rebuild keys → dao → repos → services after a config change.
171    fn rebuild_config_chain(&mut self) {
172        // 配置已是 Arc;只重建依赖它的键与仓储,不再二次包装。
173        // Config is already Arc; rebuild keys/repos only.
174        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    /// 运行时替换存储键布局(主要用于测试与迁移工具)
182    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    /// 替换序列化器(如启用 fory)。
189    ///
190    /// 显式复用原 `event_bus`,避免丢弃已注册的监听器。
191    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    /// 注册权限/角色数据源 | Register the permission/role data source
201    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    /// Replace the permission matcher used by AuthzService.
208    /// 替换 AuthzService 使用的权限匹配器。
209    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    /// 替换角色匹配策略(默认按 `config.role_wildcard` 选择精确/分段匹配)。
216    /// Replaces the role matching strategy (default follows `config.role_wildcard`).
217    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    /// 注册在线用户管理器 | Register the online user manager
224    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    /// 注册分布式 Session 管理器 | Register the distributed session manager
231    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    /// Attach a Dao-backed online manager (cross-instance presence).
238    /// 挂上基于 Dao 的在线管理器(跨实例 presence)。
239    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    /// Start optional background cleanup (disabled unless `CleanupConfig.enabled`).
246    /// 启动可选后台清理(除非 `CleanupConfig.enabled` 否则不跑)。
247    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    /// 注入共享事件总线(支持多 Manager 共享 / 测试 mock)
267    ///
268    /// Injects a shared event bus (supports multi-Manager sharing / test mocking).
269    ///
270    /// # 示例 | Example
271    /// ```rust,ignore
272    /// let shared_bus = SaTokenEventBus::with_config(EventBusConfig {
273    ///     dispatch_mode: DispatchMode::Detached,
274    ///     listener_timeout: Some(Duration::from_secs(10)),
275    /// });
276    /// let mgr1 = SaTokenManager::new(storage1, config1).with_event_bus(shared_bus.clone());
277    /// let mgr2 = SaTokenManager::new(storage2, config2).with_event_bus(shared_bus.clone());
278    /// shared_bus.register(Arc::new(MyListener));
279    /// ```
280    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    // ---------- 访问器 | Accessors ----------
287
288    /// 存储键构造器(A3 契约:返回引用,避免热路径克隆)
289    #[inline]
290    pub fn keys(&self) -> &SaKeys {
291        &self.keys
292    }
293
294    /// 底层存储 | Underlying storage
295    pub fn storage(&self) -> &Arc<dyn SaStorage> {
296        &self.storage
297    }
298
299    /// 存储访问层 | Storage access layer
300    pub fn dao(&self) -> &Arc<SaTokenDao> {
301        &self.dao
302    }
303
304    /// 当前序列化器 | Current serializer
305    pub fn serializer(&self) -> &sa_token_adapter::serializer::SharedSerializer {
306        &self.config.serializer
307    }
308
309    /// 认证服务 | Authentication service
310    pub fn auth_service(&self) -> &Arc<AuthService> {
311        &self.auth_service
312    }
313
314    /// Token 仓储 | Token repository
315    pub fn token_repo(&self) -> &Arc<TokenRepo> {
316        &self.token_repo
317    }
318
319    /// Session 仓储 | Session repository
320    pub fn session_repo(&self) -> &Arc<SessionRepo> {
321        &self.session_repo
322    }
323
324    /// 授权服务:权限/角色的读写与校验入口 | Authorization service
325    pub fn authz_service(&self) -> &Arc<AuthzService> {
326        &self.authz_service
327    }
328
329    /// 授权仓储(**纯存储**,绕过数据源优先级与缓存失效)。
330    /// 请改用 [`authz_service()`](Self::authz_service)。
331    ///
332    /// The storage-only grant repository, bypassing data-source precedence and
333    /// cache invalidation. Use `authz_service()` instead.
334    #[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    /// 事件总线 | Event bus
343    pub fn event_bus(&self) -> &SaTokenEventBus {
344        &self.event_bus
345    }
346
347    /// 在线用户管理器 | Online user manager
348    pub fn online_manager(&self) -> Option<&Arc<OnlineManager>> {
349        self.online_manager.as_ref()
350    }
351
352    /// 分布式 Session 管理器 | Distributed session manager
353    pub fn distributed_manager(&self) -> Option<&Arc<DistributedSessionManager>> {
354        self.distributed_manager.as_ref()
355    }
356
357    /// 账号命名空间(crate 内部使用,A3 契约返回 AccountNs)
358    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    // ---------- 登录 / 登出 / 踢人 ----------
363
364    /// 登录:为指定账号创建 token | Log in and issue a token
365    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    /// 登录(完整可选参数)。签名保持不变以维持对外兼容,
370    /// 内部转换为 `LoginRequest` 后委托 `AuthService`。
371    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    /// 登录:使用完整 TokenInfo(SSO / 自定义 token 场景)。
400    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    /// 登出指定 token | Log out a token
422    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    /// 踢下线指定 token(标记 -5)| Kick out a token, marker `-5`
429    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    /// 顶下线指定 token(标记 -4)| Replace a token, marker `-4`
436    pub async fn replaced_by_token(&self, token: &TokenValue) -> SaTokenResult<()> {
437        self.auth_service.logout_replaced(token).await
438    }
439
440    /// 按账号登出全部 token | Log out every token of an account
441    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    /// 按账号踢下线全部 token | Kick out every token of an account
448    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    /// 读取并校验 token | Read and validate a token
453    pub async fn get_token_info(&self, token: &TokenValue) -> SaTokenResult<TokenInfo> {
454        self.auth_service.get_token_info(token).await
455    }
456
457    /// 按 login_type + login_id 读取当前映射 token
458    /// Read the mapped token for login_type + login_id
459    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    /// 列出在线 token(B1 list 原语)
475    /// List online tokens (B1 list primitive)
476    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    /// 更新 extra_data 并经 TokenRepo 落盘
486    /// Update extra_data and persist via TokenRepo
487    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    /// Set per-token idle timeout. Errors unless `dynamic_active_timeout` is on.
498    /// 设置单 token 闲置超时。未开启 `dynamic_active_timeout` 时返回 ConfigError。
499    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    /// 创建绑定 login_type 的廉价 Clone 门面
515    /// Create a cheap Clone facade bound to login_type
516    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    /// token 是否有效 | Whether the token is valid
521    pub async fn is_valid(&self, token: &TokenValue) -> bool {
522        self.auth_service.is_valid(token).await
523    }
524
525    /// 续期 token 到指定秒数 | Renew a token to an explicit lifetime
526    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    // ---------- Session 与终端 ----------
537
538    /// 读取账号 Session(默认 login_type)
539    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    /// 读取账号 Session(指定 login_type,A3 契约)
546    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    /// 保存账号 Session(修 B1-9:以 session.id 自身作为命名空间回写)
557    pub async fn save_session(&self, session: &SaSession) -> SaTokenResult<()> {
558        self.session_repo.save_session_object(session).await
559    }
560
561    /// 保存账号 Session(指定 login_type)
562    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    /// 删除账号 Session(默认 login_type)
574    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    /// 删除账号 Session(指定 login_type)
581    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    /// 获取指定账号的终端列表 | Terminal list of an account
592    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    /// 获取指定账号的 token 列表(来自终端列表)
603    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    /// 按 token 反查终端信息 | Look up terminal info by token
614    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    // ---------- 权限 / 角色(全部委托 AuthzService)----------
626
627    /// 获取权限列表(指定账号体系)| Permission list for a login type
628    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    /// 覆盖权限列表(指定账号体系)| Overwrite the permission list for a login type
639    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    /// 追加单个权限(指定账号体系,B2-35 新增)
651    /// Append one permission for a login type (added in B2-35).
652    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    /// 移除单个权限(指定账号体系,B2-35 新增)
664    /// Remove one permission for a login type (added in B2-35).
665    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    /// 清空权限(指定账号体系,B2-35 新增)
677    /// Clear permissions for a login type (added in B2-35).
678    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    /// 获取角色列表(指定账号体系)| Role list for a login type
689    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    /// 覆盖角色列表(指定账号体系)| Overwrite the role list for a login type
698    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    /// 追加单个角色(指定账号体系,B2-35 新增)
710    /// Append one role for a login type (added in B2-35).
711    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    /// 移除单个角色(指定账号体系,B2-35 新增)
723    /// Remove one role for a login type (added in B2-35).
724    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    /// 清空角色(指定账号体系,B2-35 新增)
736    /// Clear roles for a login type (added in B2-35).
737    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    // ---------- 默认账号体系的便捷包装 | Default login type convenience wrappers ----------
746
747    /// 获取权限列表 | Permission list
748    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    /// 覆盖权限列表 | Overwrite the permission list
754    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    /// 追加单个权限 | Append one permission
764    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    /// 移除单个权限 | Remove one permission
770    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    /// 清空权限 | Clear all permissions
776    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    /// 获取角色列表 | Role list
782    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    /// 覆盖角色列表 | Overwrite the role list
787    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    /// 追加单个角色 | Append one role
793    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    /// 移除单个角色 | Remove one role
799    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    /// 清空角色 | Clear all roles
805    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        // renew_threshold=-1:每次访问都续期;否则默认 300 对新 token 永不触发
888        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        // remaining ~3600 > 300 → 不应续期
924        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}