libauth_rs/
types.rs

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