1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct User {
18 pub user_id: u64,
20
21 pub name: String,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub email: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub profile_picture: Option<String>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TokenIntrospection {
49 pub active: bool,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub client_id: Option<String>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub token_type: Option<String>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub scope: Option<String>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub exp: Option<i64>,
67}
68
69impl TokenIntrospection {
70 pub fn is_active(&self) -> bool {
72 self.active
73 }
74
75 pub fn scopes(&self) -> Vec<String> {
77 self.scope
78 .as_ref()
79 .map(|s| s.split_whitespace().map(String::from).collect())
80 .unwrap_or_default()
81 }
82
83 pub fn has_scope(&self, scope: &str) -> bool {
85 self.scopes().iter().any(|s| s == scope)
86 }
87
88 pub fn is_expired(&self) -> bool {
90 if let Some(exp) = self.exp {
91 let now = std::time::SystemTime::now()
92 .duration_since(std::time::UNIX_EPOCH)
93 .unwrap()
94 .as_secs() as i64;
95 now >= exp
96 } else {
97 false
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_token_scopes() {
108 let token = TokenIntrospection {
109 active: true,
110 client_id: Some("test".to_string()),
111 token_type: Some("Bearer".to_string()),
112 scope: Some("user:read channel:read".to_string()),
113 exp: Some(9999999999),
114 };
115
116 assert_eq!(token.scopes(), vec!["user:read", "channel:read"]);
117 assert!(token.has_scope("user:read"));
118 assert!(token.has_scope("channel:read"));
119 assert!(!token.has_scope("chat:write"));
120 }
121
122 #[test]
123 fn test_token_expiry() {
124 let expired = TokenIntrospection {
125 active: true,
126 client_id: Some("test".to_string()),
127 token_type: Some("Bearer".to_string()),
128 scope: Some("user:read".to_string()),
129 exp: Some(0), };
131
132 assert!(expired.is_expired());
133
134 let valid = TokenIntrospection {
135 active: true,
136 client_id: Some("test".to_string()),
137 token_type: Some("Bearer".to_string()),
138 scope: Some("user:read".to_string()),
139 exp: Some(9999999999), };
141
142 assert!(!valid.is_expired());
143 }
144}