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, 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, false).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, 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, false).await 86} 87 88pub async fn validate_bearer_token_for_service_auth( 89 db: &PgPool, 90 token: &str, 91) -> Result<AuthenticatedUser, TokenValidationError> { 92 validate_bearer_token_with_options_internal(db, None, token, true, true).await 93} 94 95async fn validate_bearer_token_with_options_internal( 96 db: &PgPool, 97 cache: Option<&Arc<dyn Cache>>, 98 token: &str, 99 allow_deactivated: bool, 100 allow_takendown: bool, 101) -> Result<AuthenticatedUser, TokenValidationError> { 102 let did_from_token = get_did_from_token(token).ok(); 103 104 if let Some(ref did) = did_from_token { 105 let key_cache_key = format!("auth:key:{}", did); 106 let mut cached_key: Option<Vec<u8>> = None; 107 108 if let Some(c) = cache { 109 cached_key = c.get_bytes(&key_cache_key).await; 110 if cached_key.is_some() { 111 crate::metrics::record_auth_cache_hit("key"); 112 } else { 113 crate::metrics::record_auth_cache_miss("key"); 114 } 115 } 116 117 let (decrypted_key, deactivated_at, takedown_ref, is_admin) = if let Some(key) = cached_key { 118 let user_status = sqlx::query!( 119 "SELECT deactivated_at, takedown_ref, is_admin FROM users WHERE did = $1", 120 did 121 ) 122 .fetch_optional(db) 123 .await 124 .ok() 125 .flatten(); 126 127 match user_status { 128 Some(status) => (Some(key), status.deactivated_at, status.takedown_ref, status.is_admin), 129 None => (None, None, None, false), 130 } 131 } else if let Some(user) = sqlx::query!( 132 "SELECT k.key_bytes, k.encryption_version, u.deactivated_at, u.takedown_ref, u.is_admin 133 FROM users u 134 JOIN user_keys k ON u.id = k.user_id 135 WHERE u.did = $1", 136 did 137 ) 138 .fetch_optional(db) 139 .await 140 .ok() 141 .flatten() 142 { 143 let key = crate::config::decrypt_key(&user.key_bytes, user.encryption_version) 144 .map_err(|_| TokenValidationError::KeyDecryptionFailed)?; 145 146 if let Some(c) = cache { 147 let _ = c 148 .set_bytes( 149 &key_cache_key, 150 &key, 151 Duration::from_secs(KEY_CACHE_TTL_SECS), 152 ) 153 .await; 154 } 155 156 (Some(key), user.deactivated_at, user.takedown_ref, user.is_admin) 157 } else { 158 (None, None, None, false) 159 }; 160 161 if let Some(decrypted_key) = decrypted_key { 162 if !allow_deactivated && deactivated_at.is_some() { 163 return Err(TokenValidationError::AccountDeactivated); 164 } 165 166 if !allow_takendown && takedown_ref.is_some() { 167 return Err(TokenValidationError::AccountTakedown); 168 } 169 170 if let Ok(token_data) = verify_access_token(token, &decrypted_key) { 171 let jti = &token_data.claims.jti; 172 let session_cache_key = format!("auth:session:{}:{}", did, jti); 173 let mut session_valid = false; 174 175 if let Some(c) = cache { 176 if let Some(cached_value) = c.get(&session_cache_key).await { 177 session_valid = cached_value == "1"; 178 crate::metrics::record_auth_cache_hit("session"); 179 } else { 180 crate::metrics::record_auth_cache_miss("session"); 181 } 182 } 183 184 if !session_valid { 185 let session_exists = sqlx::query_scalar!( 186 "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()", 187 did, 188 jti 189 ) 190 .fetch_optional(db) 191 .await 192 .ok() 193 .flatten(); 194 195 session_valid = session_exists.is_some(); 196 197 if session_valid 198 && let Some(c) = cache { 199 let _ = c 200 .set( 201 &session_cache_key, 202 "1", 203 Duration::from_secs(SESSION_CACHE_TTL_SECS), 204 ) 205 .await; 206 } 207 } 208 209 if session_valid { 210 return Ok(AuthenticatedUser { 211 did: did.clone(), 212 key_bytes: Some(decrypted_key), 213 is_oauth: false, 214 is_admin, 215 }); 216 } 217 } 218 } 219 } 220 221 if let Ok(oauth_info) = crate::oauth::verify::extract_oauth_token_info(token) 222 && let Some(oauth_token) = sqlx::query!( 223 r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref, u.is_admin, 224 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?" 225 FROM oauth_token t 226 JOIN users u ON t.did = u.did 227 LEFT JOIN user_keys k ON u.id = k.user_id 228 WHERE t.token_id = $1"#, 229 oauth_info.token_id 230 ) 231 .fetch_optional(db) 232 .await 233 .ok() 234 .flatten() 235 { 236 if !allow_deactivated && oauth_token.deactivated_at.is_some() { 237 return Err(TokenValidationError::AccountDeactivated); 238 } 239 240 if oauth_token.takedown_ref.is_some() { 241 return Err(TokenValidationError::AccountTakedown); 242 } 243 244 let now = chrono::Utc::now(); 245 if oauth_token.expires_at > now { 246 let key_bytes = if let (Some(kb), Some(ev)) = 247 (&oauth_token.key_bytes, oauth_token.encryption_version) 248 { 249 crate::config::decrypt_key(kb, Some(ev)).ok() 250 } else { 251 None 252 }; 253 return Ok(AuthenticatedUser { 254 did: oauth_token.did, 255 key_bytes, 256 is_oauth: true, 257 is_admin: oauth_token.is_admin, 258 }); 259 } 260 } 261 262 Err(TokenValidationError::AuthenticationFailed) 263} 264 265pub async fn invalidate_auth_cache(cache: &Arc<dyn Cache>, did: &str) { 266 let key_cache_key = format!("auth:key:{}", did); 267 let _ = cache.delete(&key_cache_key).await; 268} 269 270pub async fn validate_token_with_dpop( 271 db: &PgPool, 272 token: &str, 273 is_dpop_token: bool, 274 dpop_proof: Option<&str>, 275 http_method: &str, 276 http_uri: &str, 277 allow_deactivated: bool, 278) -> Result<AuthenticatedUser, TokenValidationError> { 279 if !is_dpop_token { 280 if allow_deactivated { 281 return validate_bearer_token_allow_deactivated(db, token).await; 282 } else { 283 return validate_bearer_token(db, token).await; 284 } 285 } 286 match crate::oauth::verify::verify_oauth_access_token( 287 db, 288 token, 289 dpop_proof, 290 http_method, 291 http_uri, 292 ) 293 .await 294 { 295 Ok(result) => { 296 let user_info = sqlx::query!( 297 r#"SELECT u.deactivated_at, u.takedown_ref, u.is_admin, 298 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?" 299 FROM users u 300 LEFT JOIN user_keys k ON u.id = k.user_id 301 WHERE u.did = $1"#, 302 result.did 303 ) 304 .fetch_optional(db) 305 .await 306 .ok() 307 .flatten(); 308 let Some(user_info) = user_info else { 309 return Err(TokenValidationError::AuthenticationFailed); 310 }; 311 if !allow_deactivated && user_info.deactivated_at.is_some() { 312 return Err(TokenValidationError::AccountDeactivated); 313 } 314 if user_info.takedown_ref.is_some() { 315 return Err(TokenValidationError::AccountTakedown); 316 } 317 let key_bytes = if let (Some(kb), Some(ev)) = (&user_info.key_bytes, user_info.encryption_version) { 318 crate::config::decrypt_key(kb, Some(ev)).ok() 319 } else { 320 None 321 }; 322 Ok(AuthenticatedUser { 323 did: result.did, 324 key_bytes, 325 is_oauth: true, 326 is_admin: user_info.is_admin, 327 }) 328 } 329 Err(_) => Err(TokenValidationError::AuthenticationFailed), 330 } 331} 332 333#[derive(Debug, Serialize, Deserialize)] 334pub struct Claims { 335 pub iss: String, 336 pub sub: String, 337 pub aud: String, 338 pub exp: usize, 339 pub iat: usize, 340 #[serde(skip_serializing_if = "Option::is_none")] 341 pub scope: Option<String>, 342 #[serde(skip_serializing_if = "Option::is_none")] 343 pub lxm: Option<String>, 344 pub jti: String, 345} 346 347#[derive(Debug, Serialize, Deserialize)] 348pub struct Header { 349 pub alg: String, 350 pub typ: String, 351} 352 353#[derive(Debug, Serialize, Deserialize)] 354pub struct UnsafeClaims { 355 pub iss: String, 356 pub sub: Option<String>, 357} 358 359pub struct TokenData<T> { 360 pub claims: T, 361}