1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5pub type Token = String;
6
7#[derive(Clone)]
10pub enum Client {
11 Reqwest(reqwest::Client),
13 #[cfg(feature = "stytch")]
15 Stytch(std::sync::Arc<stytch::client::Client>),
16}
17
18impl Client {
19 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 #[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum TokenType {
77 Bearer,
79 Session,
81 Custom(String),
83}
84
85#[derive(Debug, Clone)]
87pub struct AuthToken {
88 pub token: String,
89 pub token_type: TokenType,
90}
91
92impl AuthToken {
93 pub fn bearer(token: impl Into<String>) -> Self {
95 Self {
96 token: token.into(),
97 token_type: TokenType::Bearer,
98 }
99 }
100
101 pub fn session(token: impl Into<String>) -> Self {
103 Self {
104 token: token.into(),
105 token_type: TokenType::Session,
106 }
107 }
108
109 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 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#[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 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 pub fn anonymous() -> Self {
165 Self::new("anonymous", AuthProvider::Anonymous)
166 }
167
168 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 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 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 pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
192 self.metadata.get(key)
193 }
194
195 pub fn with_email(mut self, email: impl Into<String>) -> Self {
197 self.email = Some(email.into());
198 self
199 }
200
201 pub fn with_name(mut self, name: impl Into<String>) -> Self {
203 self.name = Some(name.into());
204 self
205 }
206
207 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 pub fn with_expiration(mut self, expires_at: DateTime<Utc>) -> Self {
215 self.expires_at = Some(expires_at);
216 self
217 }
218
219 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 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()) } else {
235 None
236 }
237 })
238 .collect()
239 }
240
241 pub fn add_role(&mut self, role: &str) {
243 self.metadata
244 .insert(format!("role_{}", role), serde_json::Value::Bool(true));
245 }
246
247 pub fn remove_role(&mut self, role: &str) {
249 self.metadata.remove(&format!("role_{}", role));
250 }
251
252 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 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 pub fn remove_permission(&mut self, permission: &str) {
270 self.metadata.remove(&format!("permission_{}", permission));
271 }
272
273 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 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#[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 pub fn issuer(&self) -> Option<&str> {
305 self.payload.get("iss").and_then(|v| v.as_str())
306 }
307
308 pub fn audience(&self) -> Option<&str> {
310 self.payload.get("aud").and_then(|v| v.as_str())
311 }
312
313 pub fn subject(&self) -> Option<&str> {
315 self.payload.get("sub").and_then(|v| v.as_str())
316 }
317
318 pub fn expires_at(&self) -> Option<i64> {
320 self.payload.get("exp").and_then(|v| v.as_i64())
321 }
322
323 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 pub fn algorithm(&self) -> Option<&str> {
338 self.header.get("alg").and_then(|v| v.as_str())
339 }
340
341 pub fn token_type(&self) -> Option<&str> {
343 self.header.get("typ").and_then(|v| v.as_str())
344 }
345}