this repo has no description
1use serde::{Deserialize, Serialize};
2use sqlx::PgPool;
3use std::fmt;
4use std::sync::Arc;
5use std::time::Duration;
6
7use crate::cache::Cache;
8
9pub mod extractor;
10pub mod service;
11pub mod token;
12pub mod verify;
13
14pub use extractor::{
15 AuthError, BearerAuth, BearerAuthAdmin, BearerAuthAllowDeactivated, ExtractedToken,
16 extract_auth_token_from_header, extract_bearer_token_from_header,
17};
18pub use token::{
19 SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, SCOPE_REFRESH, TOKEN_TYPE_ACCESS,
20 TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE, TokenWithMetadata, create_access_token,
21 create_access_token_with_metadata, create_refresh_token, create_refresh_token_with_metadata,
22 create_service_token,
23};
24pub use verify::{
25 get_did_from_token, get_jti_from_token, verify_access_token, verify_refresh_token, verify_token,
26};
27pub use service::{ServiceTokenClaims, ServiceTokenVerifier, is_service_token};
28
29const KEY_CACHE_TTL_SECS: u64 = 300;
30const SESSION_CACHE_TTL_SECS: u64 = 60;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TokenValidationError {
34 AccountDeactivated,
35 AccountTakedown,
36 KeyDecryptionFailed,
37 AuthenticationFailed,
38}
39
40impl fmt::Display for TokenValidationError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::AccountDeactivated => write!(f, "AccountDeactivated"),
44 Self::AccountTakedown => write!(f, "AccountTakedown"),
45 Self::KeyDecryptionFailed => write!(f, "KeyDecryptionFailed"),
46 Self::AuthenticationFailed => write!(f, "AuthenticationFailed"),
47 }
48 }
49}
50
51pub struct AuthenticatedUser {
52 pub did: String,
53 pub key_bytes: Option<Vec<u8>>,
54 pub is_oauth: bool,
55 pub is_admin: bool,
56}
57
58pub async fn validate_bearer_token(
59 db: &PgPool,
60 token: &str,
61) -> Result<AuthenticatedUser, TokenValidationError> {
62 validate_bearer_token_with_options_internal(db, None, token, false).await
63}
64
65pub async fn validate_bearer_token_allow_deactivated(
66 db: &PgPool,
67 token: &str,
68) -> Result<AuthenticatedUser, TokenValidationError> {
69 validate_bearer_token_with_options_internal(db, None, token, true).await
70}
71
72pub async fn validate_bearer_token_cached(
73 db: &PgPool,
74 cache: &Arc<dyn Cache>,
75 token: &str,
76) -> Result<AuthenticatedUser, TokenValidationError> {
77 validate_bearer_token_with_options_internal(db, Some(cache), token, false).await
78}
79
80pub async fn validate_bearer_token_cached_allow_deactivated(
81 db: &PgPool,
82 cache: &Arc<dyn Cache>,
83 token: &str,
84) -> Result<AuthenticatedUser, TokenValidationError> {
85 validate_bearer_token_with_options_internal(db, Some(cache), token, true).await
86}
87
88async fn validate_bearer_token_with_options_internal(
89 db: &PgPool,
90 cache: Option<&Arc<dyn Cache>>,
91 token: &str,
92 allow_deactivated: bool,
93) -> Result<AuthenticatedUser, TokenValidationError> {
94 let did_from_token = get_did_from_token(token).ok();
95
96 if let Some(ref did) = did_from_token {
97 let key_cache_key = format!("auth:key:{}", did);
98 let mut cached_key: Option<Vec<u8>> = None;
99
100 if let Some(c) = cache {
101 cached_key = c.get_bytes(&key_cache_key).await;
102 if cached_key.is_some() {
103 crate::metrics::record_auth_cache_hit("key");
104 } else {
105 crate::metrics::record_auth_cache_miss("key");
106 }
107 }
108
109 let (decrypted_key, deactivated_at, takedown_ref, is_admin) = if let Some(key) = cached_key {
110 let user_status = sqlx::query!(
111 "SELECT deactivated_at, takedown_ref, is_admin FROM users WHERE did = $1",
112 did
113 )
114 .fetch_optional(db)
115 .await
116 .ok()
117 .flatten();
118
119 match user_status {
120 Some(status) => (Some(key), status.deactivated_at, status.takedown_ref, status.is_admin),
121 None => (None, None, None, false),
122 }
123 } else if let Some(user) = sqlx::query!(
124 "SELECT k.key_bytes, k.encryption_version, u.deactivated_at, u.takedown_ref, u.is_admin
125 FROM users u
126 JOIN user_keys k ON u.id = k.user_id
127 WHERE u.did = $1",
128 did
129 )
130 .fetch_optional(db)
131 .await
132 .ok()
133 .flatten()
134 {
135 let key = crate::config::decrypt_key(&user.key_bytes, user.encryption_version)
136 .map_err(|_| TokenValidationError::KeyDecryptionFailed)?;
137
138 if let Some(c) = cache {
139 let _ = c
140 .set_bytes(
141 &key_cache_key,
142 &key,
143 Duration::from_secs(KEY_CACHE_TTL_SECS),
144 )
145 .await;
146 }
147
148 (Some(key), user.deactivated_at, user.takedown_ref, user.is_admin)
149 } else {
150 (None, None, None, false)
151 };
152
153 if let Some(decrypted_key) = decrypted_key {
154 if !allow_deactivated && deactivated_at.is_some() {
155 return Err(TokenValidationError::AccountDeactivated);
156 }
157
158 if takedown_ref.is_some() {
159 return Err(TokenValidationError::AccountTakedown);
160 }
161
162 if let Ok(token_data) = verify_access_token(token, &decrypted_key) {
163 let jti = &token_data.claims.jti;
164 let session_cache_key = format!("auth:session:{}:{}", did, jti);
165 let mut session_valid = false;
166
167 if let Some(c) = cache {
168 if let Some(cached_value) = c.get(&session_cache_key).await {
169 session_valid = cached_value == "1";
170 crate::metrics::record_auth_cache_hit("session");
171 } else {
172 crate::metrics::record_auth_cache_miss("session");
173 }
174 }
175
176 if !session_valid {
177 let session_exists = sqlx::query_scalar!(
178 "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()",
179 did,
180 jti
181 )
182 .fetch_optional(db)
183 .await
184 .ok()
185 .flatten();
186
187 session_valid = session_exists.is_some();
188
189 if session_valid
190 && let Some(c) = cache {
191 let _ = c
192 .set(
193 &session_cache_key,
194 "1",
195 Duration::from_secs(SESSION_CACHE_TTL_SECS),
196 )
197 .await;
198 }
199 }
200
201 if session_valid {
202 return Ok(AuthenticatedUser {
203 did: did.clone(),
204 key_bytes: Some(decrypted_key),
205 is_oauth: false,
206 is_admin,
207 });
208 }
209 }
210 }
211 }
212
213 if let Ok(oauth_info) = crate::oauth::verify::extract_oauth_token_info(token)
214 && let Some(oauth_token) = sqlx::query!(
215 r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref, u.is_admin,
216 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?"
217 FROM oauth_token t
218 JOIN users u ON t.did = u.did
219 LEFT JOIN user_keys k ON u.id = k.user_id
220 WHERE t.token_id = $1"#,
221 oauth_info.token_id
222 )
223 .fetch_optional(db)
224 .await
225 .ok()
226 .flatten()
227 {
228 if !allow_deactivated && oauth_token.deactivated_at.is_some() {
229 return Err(TokenValidationError::AccountDeactivated);
230 }
231
232 if oauth_token.takedown_ref.is_some() {
233 return Err(TokenValidationError::AccountTakedown);
234 }
235
236 let now = chrono::Utc::now();
237 if oauth_token.expires_at > now {
238 let key_bytes = if let (Some(kb), Some(ev)) =
239 (&oauth_token.key_bytes, oauth_token.encryption_version)
240 {
241 crate::config::decrypt_key(kb, Some(ev)).ok()
242 } else {
243 None
244 };
245 return Ok(AuthenticatedUser {
246 did: oauth_token.did,
247 key_bytes,
248 is_oauth: true,
249 is_admin: oauth_token.is_admin,
250 });
251 }
252 }
253
254 Err(TokenValidationError::AuthenticationFailed)
255}
256
257pub async fn invalidate_auth_cache(cache: &Arc<dyn Cache>, did: &str) {
258 let key_cache_key = format!("auth:key:{}", did);
259 let _ = cache.delete(&key_cache_key).await;
260}
261
262pub async fn validate_token_with_dpop(
263 db: &PgPool,
264 token: &str,
265 is_dpop_token: bool,
266 dpop_proof: Option<&str>,
267 http_method: &str,
268 http_uri: &str,
269 allow_deactivated: bool,
270) -> Result<AuthenticatedUser, TokenValidationError> {
271 if !is_dpop_token {
272 if allow_deactivated {
273 return validate_bearer_token_allow_deactivated(db, token).await;
274 } else {
275 return validate_bearer_token(db, token).await;
276 }
277 }
278 match crate::oauth::verify::verify_oauth_access_token(
279 db,
280 token,
281 dpop_proof,
282 http_method,
283 http_uri,
284 )
285 .await
286 {
287 Ok(result) => {
288 let user_info = sqlx::query!(
289 r#"SELECT u.deactivated_at, u.takedown_ref, u.is_admin,
290 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?"
291 FROM users u
292 LEFT JOIN user_keys k ON u.id = k.user_id
293 WHERE u.did = $1"#,
294 result.did
295 )
296 .fetch_optional(db)
297 .await
298 .ok()
299 .flatten();
300 let Some(user_info) = user_info else {
301 return Err(TokenValidationError::AuthenticationFailed);
302 };
303 if !allow_deactivated && user_info.deactivated_at.is_some() {
304 return Err(TokenValidationError::AccountDeactivated);
305 }
306 if user_info.takedown_ref.is_some() {
307 return Err(TokenValidationError::AccountTakedown);
308 }
309 let key_bytes = if let (Some(kb), Some(ev)) = (&user_info.key_bytes, user_info.encryption_version) {
310 crate::config::decrypt_key(kb, Some(ev)).ok()
311 } else {
312 None
313 };
314 Ok(AuthenticatedUser {
315 did: result.did,
316 key_bytes,
317 is_oauth: true,
318 is_admin: user_info.is_admin,
319 })
320 }
321 Err(_) => Err(TokenValidationError::AuthenticationFailed),
322 }
323}
324
325#[derive(Debug, Serialize, Deserialize)]
326pub struct Claims {
327 pub iss: String,
328 pub sub: String,
329 pub aud: String,
330 pub exp: usize,
331 pub iat: usize,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub scope: Option<String>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 pub lxm: Option<String>,
336 pub jti: String,
337}
338
339#[derive(Debug, Serialize, Deserialize)]
340pub struct Header {
341 pub alg: String,
342 pub typ: String,
343}
344
345#[derive(Debug, Serialize, Deserialize)]
346pub struct UnsafeClaims {
347 pub iss: String,
348 pub sub: Option<String>,
349}
350
351pub struct TokenData<T> {
352 pub claims: T,
353}