1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[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#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum TokenType {
34 Bearer,
35 Basic,
36 Custom(String),
37}
38
39#[derive(Debug, Clone)]
41pub struct AuthToken {
42 pub token: String,
43 pub token_type: TokenType,
44}
45
46impl AuthToken {
47 pub fn bearer(token: impl Into<String>) -> Self {
49 Self {
50 token: token.into(),
51 token_type: TokenType::Bearer,
52 }
53 }
54
55 pub fn basic(token: impl Into<String>) -> Self {
57 Self {
58 token: token.into(),
59 token_type: TokenType::Basic,
60 }
61 }
62
63 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 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#[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 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 pub fn anonymous() -> Self {
119 Self::new("anonymous", AuthProvider::Anonymous)
120 }
121
122 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 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 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 pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
146 self.metadata.get(key)
147 }
148
149 pub fn with_email(mut self, email: impl Into<String>) -> Self {
151 self.email = Some(email.into());
152 self
153 }
154
155 pub fn with_name(mut self, name: impl Into<String>) -> Self {
157 self.name = Some(name.into());
158 self
159 }
160
161 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 pub fn with_expiration(mut self, expires_at: DateTime<Utc>) -> Self {
169 self.expires_at = Some(expires_at);
170 self
171 }
172
173 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 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()) } else {
189 None
190 }
191 })
192 .collect()
193 }
194
195 pub fn add_role(&mut self, role: &str) {
197 self.metadata
198 .insert(format!("role_{}", role), serde_json::Value::Bool(true));
199 }
200
201 pub fn remove_role(&mut self, role: &str) {
203 self.metadata.remove(&format!("role_{}", role));
204 }
205
206 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 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 pub fn remove_permission(&mut self, permission: &str) {
224 self.metadata.remove(&format!("permission_{}", permission));
225 }
226
227 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 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#[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#[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 pub fn issuer(&self) -> Option<&str> {
284 self.payload.get("iss").and_then(|v| v.as_str())
285 }
286
287 pub fn audience(&self) -> Option<&str> {
289 self.payload.get("aud").and_then(|v| v.as_str())
290 }
291
292 pub fn subject(&self) -> Option<&str> {
294 self.payload.get("sub").and_then(|v| v.as_str())
295 }
296
297 pub fn expires_at(&self) -> Option<i64> {
299 self.payload.get("exp").and_then(|v| v.as_i64())
300 }
301
302 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 pub fn algorithm(&self) -> Option<&str> {
317 self.header.get("alg").and_then(|v| v.as_str())
318 }
319
320 pub fn token_type(&self) -> Option<&str> {
322 self.header.get("typ").and_then(|v| v.as_str())
323 }
324}