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