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}