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 let Some(exp) = self.exp else {
91 return false;
92 };
93 let Ok(duration) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
94 else {
95 return false;
96 };
97 duration.as_secs() as i64 >= exp
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_token_scopes() {
107 let token = TokenIntrospection {
108 active: true,
109 client_id: Some("test".to_string()),
110 token_type: Some("Bearer".to_string()),
111 scope: Some("user:read channel:read".to_string()),
112 exp: Some(9999999999),
113 };
114
115 assert_eq!(token.scopes(), vec!["user:read", "channel:read"]);
116 assert!(token.has_scope("user:read"));
117 assert!(token.has_scope("channel:read"));
118 assert!(!token.has_scope("chat:write"));
119 }
120
121 #[test]
122 fn test_token_expiry() {
123 let expired = TokenIntrospection {
124 active: true,
125 client_id: Some("test".to_string()),
126 token_type: Some("Bearer".to_string()),
127 scope: Some("user:read".to_string()),
128 exp: Some(0), };
130
131 assert!(expired.is_expired());
132
133 let valid = TokenIntrospection {
134 active: true,
135 client_id: Some("test".to_string()),
136 token_type: Some("Bearer".to_string()),
137 scope: Some("user:read".to_string()),
138 exp: Some(9999999999), };
140
141 assert!(!valid.is_expired());
142 }
143}