Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun

fix: some small bugs #12

merged opened by lewis.moe targeting main from fix/small-bugs

service token creation, and scope encoding

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mdljow4t6722
+83 -29
Diff #0
+52 -26
crates/tranquil-pds/src/api/proxy.rs
··· 218 218 ) { 219 219 let token = extracted.token; 220 220 let dpop_proof = crate::util::get_header_str(&headers, "DPoP"); 221 - let http_uri = crate::util::build_full_url(&uri.to_string()); 221 + let http_uri = crate::util::build_full_url(&format!("/xrpc{}", uri)); 222 222 223 223 match crate::auth::validate_token_with_dpop( 224 224 state.user_repo.as_ref(), ··· 243 243 return e; 244 244 } 245 245 246 - if let Some(key_bytes) = auth_user.key_bytes { 247 - match crate::auth::create_service_token( 248 - &auth_user.did, 249 - &resolved.did, 250 - method, 251 - &key_bytes, 252 - ) { 253 - Ok(new_token) => { 254 - if let Ok(val) = 255 - axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token)) 256 - { 257 - auth_header_val = Some(val); 246 + let key_bytes = match auth_user.key_bytes { 247 + Some(kb) => kb, 248 + None => { 249 + match state.user_repo.get_user_info_by_did(&auth_user.did).await { 250 + Ok(Some(info)) => match info.key_bytes { 251 + Some(key_bytes_enc) => { 252 + match crate::config::decrypt_key( 253 + &key_bytes_enc, 254 + info.encryption_version, 255 + ) { 256 + Ok(key) => key, 257 + Err(e) => { 258 + error!(error = ?e, "Failed to decrypt user key for proxy"); 259 + return ApiError::UpstreamFailure.into_response(); 260 + } 261 + } 262 + } 263 + None => { 264 + warn!(did = %auth_user.did, "User has no signing key for proxy"); 265 + return ApiError::UpstreamFailure.into_response(); 266 + } 267 + }, 268 + Ok(None) => { 269 + warn!(did = %auth_user.did, "User not found for proxy service auth"); 270 + return ApiError::UpstreamFailure.into_response(); 271 + } 272 + Err(e) => { 273 + error!(error = ?e, "DB error fetching user key for proxy"); 274 + return ApiError::UpstreamFailure.into_response(); 258 275 } 259 276 } 260 - Err(e) => { 261 - warn!("Failed to create service token: {:?}", e); 277 + } 278 + }; 279 + 280 + match crate::auth::create_service_token( 281 + &auth_user.did, 282 + &resolved.did, 283 + method, 284 + &key_bytes, 285 + ) { 286 + Ok(new_token) => { 287 + if let Ok(val) = 288 + axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token)) 289 + { 290 + auth_header_val = Some(val); 262 291 } 263 292 } 293 + Err(e) => { 294 + error!("Failed to create service token: {:?}", e); 295 + return ApiError::UpstreamFailure.into_response(); 296 + } 264 297 } 265 298 } 266 299 Err(e) => { 267 300 info!(error = ?e, "Proxy token validation failed, returning error to client"); 268 - if matches!( 269 - e, 270 - crate::auth::TokenValidationError::OAuthTokenExpired 271 - | crate::auth::TokenValidationError::TokenExpired 272 - ) { 273 - let mut response = ApiError::from(e).into_response(); 274 - let nonce = crate::oauth::verify::generate_dpop_nonce(); 275 - if let Ok(nonce_val) = nonce.parse() { 276 - response.headers_mut().insert("DPoP-Nonce", nonce_val); 277 - } 278 - return response; 301 + let mut response = ApiError::from(e).into_response(); 302 + if let Ok(nonce_val) = crate::oauth::verify::generate_dpop_nonce().parse() { 303 + response.headers_mut().insert("DPoP-Nonce", nonce_val); 279 304 } 305 + return response; 280 306 } 281 307 } 282 308 }
+31 -3
crates/tranquil-scopes/src/parser.rs
··· 142 142 .split('&') 143 143 .filter_map(|part| part.split_once('=')) 144 144 .fold(HashMap::new(), |mut acc, (key, value)| { 145 - acc.entry(key.to_string()) 146 - .or_default() 147 - .push(value.to_string()); 145 + let decoded = urlencoding::decode(value) 146 + .map(|s| s.into_owned()) 147 + .unwrap_or_else(|_| value.to_string()); 148 + acc.entry(key.to_string()).or_default().push(decoded); 148 149 acc 149 150 }) 150 151 } ··· 480 481 let scope4 = parse_scope("rpc:*?aud=did:web:api.bsky.app"); 481 482 assert!(matches!(scope4, ParsedScope::Rpc(_))); 482 483 } 484 + 485 + #[test] 486 + fn test_url_encoded_aud_with_fragment() { 487 + let scope = 488 + parse_scope("include:app.bsky.authFullApp?aud=did:web:api.bsky.app%23bsky_appview"); 489 + match scope { 490 + ParsedScope::Include(i) => { 491 + assert_eq!(i.nsid, "app.bsky.authFullApp"); 492 + assert_eq!(i.aud, Some("did:web:api.bsky.app#bsky_appview".to_string())); 493 + } 494 + _ => panic!("Expected Include scope"), 495 + } 496 + 497 + let scope2 = parse_scope( 498 + "rpc:com.atproto.moderation.createReport?aud=did:web:api.bsky.app%23bsky_appview", 499 + ); 500 + match scope2 { 501 + ParsedScope::Rpc(r) => { 502 + assert_eq!( 503 + r.lxm, 504 + Some("com.atproto.moderation.createReport".to_string()) 505 + ); 506 + assert_eq!(r.aud, Some("did:web:api.bsky.app#bsky_appview".to_string())); 507 + } 508 + _ => panic!("Expected Rpc scope"), 509 + } 510 + } 483 511 }

History

1 round 0 comments
sign up or login to add to the discussion
lewis.moe submitted #0
1 commit
expand
fix: some small bugs
expand 0 comments
pull request successfully merged