libauth_rs/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Authentication provider types
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum AuthProvider {
8    #[cfg(feature = "clerk")]
9    Clerk,
10    #[cfg(feature = "stytch")]
11    Stytch,
12    #[cfg(feature = "msal")]
13    Msal,
14    Anonymous,
15}
16
17impl std::fmt::Display for AuthProvider {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            #[cfg(feature = "clerk")]
21            AuthProvider::Clerk => write!(f, "clerk"),
22            #[cfg(feature = "stytch")]
23            AuthProvider::Stytch => write!(f, "stytch"),
24            #[cfg(feature = "msal")]
25            AuthProvider::Msal => write!(f, "msal"),
26            AuthProvider::Anonymous => write!(f, "anonymous"),
27        }
28    }
29}
30
31/// Token type enumeration
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum TokenType {
34    Bearer,
35    Basic,
36    Custom(String),
37}
38
39/// Authentication token wrapper
40#[derive(Debug, Clone)]
41pub struct AuthToken {
42    pub token: String,
43    pub token_type: TokenType,
44}
45
46impl AuthToken {
47    /// Create a new Bearer token
48    pub fn bearer(token: impl Into<String>) -> Self {
49        Self {
50            token: token.into(),
51            token_type: TokenType::Bearer,
52        }
53    }
54
55    /// Create a new Basic token
56    pub fn basic(token: impl Into<String>) -> Self {
57        Self {
58            token: token.into(),
59            token_type: TokenType::Basic,
60        }
61    }
62
63    /// Create a new Custom token
64    pub fn custom(token: impl Into<String>, custom_type: impl Into<String>) -> Self {
65        Self {
66            token: token.into(),
67            token_type: TokenType::Custom(custom_type.into()),
68        }
69    }
70
71    /// Parse from Authorization header value
72    pub fn from_auth_header(header: &str) -> Option<Self> {
73        let parts: Vec<&str> = header.splitn(2, ' ').collect();
74        if parts.len() != 2 {
75            return None;
76        }
77
78        let token_type = match parts[0].to_lowercase().as_str() {
79            "bearer" => TokenType::Bearer,
80            "basic" => TokenType::Basic,
81            other => TokenType::Custom(other.to_string()),
82        };
83
84        Some(Self {
85            token: parts[1].to_string(),
86            token_type,
87        })
88    }
89}
90
91/// User context containing authentication and profile information
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct UserContext {
94    pub user_id: String,
95    pub email: Option<String>,
96    pub name: Option<String>,
97    pub provider: AuthProvider,
98    pub session_id: Option<String>,
99    pub expires_at: Option<DateTime<Utc>>,
100    pub metadata: HashMap<String, serde_json::Value>,
101}
102
103impl UserContext {
104    /// Create a new user context
105    pub fn new(user_id: impl Into<String>, provider: AuthProvider) -> Self {
106        Self {
107            user_id: user_id.into(),
108            email: None,
109            name: None,
110            provider,
111            session_id: None,
112            expires_at: None,
113            metadata: HashMap::new(),
114        }
115    }
116
117    /// Create an anonymous user context
118    pub fn anonymous() -> Self {
119        Self::new("anonymous", AuthProvider::Anonymous)
120    }
121
122    /// Check if the user is authenticated (not anonymous)
123    pub fn is_authenticated(&self) -> bool {
124        self.provider != AuthProvider::Anonymous
125            && !self.user_id.is_empty()
126            && self.user_id != "anonymous"
127    }
128
129    /// Check if the session has expired
130    pub fn is_expired(&self) -> bool {
131        if let Some(expires_at) = self.expires_at {
132            expires_at < Utc::now()
133        } else {
134            false
135        }
136    }
137
138    /// Add metadata to the user context
139    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
140        self.metadata.insert(key.into(), value);
141        self
142    }
143
144    /// Get metadata value
145    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
146        self.metadata.get(key)
147    }
148
149    /// Set email
150    pub fn with_email(mut self, email: impl Into<String>) -> Self {
151        self.email = Some(email.into());
152        self
153    }
154
155    /// Set name
156    pub fn with_name(mut self, name: impl Into<String>) -> Self {
157        self.name = Some(name.into());
158        self
159    }
160
161    /// Set session ID
162    pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
163        self.session_id = Some(session_id.into());
164        self
165    }
166
167    /// Set expiration time
168    pub fn with_expiration(mut self, expires_at: DateTime<Utc>) -> Self {
169        self.expires_at = Some(expires_at);
170        self
171    }
172
173    /// Check if user has a specific role
174    pub fn has_role(&self, role: &str) -> bool {
175        self.metadata
176            .get(&format!("role_{}", role))
177            .and_then(|v| v.as_bool())
178            .unwrap_or(false)
179    }
180
181    /// Get all user roles
182    pub fn get_roles(&self) -> Vec<String> {
183        self.metadata
184            .iter()
185            .filter_map(|(key, value)| {
186                if key.starts_with("role_") && value.as_bool().unwrap_or(false) {
187                    Some(key[5..].to_string()) // Remove "role_" prefix
188                } else {
189                    None
190                }
191            })
192            .collect()
193    }
194
195    /// Add a role to the user
196    pub fn add_role(&mut self, role: &str) {
197        self.metadata
198            .insert(format!("role_{}", role), serde_json::Value::Bool(true));
199    }
200
201    /// Remove a role from the user
202    pub fn remove_role(&mut self, role: &str) {
203        self.metadata.remove(&format!("role_{}", role));
204    }
205
206    /// Check if user has a specific permission
207    pub fn has_permission(&self, permission: &str) -> bool {
208        self.metadata
209            .get(&format!("permission_{}", permission))
210            .and_then(|v| v.as_bool())
211            .unwrap_or(false)
212    }
213
214    /// Add a permission to the user
215    pub fn add_permission(&mut self, permission: &str) {
216        self.metadata.insert(
217            format!("permission_{}", permission),
218            serde_json::Value::Bool(true),
219        );
220    }
221
222    /// Remove a permission from the user
223    pub fn remove_permission(&mut self, permission: &str) {
224        self.metadata.remove(&format!("permission_{}", permission));
225    }
226
227    /// Get organization/tenant ID if available
228    pub fn organization_id(&self) -> Option<&str> {
229        self.metadata
230            .get("organization_id")
231            .or_else(|| self.metadata.get("org_id"))
232            .or_else(|| self.metadata.get("tenant_id"))
233            .and_then(|v| v.as_str())
234    }
235
236    /// Set organization/tenant ID
237    pub fn with_organization_id(mut self, org_id: impl Into<String>) -> Self {
238        self.metadata.insert(
239            "organization_id".to_string(),
240            serde_json::Value::String(org_id.into()),
241        );
242        self
243    }
244}
245
246/// Standard JWT claims
247#[cfg(feature = "authentication")]
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct AuthClaims {
250    pub sub: String,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub iss: Option<String>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub aud: Option<String>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub exp: Option<i64>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub nbf: Option<i64>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub iat: Option<i64>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub jti: Option<String>,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub name: Option<String>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub email: Option<String>,
267    #[serde(flatten)]
268    pub additional: HashMap<String, serde_json::Value>,
269}
270
271/// Token information extracted from JWT without verification
272#[cfg(feature = "authentication")]
273#[derive(Debug, Clone)]
274pub struct TokenInfo {
275    pub header: serde_json::Value,
276    pub payload: serde_json::Value,
277    pub provider: Option<AuthProvider>,
278}
279
280#[cfg(feature = "authentication")]
281impl TokenInfo {
282    /// Get issuer from token payload
283    pub fn issuer(&self) -> Option<&str> {
284        self.payload.get("iss").and_then(|v| v.as_str())
285    }
286
287    /// Get audience from token payload
288    pub fn audience(&self) -> Option<&str> {
289        self.payload.get("aud").and_then(|v| v.as_str())
290    }
291
292    /// Get subject (user ID) from token payload
293    pub fn subject(&self) -> Option<&str> {
294        self.payload.get("sub").and_then(|v| v.as_str())
295    }
296
297    /// Get expiration timestamp from token payload
298    pub fn expires_at(&self) -> Option<i64> {
299        self.payload.get("exp").and_then(|v| v.as_i64())
300    }
301
302    /// Check if token has expired (based on exp claim)
303    pub fn is_expired(&self) -> bool {
304        if let Some(exp) = self.expires_at() {
305            let now = std::time::SystemTime::now()
306                .duration_since(std::time::UNIX_EPOCH)
307                .unwrap()
308                .as_secs() as i64;
309            exp < now
310        } else {
311            false
312        }
313    }
314
315    /// Get algorithm from token header
316    pub fn algorithm(&self) -> Option<&str> {
317        self.header.get("alg").and_then(|v| v.as_str())
318    }
319
320    /// Get token type from header
321    pub fn token_type(&self) -> Option<&str> {
322        self.header.get("typ").and_then(|v| v.as_str())
323    }
324}