this repo has no description
1use serde::{Deserialize, Serialize}; 2use sqlx::PgPool; 3use std::fmt; 4use std::sync::Arc; 5use std::time::Duration; 6use crate::cache::Cache; 7pub mod extractor; 8pub mod token; 9pub mod verify; 10pub use extractor::{BearerAuth, BearerAuthAllowDeactivated, AuthError, extract_bearer_token_from_header}; 11pub use token::{ 12 create_access_token, create_refresh_token, create_service_token, 13 create_access_token_with_metadata, create_refresh_token_with_metadata, 14 TokenWithMetadata, 15 TOKEN_TYPE_ACCESS, TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE, 16 SCOPE_ACCESS, SCOPE_REFRESH, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, 17}; 18pub use verify::{get_did_from_token, get_jti_from_token, verify_token, verify_access_token, verify_refresh_token}; 19const KEY_CACHE_TTL_SECS: u64 = 300; 20const SESSION_CACHE_TTL_SECS: u64 = 60; 21#[derive(Debug, Clone, Copy, PartialEq, Eq)] 22pub enum TokenValidationError { 23 AccountDeactivated, 24 AccountTakedown, 25 KeyDecryptionFailed, 26 AuthenticationFailed, 27} 28impl fmt::Display for TokenValidationError { 29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 match self { 31 Self::AccountDeactivated => write!(f, "AccountDeactivated"), 32 Self::AccountTakedown => write!(f, "AccountTakedown"), 33 Self::KeyDecryptionFailed => write!(f, "KeyDecryptionFailed"), 34 Self::AuthenticationFailed => write!(f, "AuthenticationFailed"), 35 } 36 } 37} 38pub struct AuthenticatedUser { 39 pub did: String, 40 pub key_bytes: Option<Vec<u8>>, 41 pub is_oauth: bool, 42} 43pub async fn validate_bearer_token( 44 db: &PgPool, 45 token: &str, 46) -> Result<AuthenticatedUser, TokenValidationError> { 47 validate_bearer_token_with_options_internal(db, None, token, false).await 48} 49pub async fn validate_bearer_token_allow_deactivated( 50 db: &PgPool, 51 token: &str, 52) -> Result<AuthenticatedUser, TokenValidationError> { 53 validate_bearer_token_with_options_internal(db, None, token, true).await 54} 55pub async fn validate_bearer_token_cached( 56 db: &PgPool, 57 cache: &Arc<dyn Cache>, 58 token: &str, 59) -> Result<AuthenticatedUser, TokenValidationError> { 60 validate_bearer_token_with_options_internal(db, Some(cache), token, false).await 61} 62pub async fn validate_bearer_token_cached_allow_deactivated( 63 db: &PgPool, 64 cache: &Arc<dyn Cache>, 65 token: &str, 66) -> Result<AuthenticatedUser, TokenValidationError> { 67 validate_bearer_token_with_options_internal(db, Some(cache), token, true).await 68} 69async fn validate_bearer_token_with_options_internal( 70 db: &PgPool, 71 cache: Option<&Arc<dyn Cache>>, 72 token: &str, 73 allow_deactivated: bool, 74) -> Result<AuthenticatedUser, TokenValidationError> { 75 let did_from_token = get_did_from_token(token).ok(); 76 if let Some(ref did) = did_from_token { 77 let key_cache_key = format!("auth:key:{}", did); 78 let mut cached_key: Option<Vec<u8>> = None; 79 if let Some(c) = cache { 80 cached_key = c.get_bytes(&key_cache_key).await; 81 if cached_key.is_some() { 82 crate::metrics::record_auth_cache_hit("key"); 83 } else { 84 crate::metrics::record_auth_cache_miss("key"); 85 } 86 } 87 let (decrypted_key, deactivated_at, takedown_ref) = if let Some(key) = cached_key { 88 let user_status = sqlx::query!( 89 "SELECT deactivated_at, takedown_ref FROM users WHERE did = $1", 90 did 91 ) 92 .fetch_optional(db) 93 .await 94 .ok() 95 .flatten(); 96 match user_status { 97 Some(status) => (Some(key), status.deactivated_at, status.takedown_ref), 98 None => (None, None, None), 99 } 100 } else { 101 if let Some(user) = sqlx::query!( 102 "SELECT k.key_bytes, k.encryption_version, u.deactivated_at, u.takedown_ref 103 FROM users u 104 JOIN user_keys k ON u.id = k.user_id 105 WHERE u.did = $1", 106 did 107 ) 108 .fetch_optional(db) 109 .await 110 .ok() 111 .flatten() 112 { 113 let key = crate::config::decrypt_key(&user.key_bytes, user.encryption_version) 114 .map_err(|_| TokenValidationError::KeyDecryptionFailed)?; 115 if let Some(c) = cache { 116 let _ = c.set_bytes(&key_cache_key, &key, Duration::from_secs(KEY_CACHE_TTL_SECS)).await; 117 } 118 (Some(key), user.deactivated_at, user.takedown_ref) 119 } else { 120 (None, None, None) 121 } 122 }; 123 if let Some(decrypted_key) = decrypted_key { 124 if !allow_deactivated && deactivated_at.is_some() { 125 return Err(TokenValidationError::AccountDeactivated); 126 } 127 if takedown_ref.is_some() { 128 return Err(TokenValidationError::AccountTakedown); 129 } 130 if let Ok(token_data) = verify_access_token(token, &decrypted_key) { 131 let jti = &token_data.claims.jti; 132 let session_cache_key = format!("auth:session:{}:{}", did, jti); 133 let mut session_valid = false; 134 if let Some(c) = cache { 135 if let Some(cached_value) = c.get(&session_cache_key).await { 136 session_valid = cached_value == "1"; 137 crate::metrics::record_auth_cache_hit("session"); 138 } else { 139 crate::metrics::record_auth_cache_miss("session"); 140 } 141 } 142 if !session_valid { 143 let session_exists = sqlx::query_scalar!( 144 "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()", 145 did, 146 jti 147 ) 148 .fetch_optional(db) 149 .await 150 .ok() 151 .flatten(); 152 session_valid = session_exists.is_some(); 153 if session_valid { 154 if let Some(c) = cache { 155 let _ = c.set(&session_cache_key, "1", Duration::from_secs(SESSION_CACHE_TTL_SECS)).await; 156 } 157 } 158 } 159 if session_valid { 160 return Ok(AuthenticatedUser { 161 did: did.clone(), 162 key_bytes: Some(decrypted_key), 163 is_oauth: false, 164 }); 165 } 166 } 167 } 168 } 169 if let Ok(oauth_info) = crate::oauth::verify::extract_oauth_token_info(token) { 170 if let Some(oauth_token) = sqlx::query!( 171 r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref 172 FROM oauth_token t 173 JOIN users u ON t.did = u.did 174 WHERE t.token_id = $1"#, 175 oauth_info.token_id 176 ) 177 .fetch_optional(db) 178 .await 179 .ok() 180 .flatten() 181 { 182 if !allow_deactivated && oauth_token.deactivated_at.is_some() { 183 return Err(TokenValidationError::AccountDeactivated); 184 } 185 if oauth_token.takedown_ref.is_some() { 186 return Err(TokenValidationError::AccountTakedown); 187 } 188 let now = chrono::Utc::now(); 189 if oauth_token.expires_at > now { 190 return Ok(AuthenticatedUser { 191 did: oauth_token.did, 192 key_bytes: None, 193 is_oauth: true, 194 }); 195 } 196 } 197 } 198 Err(TokenValidationError::AuthenticationFailed) 199} 200pub async fn invalidate_auth_cache(cache: &Arc<dyn Cache>, did: &str) { 201 let key_cache_key = format!("auth:key:{}", did); 202 let _ = cache.delete(&key_cache_key).await; 203} 204#[derive(Debug, Serialize, Deserialize)] 205pub struct Claims { 206 pub iss: String, 207 pub sub: String, 208 pub aud: String, 209 pub exp: usize, 210 pub iat: usize, 211 #[serde(skip_serializing_if = "Option::is_none")] 212 pub scope: Option<String>, 213 #[serde(skip_serializing_if = "Option::is_none")] 214 pub lxm: Option<String>, 215 pub jti: String, 216} 217#[derive(Debug, Serialize, Deserialize)] 218pub struct Header { 219 pub alg: String, 220 pub typ: String, 221} 222#[derive(Debug, Serialize, Deserialize)] 223pub struct UnsafeClaims { 224 pub iss: String, 225 pub sub: Option<String>, 226} 227// fancy boy TokenData equivalent for compatibility/structure 228pub struct TokenData<T> { 229 pub claims: T, 230}