From bee0bba6f41b8957a198ed3413c8e34958428113 Mon Sep 17 00:00:00 2001 From: lewis Date: Thu, 29 Jan 2026 21:30:43 +0200 Subject: [PATCH] fix: some small bugs --- crates/tranquil-pds/src/api/proxy.rs | 78 ++++++++++++++++++---------- crates/tranquil-scopes/src/parser.rs | 34 ++++++++++-- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/crates/tranquil-pds/src/api/proxy.rs b/crates/tranquil-pds/src/api/proxy.rs index fce2100..7488ea0 100644 --- a/crates/tranquil-pds/src/api/proxy.rs +++ b/crates/tranquil-pds/src/api/proxy.rs @@ -218,7 +218,7 @@ async fn proxy_handler( ) { let token = extracted.token; let dpop_proof = crate::util::get_header_str(&headers, "DPoP"); - let http_uri = crate::util::build_full_url(&uri.to_string()); + let http_uri = crate::util::build_full_url(&format!("/xrpc{}", uri)); match crate::auth::validate_token_with_dpop( state.user_repo.as_ref(), @@ -243,40 +243,66 @@ async fn proxy_handler( return e; } - if let Some(key_bytes) = auth_user.key_bytes { - match crate::auth::create_service_token( - &auth_user.did, - &resolved.did, - method, - &key_bytes, - ) { - Ok(new_token) => { - if let Ok(val) = - axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token)) - { - auth_header_val = Some(val); + let key_bytes = match auth_user.key_bytes { + Some(kb) => kb, + None => { + match state.user_repo.get_user_info_by_did(&auth_user.did).await { + Ok(Some(info)) => match info.key_bytes { + Some(key_bytes_enc) => { + match crate::config::decrypt_key( + &key_bytes_enc, + info.encryption_version, + ) { + Ok(key) => key, + Err(e) => { + error!(error = ?e, "Failed to decrypt user key for proxy"); + return ApiError::UpstreamFailure.into_response(); + } + } + } + None => { + warn!(did = %auth_user.did, "User has no signing key for proxy"); + return ApiError::UpstreamFailure.into_response(); + } + }, + Ok(None) => { + warn!(did = %auth_user.did, "User not found for proxy service auth"); + return ApiError::UpstreamFailure.into_response(); + } + Err(e) => { + error!(error = ?e, "DB error fetching user key for proxy"); + return ApiError::UpstreamFailure.into_response(); } } - Err(e) => { - warn!("Failed to create service token: {:?}", e); + } + }; + + match crate::auth::create_service_token( + &auth_user.did, + &resolved.did, + method, + &key_bytes, + ) { + Ok(new_token) => { + if let Ok(val) = + axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token)) + { + auth_header_val = Some(val); } } + Err(e) => { + error!("Failed to create service token: {:?}", e); + return ApiError::UpstreamFailure.into_response(); + } } } Err(e) => { info!(error = ?e, "Proxy token validation failed, returning error to client"); - if matches!( - e, - crate::auth::TokenValidationError::OAuthTokenExpired - | crate::auth::TokenValidationError::TokenExpired - ) { - let mut response = ApiError::from(e).into_response(); - let nonce = crate::oauth::verify::generate_dpop_nonce(); - if let Ok(nonce_val) = nonce.parse() { - response.headers_mut().insert("DPoP-Nonce", nonce_val); - } - return response; + let mut response = ApiError::from(e).into_response(); + if let Ok(nonce_val) = crate::oauth::verify::generate_dpop_nonce().parse() { + response.headers_mut().insert("DPoP-Nonce", nonce_val); } + return response; } } } diff --git a/crates/tranquil-scopes/src/parser.rs b/crates/tranquil-scopes/src/parser.rs index dbc65a9..9193bf7 100644 --- a/crates/tranquil-scopes/src/parser.rs +++ b/crates/tranquil-scopes/src/parser.rs @@ -142,9 +142,10 @@ fn parse_query_params(query: &str) -> HashMap> { .split('&') .filter_map(|part| part.split_once('=')) .fold(HashMap::new(), |mut acc, (key, value)| { - acc.entry(key.to_string()) - .or_default() - .push(value.to_string()); + let decoded = urlencoding::decode(value) + .map(|s| s.into_owned()) + .unwrap_or_else(|_| value.to_string()); + acc.entry(key.to_string()).or_default().push(decoded); acc }) } @@ -480,4 +481,31 @@ mod tests { let scope4 = parse_scope("rpc:*?aud=did:web:api.bsky.app"); assert!(matches!(scope4, ParsedScope::Rpc(_))); } + + #[test] + fn test_url_encoded_aud_with_fragment() { + let scope = + parse_scope("include:app.bsky.authFullApp?aud=did:web:api.bsky.app%23bsky_appview"); + match scope { + ParsedScope::Include(i) => { + assert_eq!(i.nsid, "app.bsky.authFullApp"); + assert_eq!(i.aud, Some("did:web:api.bsky.app#bsky_appview".to_string())); + } + _ => panic!("Expected Include scope"), + } + + let scope2 = parse_scope( + "rpc:com.atproto.moderation.createReport?aud=did:web:api.bsky.app%23bsky_appview", + ); + match scope2 { + ParsedScope::Rpc(r) => { + assert_eq!( + r.lxm, + Some("com.atproto.moderation.createReport".to_string()) + ); + assert_eq!(r.aud, Some("did:web:api.bsky.app#bsky_appview".to_string())); + } + _ => panic!("Expected Rpc scope"), + } + } }