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