Skip to main content

sa_token_core/
safe.rs

1// Author: 金书记
2//
3//! Secondary authentication (safe window).
4//! 二级认证(安全窗口)。
5
6use std::time::Duration;
7
8use crate::error::{SaTokenError, SaTokenResult};
9use crate::manager::SaTokenManager;
10use crate::token::TokenValue;
11
12/// Default secondary-auth service id.
13/// 默认二级认证业务标识。
14pub const DEFAULT_SAFE_SERVICE: &str = "";
15
16/// 二级认证存储标记值
17pub const SAFE_AUTH_VALUE: &str = "ok";
18
19impl SaTokenManager {
20    fn safe_key(&self, token: &str, service: &str) -> String {
21        self.keys().safe(token, service)
22    }
23
24    /// 为指定 token 开启二级认证
25    pub async fn open_safe(
26        &self,
27        token: &TokenValue,
28        service: &str,
29        safe_time: i64,
30    ) -> SaTokenResult<()> {
31        if safe_time < 0 {
32            return Err(SaTokenError::ConfigError(
33                "safe_time must be >= 0".to_string(),
34            ));
35        }
36
37        let ttl = if safe_time == 0 {
38            None
39        } else {
40            Some(Duration::from_secs(safe_time as u64))
41        };
42
43        // 走 Dao 漏斗,避免绕过序列化与键契约。
44        // Go through Dao so serialization and key contracts are not bypassed.
45        self.dao
46            .set_string(
47                &self.safe_key(token.as_str(), service),
48                SAFE_AUTH_VALUE,
49                ttl,
50            )
51            .await?;
52
53        self.event_bus
54            .publish(crate::event::SaTokenEvent::open_safe(
55                token.as_str(),
56                service,
57            ))
58            .await;
59
60        Ok(())
61    }
62
63    /// 判断 token 是否已通过指定业务的二级认证
64    pub async fn is_safe(&self, token: &TokenValue, service: &str) -> SaTokenResult<bool> {
65        if token.as_str().is_empty() {
66            return Ok(false);
67        }
68
69        if !self.is_valid(token).await {
70            return Ok(false);
71        }
72
73        Ok(self
74            .dao
75            .get_string(&self.safe_key(token.as_str(), service))
76            .await?
77            .as_deref()
78            == Some(SAFE_AUTH_VALUE))
79    }
80
81    /// 校验二级认证;未通过抛出 [`SaTokenError::NotSafe`]
82    pub async fn check_safe(&self, token: &TokenValue, service: &str) -> SaTokenResult<()> {
83        if !self.is_valid(token).await {
84            return Err(SaTokenError::NotLogin);
85        }
86
87        if !self.is_safe(token, service).await? {
88            return Err(SaTokenError::NotSafe(service.to_string()));
89        }
90
91        let event = crate::event::SaTokenEvent::safe_verify(token.as_str(), service);
92        self.event_bus.publish(event).await;
93
94        Ok(())
95    }
96
97    /// 关闭二级认证
98    pub async fn close_safe(&self, token: &TokenValue, service: &str) -> SaTokenResult<()> {
99        self.dao
100            .delete(&self.safe_key(token.as_str(), service))
101            .await?;
102
103        self.event_bus
104            .publish(crate::event::SaTokenEvent::close_safe(
105                token.as_str(),
106                service,
107            ))
108            .await;
109
110        Ok(())
111    }
112
113    /// 获取二级认证剩余有效时间(秒);未认证返回 `None`
114    pub async fn get_safe_time(
115        &self,
116        token: &TokenValue,
117        service: &str,
118    ) -> SaTokenResult<Option<i64>> {
119        match self.dao.ttl(&self.safe_key(token.as_str(), service)).await {
120            Ok(Some(d)) => Ok(Some(d.as_secs() as i64)),
121            Ok(None) => Ok(None),
122            Err(e) => Err(e),
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::config::SaTokenConfig;
131    use sa_token_storage_memory::MemoryStorage;
132    use std::sync::Arc;
133
134    fn manager() -> SaTokenManager {
135        SaTokenManager::new(Arc::new(MemoryStorage::new()), SaTokenConfig::default())
136    }
137
138    #[tokio::test]
139    async fn open_check_close_safe() {
140        let mgr = manager();
141        let token = mgr.login("u1").await.unwrap();
142        assert!(!mgr.is_safe(&token, DEFAULT_SAFE_SERVICE).await.unwrap());
143        mgr.open_safe(&token, "pay", 120).await.unwrap();
144        assert!(mgr.is_safe(&token, "pay").await.unwrap());
145        mgr.check_safe(&token, "pay").await.unwrap();
146        mgr.close_safe(&token, "pay").await.unwrap();
147        assert!(!mgr.is_safe(&token, "pay").await.unwrap());
148    }
149}