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}