Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
238
fork

Configure Feed

Select the types of activity you want to include in your feed.

JWT token refresh good error

+170 -42
+1
src/api/error.rs
··· 192 192 crate::auth::TokenValidationError::AccountTakedown => Self::AccountTakedown, 193 193 crate::auth::TokenValidationError::KeyDecryptionFailed => Self::InternalError, 194 194 crate::auth::TokenValidationError::AuthenticationFailed => Self::AuthenticationFailed, 195 + crate::auth::TokenValidationError::TokenExpired => Self::ExpiredToken, 195 196 } 196 197 } 197 198 }
+18 -2
src/auth/extractor.rs
··· 19 19 MissingToken, 20 20 InvalidFormat, 21 21 AuthenticationFailed, 22 + TokenExpired, 22 23 AccountDeactivated, 23 24 AccountTakedown, 24 25 AdminRequired, ··· 39 40 ), 40 41 AuthError::AuthenticationFailed => ( 41 42 StatusCode::UNAUTHORIZED, 42 - "AuthenticationFailed", 43 - "Invalid or expired token", 43 + "InvalidToken", 44 + "Token could not be verified", 45 + ), 46 + AuthError::TokenExpired => ( 47 + StatusCode::UNAUTHORIZED, 48 + "ExpiredToken", 49 + "Token has expired", 44 50 ), 45 51 AuthError::AccountDeactivated => ( 46 52 StatusCode::UNAUTHORIZED, ··· 174 180 Ok(user) => Ok(BearerAuth(user)), 175 181 Err(TokenValidationError::AccountDeactivated) => Err(AuthError::AccountDeactivated), 176 182 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 183 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 177 184 Err(_) => Err(AuthError::AuthenticationFailed), 178 185 } 179 186 } else { ··· 181 188 Ok(user) => Ok(BearerAuth(user)), 182 189 Err(TokenValidationError::AccountDeactivated) => Err(AuthError::AccountDeactivated), 183 190 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 191 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 184 192 Err(_) => Err(AuthError::AuthenticationFailed), 185 193 } 186 194 } ··· 224 232 { 225 233 Ok(user) => Ok(BearerAuthAllowDeactivated(user)), 226 234 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 235 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 227 236 Err(_) => Err(AuthError::AuthenticationFailed), 228 237 } 229 238 } else { ··· 236 245 { 237 246 Ok(user) => Ok(BearerAuthAllowDeactivated(user)), 238 247 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 248 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 239 249 Err(_) => Err(AuthError::AuthenticationFailed), 240 250 } 241 251 } ··· 284 294 Err(TokenValidationError::AccountTakedown) => { 285 295 return Err(AuthError::AccountTakedown); 286 296 } 297 + Err(TokenValidationError::TokenExpired) => { 298 + return Err(AuthError::TokenExpired); 299 + } 287 300 Err(_) => return Err(AuthError::AuthenticationFailed), 288 301 } 289 302 } else { ··· 294 307 } 295 308 Err(TokenValidationError::AccountTakedown) => { 296 309 return Err(AuthError::AccountTakedown); 310 + } 311 + Err(TokenValidationError::TokenExpired) => { 312 + return Err(AuthError::TokenExpired); 297 313 } 298 314 Err(_) => return Err(AuthError::AuthenticationFailed), 299 315 }
+51 -40
src/auth/mod.rs
··· 28 28 create_service_token, 29 29 }; 30 30 pub use verify::{ 31 - get_did_from_token, get_jti_from_token, verify_access_token, verify_refresh_token, verify_token, 31 + TokenVerifyError, get_did_from_token, get_jti_from_token, verify_access_token, 32 + verify_access_token_typed, verify_refresh_token, verify_token, 32 33 }; 33 34 34 35 const KEY_CACHE_TTL_SECS: u64 = 300; ··· 40 41 AccountTakedown, 41 42 KeyDecryptionFailed, 42 43 AuthenticationFailed, 44 + TokenExpired, 43 45 } 44 46 45 47 impl fmt::Display for TokenValidationError { ··· 49 51 Self::AccountTakedown => write!(f, "AccountTakedown"), 50 52 Self::KeyDecryptionFailed => write!(f, "KeyDecryptionFailed"), 51 53 Self::AuthenticationFailed => write!(f, "AuthenticationFailed"), 54 + Self::TokenExpired => write!(f, "ExpiredToken"), 52 55 } 53 56 } 54 57 } ··· 193 196 return Err(TokenValidationError::AccountTakedown); 194 197 } 195 198 196 - if let Ok(token_data) = verify_access_token(token, &decrypted_key) { 197 - let jti = &token_data.claims.jti; 198 - let session_cache_key = format!("auth:session:{}:{}", did, jti); 199 - let mut session_valid = false; 199 + match verify_access_token_typed(token, &decrypted_key) { 200 + Ok(token_data) => { 201 + let jti = &token_data.claims.jti; 202 + let session_cache_key = format!("auth:session:{}:{}", did, jti); 203 + let mut session_valid = false; 200 204 201 - if let Some(c) = cache { 202 - if let Some(cached_value) = c.get(&session_cache_key).await { 203 - session_valid = cached_value == "1"; 204 - crate::metrics::record_auth_cache_hit("session"); 205 - } else { 206 - crate::metrics::record_auth_cache_miss("session"); 205 + if let Some(c) = cache { 206 + if let Some(cached_value) = c.get(&session_cache_key).await { 207 + session_valid = cached_value == "1"; 208 + crate::metrics::record_auth_cache_hit("session"); 209 + } else { 210 + crate::metrics::record_auth_cache_miss("session"); 211 + } 207 212 } 208 - } 209 213 210 - if !session_valid { 211 - let session_exists = sqlx::query_scalar!( 212 - "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()", 213 - did, 214 - jti 215 - ) 216 - .fetch_optional(db) 217 - .await 218 - .ok() 219 - .flatten(); 214 + if !session_valid { 215 + let session_exists = sqlx::query_scalar!( 216 + "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()", 217 + did, 218 + jti 219 + ) 220 + .fetch_optional(db) 221 + .await 222 + .ok() 223 + .flatten(); 220 224 221 - session_valid = session_exists.is_some(); 225 + session_valid = session_exists.is_some(); 226 + 227 + if session_valid && let Some(c) = cache { 228 + let _ = c 229 + .set( 230 + &session_cache_key, 231 + "1", 232 + Duration::from_secs(SESSION_CACHE_TTL_SECS), 233 + ) 234 + .await; 235 + } 236 + } 222 237 223 - if session_valid && let Some(c) = cache { 224 - let _ = c 225 - .set( 226 - &session_cache_key, 227 - "1", 228 - Duration::from_secs(SESSION_CACHE_TTL_SECS), 229 - ) 230 - .await; 238 + if session_valid { 239 + return Ok(AuthenticatedUser { 240 + did: did.clone(), 241 + key_bytes: Some(decrypted_key), 242 + is_oauth: false, 243 + is_admin, 244 + scope: None, 245 + }); 231 246 } 232 247 } 233 - 234 - if session_valid { 235 - return Ok(AuthenticatedUser { 236 - did: did.clone(), 237 - key_bytes: Some(decrypted_key), 238 - is_oauth: false, 239 - is_admin, 240 - scope: None, 241 - }); 248 + Err(verify::TokenVerifyError::Expired) => { 249 + return Err(TokenValidationError::TokenExpired); 242 250 } 251 + Err(verify::TokenVerifyError::Invalid) => {} 243 252 } 244 253 } 245 254 } ··· 283 292 is_admin: oauth_token.is_admin, 284 293 scope: oauth_info.scope, 285 294 }); 295 + } else { 296 + return Err(TokenValidationError::TokenExpired); 286 297 } 287 298 } 288 299
+100
src/auth/verify.rs
··· 10 10 use hmac::{Hmac, Mac}; 11 11 use k256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Verifier}; 12 12 use sha2::Sha256; 13 + use std::fmt; 13 14 use subtle::ConstantTimeEq; 14 15 15 16 type HmacSha256 = Hmac<Sha256>; 17 + 18 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 + pub enum TokenVerifyError { 20 + Expired, 21 + Invalid, 22 + } 23 + 24 + impl fmt::Display for TokenVerifyError { 25 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 + match self { 27 + Self::Expired => write!(f, "Token expired"), 28 + Self::Invalid => write!(f, "Token invalid"), 29 + } 30 + } 31 + } 32 + 33 + impl std::error::Error for TokenVerifyError {} 16 34 17 35 pub fn get_did_from_token(token: &str) -> Result<String, String> { 18 36 let parts: Vec<&str> = token.split('.').collect(); ··· 228 246 let token_scope = claims.scope.as_deref().unwrap_or(""); 229 247 if !scopes.contains(&token_scope) { 230 248 return Err(anyhow!("Invalid token scope: {}", token_scope)); 249 + } 250 + } 251 + 252 + Ok(TokenData { claims }) 253 + } 254 + 255 + pub fn verify_access_token_typed( 256 + token: &str, 257 + key_bytes: &[u8], 258 + ) -> Result<TokenData<Claims>, TokenVerifyError> { 259 + verify_token_typed_internal( 260 + token, 261 + key_bytes, 262 + Some(TOKEN_TYPE_ACCESS), 263 + Some(&[SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED]), 264 + ) 265 + } 266 + 267 + fn verify_token_typed_internal( 268 + token: &str, 269 + key_bytes: &[u8], 270 + expected_typ: Option<&str>, 271 + allowed_scopes: Option<&[&str]>, 272 + ) -> Result<TokenData<Claims>, TokenVerifyError> { 273 + let parts: Vec<&str> = token.split('.').collect(); 274 + if parts.len() != 3 { 275 + return Err(TokenVerifyError::Invalid); 276 + } 277 + 278 + let header_b64 = parts[0]; 279 + let claims_b64 = parts[1]; 280 + let signature_b64 = parts[2]; 281 + 282 + let Ok(header_bytes) = URL_SAFE_NO_PAD.decode(header_b64) else { 283 + return Err(TokenVerifyError::Invalid); 284 + }; 285 + 286 + let Ok(header) = serde_json::from_slice::<Header>(&header_bytes) else { 287 + return Err(TokenVerifyError::Invalid); 288 + }; 289 + 290 + if let Some(expected) = expected_typ 291 + && header.typ != expected 292 + { 293 + return Err(TokenVerifyError::Invalid); 294 + } 295 + 296 + let Ok(signature_bytes) = URL_SAFE_NO_PAD.decode(signature_b64) else { 297 + return Err(TokenVerifyError::Invalid); 298 + }; 299 + 300 + let Ok(signature) = Signature::from_slice(&signature_bytes) else { 301 + return Err(TokenVerifyError::Invalid); 302 + }; 303 + 304 + let Ok(signing_key) = SigningKey::from_slice(key_bytes) else { 305 + return Err(TokenVerifyError::Invalid); 306 + }; 307 + let verifying_key = VerifyingKey::from(&signing_key); 308 + 309 + let message = format!("{}.{}", header_b64, claims_b64); 310 + if verifying_key.verify(message.as_bytes(), &signature).is_err() { 311 + return Err(TokenVerifyError::Invalid); 312 + } 313 + 314 + let Ok(claims_bytes) = URL_SAFE_NO_PAD.decode(claims_b64) else { 315 + return Err(TokenVerifyError::Invalid); 316 + }; 317 + 318 + let Ok(claims) = serde_json::from_slice::<Claims>(&claims_bytes) else { 319 + return Err(TokenVerifyError::Invalid); 320 + }; 321 + 322 + let now = Utc::now().timestamp() as usize; 323 + if claims.exp < now { 324 + return Err(TokenVerifyError::Expired); 325 + } 326 + 327 + if let Some(scopes) = allowed_scopes { 328 + let token_scope = claims.scope.as_deref().unwrap_or(""); 329 + if !scopes.contains(&token_scope) { 330 + return Err(TokenVerifyError::Invalid); 231 331 } 232 332 } 233 333