Skip to main content

openlark_client/types/
auth.rs

1//! 认证相关类型定义
2//!
3//! 包含应用认证、用户认证相关的请求和响应数据结构。
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8/// 应用访问令牌响应
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AccessToken {
11    /// 访问令牌
12    pub access_token: String,
13    /// 令牌类型
14    pub token_type: String,
15    /// 过期时间(秒)
16    pub expires_in: u64,
17    /// 获取令牌的时间戳
18    pub created_at: DateTime<Utc>,
19}
20
21impl AccessToken {
22    /// 创建新的访问令牌
23    pub fn new(access_token: String, token_type: String, expires_in: u64) -> Self {
24        Self {
25            access_token,
26            token_type,
27            expires_in,
28            created_at: Utc::now(),
29        }
30    }
31
32    /// 检查令牌是否过期
33    pub fn is_expired(&self) -> bool {
34        let elapsed = Utc::now().signed_duration_since(self.created_at);
35        elapsed.num_seconds() >= (self.expires_in as i64 - 60) // 提前1分钟过期
36    }
37
38    /// 获取剩余有效时间(秒)
39    pub fn remaining_seconds(&self) -> i64 {
40        let elapsed = Utc::now().signed_duration_since(self.created_at);
41        (self.expires_in as i64) - elapsed.num_seconds()
42    }
43}
44
45/// 用户访问令牌响应
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct UserAccessToken {
48    /// 访问令牌
49    pub access_token: String,
50    /// 刷新令牌
51    pub refresh_token: String,
52    /// 令牌类型
53    pub token_type: String,
54    /// 访问令牌过期时间(秒)
55    pub expires_in: u64,
56    /// 刷新令牌过期时间(秒)
57    pub refresh_expires_in: u64,
58    /// 令牌作用域
59    pub scope: String,
60    /// 获取令牌的时间戳
61    pub created_at: DateTime<Utc>,
62}
63
64impl UserAccessToken {
65    /// 创建新的用户访问令牌
66    pub fn new(
67        access_token: String,
68        refresh_token: String,
69        token_type: String,
70        expires_in: u64,
71        refresh_expires_in: u64,
72        scope: String,
73    ) -> Self {
74        Self {
75            access_token,
76            refresh_token,
77            token_type,
78            expires_in,
79            refresh_expires_in,
80            scope,
81            created_at: Utc::now(),
82        }
83    }
84
85    /// 检查访问令牌是否过期
86    pub fn is_access_token_expired(&self) -> bool {
87        let elapsed = Utc::now().signed_duration_since(self.created_at);
88        elapsed.num_seconds() >= (self.expires_in as i64 - 60) // 提前1分钟过期
89    }
90
91    /// 检查刷新令牌是否过期
92    pub fn is_refresh_token_expired(&self) -> bool {
93        let elapsed = Utc::now().signed_duration_since(self.created_at);
94        elapsed.num_seconds() >= (self.refresh_expires_in as i64 - 60)
95    }
96
97    /// 获取访问令牌剩余有效时间(秒)
98    pub fn access_token_remaining_seconds(&self) -> i64 {
99        let elapsed = Utc::now().signed_duration_since(self.created_at);
100        (self.expires_in as i64) - elapsed.num_seconds()
101    }
102
103    /// 获取刷新令牌剩余有效时间(秒)
104    pub fn refresh_token_remaining_seconds(&self) -> i64 {
105        let elapsed = Utc::now().signed_duration_since(self.created_at);
106        (self.refresh_expires_in as i64) - elapsed.num_seconds()
107    }
108}
109
110/// 令牌信息
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct TokenInfo {
113    /// 令牌内容
114    pub content: serde_json::Value,
115    /// 验证时间戳
116    pub verified_at: DateTime<Utc>,
117}
118
119impl TokenInfo {
120    /// 创建新的令牌信息
121    pub fn new(content: serde_json::Value) -> Self {
122        Self {
123            content,
124            verified_at: Utc::now(),
125        }
126    }
127}
128
129/// 用户信息
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct UserInfo {
132    /// 用户ID
133    pub user_id: String,
134    /// 用户名
135    pub name: String,
136    /// 邮箱
137    pub email: Option<String>,
138    /// 手机号
139    pub mobile: Option<String>,
140    /// 头像URL
141    pub avatar: Option<String>,
142    /// 部门信息
143    pub department_ids: Option<Vec<String>>,
144    /// 获取信息的时间戳
145    pub fetched_at: DateTime<Utc>,
146}
147
148impl UserInfo {
149    /// 创建新的用户信息
150    pub fn new(
151        user_id: String,
152        name: String,
153        email: Option<String>,
154        mobile: Option<String>,
155        avatar: Option<String>,
156        department_ids: Option<Vec<String>>,
157    ) -> Self {
158        Self {
159            user_id,
160            name,
161            email,
162            mobile,
163            avatar,
164            department_ids,
165            fetched_at: Utc::now(),
166        }
167    }
168}
169
170#[cfg(test)]
171#[allow(unused_imports)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_access_token_creation() {
177        let token = AccessToken::new(
178            "test_access_token".to_string(),
179            "Bearer".to_string(),
180            7200, // 2小时
181        );
182
183        assert_eq!(token.access_token, "test_access_token");
184        assert_eq!(token.token_type, "Bearer");
185        assert_eq!(token.expires_in, 7200);
186        assert!(!token.is_expired()); // 新创建的令牌不应该过期
187        assert!(token.remaining_seconds() > 7000); // 应该接近7200秒
188    }
189
190    #[test]
191    fn test_access_token_expiry() {
192        // 通过回溯 created_at 构造已过期的令牌,避免 sleep 导致测试变慢/不稳定
193        let token = AccessToken {
194            access_token: "expired_token".to_string(),
195            token_type: "Bearer".to_string(),
196            expires_in: 120,                                        // 2分钟
197            created_at: Utc::now() - chrono::Duration::seconds(61), // 预留 60s 缓冲
198        };
199
200        assert!(token.is_expired());
201        // 注意:is_expired() 采用“提前 60 秒过期”的策略,因此 remaining_seconds 可能仍为正数。
202        assert!(token.remaining_seconds() <= 60);
203    }
204
205    #[test]
206    fn test_access_token_remaining_seconds() {
207        let token = AccessToken::new(
208            "test_token".to_string(),
209            "Bearer".to_string(),
210            3600, // 1小时
211        );
212
213        let remaining = token.remaining_seconds();
214        assert!(remaining > 3500); // 应该接近3600秒
215        assert!(remaining <= 3600); // 不应该超过3600秒
216    }
217
218    #[test]
219    fn test_user_access_token_creation() {
220        let user_token = UserAccessToken::new(
221            "user_access_token".to_string(),
222            "refresh_token".to_string(),
223            "Bearer".to_string(),
224            7200,    // 访问令牌2小时
225            2592000, // 刷新令牌30天
226            "scope1 scope2".to_string(),
227        );
228
229        assert_eq!(user_token.access_token, "user_access_token");
230        assert_eq!(user_token.refresh_token, "refresh_token");
231        assert_eq!(user_token.token_type, "Bearer");
232        assert_eq!(user_token.expires_in, 7200);
233        assert_eq!(user_token.refresh_expires_in, 2592000);
234        assert_eq!(user_token.scope, "scope1 scope2");
235        assert!(!user_token.is_access_token_expired());
236        assert!(!user_token.is_refresh_token_expired());
237    }
238
239    #[test]
240    fn test_user_access_token_expiry() {
241        // 通过回溯 created_at 构造已过期的令牌,避免 sleep 导致测试变慢/不稳定
242        let user_token = UserAccessToken {
243            access_token: "expired_user_token".to_string(),
244            refresh_token: "refresh_token".to_string(),
245            token_type: "Bearer".to_string(),
246            expires_in: 120,         // 2分钟
247            refresh_expires_in: 180, // 3分钟
248            scope: "test_scope".to_string(),
249            created_at: Utc::now() - chrono::Duration::seconds(121),
250        };
251
252        assert!(user_token.is_access_token_expired());
253        assert!(user_token.is_refresh_token_expired());
254    }
255
256    #[test]
257    fn test_user_access_token_remaining_seconds() {
258        let user_token = UserAccessToken::new(
259            "test_user_token".to_string(),
260            "refresh_token".to_string(),
261            "Bearer".to_string(),
262            3600, // 访问令牌1小时
263            7200, // 刷新令牌2小时
264            "scope".to_string(),
265        );
266
267        let access_remaining = user_token.access_token_remaining_seconds();
268        let refresh_remaining = user_token.refresh_token_remaining_seconds();
269
270        assert!(access_remaining > 3500 && access_remaining <= 3600);
271        assert!(refresh_remaining > 7000 && refresh_remaining <= 7200);
272    }
273
274    #[test]
275    fn test_token_info_creation() {
276        let content = serde_json::json!({
277            "user_id": "test_user",
278            "tenant_key": "test_tenant"
279        });
280
281        let token_info = TokenInfo::new(content.clone());
282
283        assert_eq!(token_info.content, content);
284        // 验证时间戳是最近的(在1秒内)
285        let now = Utc::now();
286        let time_diff = (now - token_info.verified_at).num_seconds().abs();
287        assert!(time_diff <= 1);
288    }
289
290    #[test]
291    fn test_user_info_creation() {
292        let departments = vec!["dept1".to_string(), "dept2".to_string()];
293        let user_info = UserInfo::new(
294            "user_123".to_string(),
295            "张三".to_string(),
296            Some("zhangsan@example.com".to_string()),
297            Some("+86 138 0013 8000".to_string()),
298            Some("https://example.com/avatar.jpg".to_string()),
299            Some(departments.clone()),
300        );
301
302        assert_eq!(user_info.user_id, "user_123");
303        assert_eq!(user_info.name, "张三");
304        assert_eq!(user_info.email, Some("zhangsan@example.com".to_string()));
305        assert_eq!(user_info.mobile, Some("+86 138 0013 8000".to_string()));
306        assert_eq!(
307            user_info.avatar,
308            Some("https://example.com/avatar.jpg".to_string())
309        );
310        assert_eq!(user_info.department_ids, Some(departments));
311
312        // 验证时间戳是最近的
313        let now = Utc::now();
314        let time_diff = (now - user_info.fetched_at).num_seconds().abs();
315        assert!(time_diff <= 1);
316    }
317
318    #[test]
319    fn test_user_info_optional_fields() {
320        let user_info = UserInfo::new(
321            "user_456".to_string(),
322            "李四".to_string(),
323            None,
324            None,
325            None,
326            None,
327        );
328
329        assert_eq!(user_info.user_id, "user_456");
330        assert_eq!(user_info.name, "李四");
331        assert!(user_info.email.is_none());
332        assert!(user_info.mobile.is_none());
333        assert!(user_info.avatar.is_none());
334        assert!(user_info.department_ids.is_none());
335    }
336
337    #[test]
338    fn test_token_serialization() {
339        let token = AccessToken::new("test_token".to_string(), "Bearer".to_string(), 3600);
340
341        // 测试序列化
342        let json_str = serde_json::to_string(&token).unwrap();
343        let parsed: AccessToken = serde_json::from_str(&json_str).unwrap();
344
345        assert_eq!(parsed.access_token, token.access_token);
346        assert_eq!(parsed.token_type, token.token_type);
347        assert_eq!(parsed.expires_in, token.expires_in);
348    }
349
350    #[test]
351    fn test_user_token_serialization() {
352        let user_token = UserAccessToken::new(
353            "access_token".to_string(),
354            "refresh_token".to_string(),
355            "Bearer".to_string(),
356            3600,
357            7200,
358            "read write".to_string(),
359        );
360
361        // 测试序列化
362        let json_str = serde_json::to_string(&user_token).unwrap();
363        let parsed: UserAccessToken = serde_json::from_str(&json_str).unwrap();
364
365        assert_eq!(parsed.access_token, user_token.access_token);
366        assert_eq!(parsed.refresh_token, user_token.refresh_token);
367        assert_eq!(parsed.token_type, user_token.token_type);
368        assert_eq!(parsed.scope, user_token.scope);
369    }
370
371    #[test]
372    fn test_edge_case_expiry_times() {
373        // 测试边界情况:正好在过期边界
374        let token = AccessToken::new(
375            "boundary_token".to_string(),
376            "Bearer".to_string(),
377            0, // 立即过期
378        );
379
380        // 由于我们有1分钟的缓冲期,0过期的令牌应该立即被认为是过期的
381        assert!(token.is_expired());
382
383        // 测试负数过期时间(不应该发生,但要确保代码能处理)
384        let negative_token =
385            AccessToken::new("negative_token".to_string(), "Bearer".to_string(), 0);
386        assert!(negative_token.is_expired());
387    }
388
389    #[test]
390    fn test_debug_formatting() {
391        let token = AccessToken::new("debug_token".to_string(), "Bearer".to_string(), 3600);
392
393        let debug_str = format!("{:?}", token);
394        assert!(debug_str.contains("AccessToken"));
395        assert!(debug_str.contains("debug_token"));
396        // 注意:由于安全原因,我们不应该在debug输出中显示完整的令牌
397        // 但这是测试数据,所以可以接受
398    }
399
400    #[test]
401    fn test_clone_functionality() {
402        let original_token = AccessToken::new(
403            "clone_test_token".to_string(),
404            "Bearer".to_string(),
405            1800, // 30分钟
406        );
407
408        let cloned_token = original_token.clone();
409        assert_eq!(original_token.access_token, cloned_token.access_token);
410        assert_eq!(original_token.token_type, cloned_token.token_type);
411        assert_eq!(original_token.expires_in, cloned_token.expires_in);
412        // created_at 应该相同,因为它是复制而不是重新创建
413        assert_eq!(original_token.created_at, cloned_token.created_at);
414
415        // 同样测试 UserAccessToken
416        let original_user_token = UserAccessToken::new(
417            "user_access_token".to_string(),
418            "refresh_token".to_string(),
419            "Bearer".to_string(),
420            3600,
421            7200,
422            "read".to_string(),
423        );
424
425        let cloned_user_token = original_user_token.clone();
426        assert_eq!(
427            original_user_token.access_token,
428            cloned_user_token.access_token
429        );
430        assert_eq!(
431            original_user_token.refresh_token,
432            cloned_user_token.refresh_token
433        );
434    }
435}