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