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 match session_row {
233 Some(row) => {
234 if row.access_expires_at > chrono::Utc::now() {
235 session_valid = true;
236 if let Some(c) = cache {
237 let _ = c
238 .set(
239 &session_cache_key,
240 "1",
241 Duration::from_secs(SESSION_CACHE_TTL_SECS),
242 )
243 .await;
244 }
245 } else {
246 return Err(TokenValidationError::TokenExpired);
247 }
248 }
249 None => {}
250 }
251 }
252
253 if session_valid {
254 let controller_did = token_data.claims.act.as_ref().map(|a| a.sub.clone());
255 return Ok(AuthenticatedUser {
256 did: did.clone(),
257 key_bytes: Some(decrypted_key),
258 is_oauth: false,
259 is_admin,
260 scope: token_data.claims.scope.clone(),
261 controller_did,
262 });
263 }
264 }
265 Err(verify::TokenVerifyError::Expired) => {
266 return Err(TokenValidationError::TokenExpired);
267 }
268 Err(verify::TokenVerifyError::Invalid) => {}
269 }
270 }
271 }
272
273 if let Ok(oauth_info) = crate::oauth::verify::extract_oauth_token_info(token)
274 && let Some(oauth_token) = sqlx::query!(
275 r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref, u.is_admin,
276 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?"
277 FROM oauth_token t
278 JOIN users u ON t.did = u.did
279 LEFT JOIN user_keys k ON u.id = k.user_id
280 WHERE t.token_id = $1"#,
281 oauth_info.token_id
282 )
283 .fetch_optional(db)
284 .await
285 .ok()
286 .flatten()
287 {
288 if !allow_deactivated && oauth_token.deactivated_at.is_some() {
289 return Err(TokenValidationError::AccountDeactivated);
290 }
291
292 if oauth_token.takedown_ref.is_some() {
293 return Err(TokenValidationError::AccountTakedown);
294 }
295
296 let now = chrono::Utc::now();
297 if oauth_token.expires_at > now {
298 let key_bytes = if let (Some(kb), Some(ev)) =
299 (&oauth_token.key_bytes, oauth_token.encryption_version)
300 {
301 crate::config::decrypt_key(kb, Some(ev)).ok()
302 } else {
303 None
304 };
305 return Ok(AuthenticatedUser {
306 did: oauth_token.did,
307 key_bytes,
308 is_oauth: true,
309 is_admin: oauth_token.is_admin,
310 scope: oauth_info.scope,
311 controller_did: oauth_info.controller_did,
312 });
313 } else {
314 return Err(TokenValidationError::TokenExpired);
315 }
316 }
317
318 Err(TokenValidationError::AuthenticationFailed)
319}
320
321pub async fn invalidate_auth_cache(cache: &Arc<dyn Cache>, did: &str) {
322 let key_cache_key = format!("auth:key:{}", did);
323 let _ = cache.delete(&key_cache_key).await;
324}
325
326pub async fn validate_token_with_dpop(
327 db: &PgPool,
328 token: &str,
329 is_dpop_token: bool,
330 dpop_proof: Option<&str>,
331 http_method: &str,
332 http_uri: &str,
333 allow_deactivated: bool,
334) -> Result<AuthenticatedUser, TokenValidationError> {
335 if !is_dpop_token {
336 if allow_deactivated {
337 return validate_bearer_token_allow_deactivated(db, token).await;
338 } else {
339 return validate_bearer_token(db, token).await;
340 }
341 }
342 match crate::oauth::verify::verify_oauth_access_token(
343 db,
344 token,
345 dpop_proof,
346 http_method,
347 http_uri,
348 )
349 .await
350 {
351 Ok(result) => {
352 let user_info = sqlx::query!(
353 r#"SELECT u.deactivated_at, u.takedown_ref, u.is_admin,
354 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?"
355 FROM users u
356 LEFT JOIN user_keys k ON u.id = k.user_id
357 WHERE u.did = $1"#,
358 result.did
359 )
360 .fetch_optional(db)
361 .await
362 .ok()
363 .flatten();
364 let Some(user_info) = user_info else {
365 return Err(TokenValidationError::AuthenticationFailed);
366 };
367 if !allow_deactivated && user_info.deactivated_at.is_some() {
368 return Err(TokenValidationError::AccountDeactivated);
369 }
370 if user_info.takedown_ref.is_some() {
371 return Err(TokenValidationError::AccountTakedown);
372 }
373 let key_bytes = if let (Some(kb), Some(ev)) =
374 (&user_info.key_bytes, user_info.encryption_version)
375 {
376 crate::config::decrypt_key(kb, Some(ev)).ok()
377 } else {
378 None
379 };
380 Ok(AuthenticatedUser {
381 did: result.did,
382 key_bytes,
383 is_oauth: true,
384 is_admin: user_info.is_admin,
385 scope: result.scope,
386 controller_did: None,
387 })
388 }
389 Err(_) => Err(TokenValidationError::AuthenticationFailed),
390 }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct ActClaim {
395 pub sub: String,
396}
397
398#[derive(Debug, Serialize, Deserialize)]
399pub struct Claims {
400 pub iss: String,
401 pub sub: String,
402 pub aud: String,
403 pub exp: usize,
404 pub iat: usize,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub scope: Option<String>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub lxm: Option<String>,
409 pub jti: String,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub act: Option<ActClaim>,
412}
413
414#[derive(Debug, Serialize, Deserialize)]
415pub struct Header {
416 pub alg: String,
417 pub typ: String,
418}
419
420#[derive(Debug, Serialize, Deserialize)]
421pub struct UnsafeClaims {
422 pub iss: String,
423 pub sub: Option<String>,
424}
425
426pub struct TokenData<T> {
427 pub claims: T,
428}