Our Personal Data Server from scratch!
0
fork

Configure Feed

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

fix(oauth): fail properly on non-expanded include scopes

+52 -17
+10 -1
crates/tranquil-oauth-server/src/endpoints/authorize.rs
··· 1432 1432 requested_scope_str.to_string() 1433 1433 }; 1434 1434 1435 - let expanded_scope_str = expand_include_scopes(&effective_scope_str).await; 1435 + let expanded_scope_str = match expand_include_scopes(&effective_scope_str).await { 1436 + Ok(s) => s, 1437 + Err(e) => { 1438 + return json_error( 1439 + StatusCode::BAD_REQUEST, 1440 + "invalid_scope", 1441 + &format!("Failed to expand permission set: {e}"), 1442 + ); 1443 + } 1444 + }; 1436 1445 let requested_scopes: Vec<&str> = expanded_scope_str.split_whitespace().collect(); 1437 1446 let consent_client_id = ClientId::from(request_data.parameters.client_id.clone()); 1438 1447 let preferences = state
+5 -1
crates/tranquil-oauth-server/src/endpoints/token/grants.rs
··· 155 155 156 156 let final_scope = if let Some(ref scope) = raw_scope { 157 157 if scope.contains("include:") { 158 - Some(expand_include_scopes(scope).await) 158 + Some( 159 + expand_include_scopes(scope) 160 + .await 161 + .map_err(|e| OAuthError::InvalidScope(format!("Failed to expand permission set: {e}")))?, 162 + ) 159 163 } else { 160 164 raw_scope 161 165 }
+3 -2
crates/tranquil-pds/src/oauth/scopes/mod.rs
··· 1 1 pub use tranquil_scopes::{ 2 2 AccountAction, AccountAttr, AccountScope, BlobScope, IdentityAttr, IdentityScope, IncludeScope, 3 3 ParsedScope, RepoAction, RepoScope, RpcScope, SCOPE_DEFINITIONS, ScopeCategory, 4 - ScopeDefinition, ScopeError, ScopePermissions, expand_include_scopes, format_scope_for_display, 5 - get_required_scopes, get_scope_definition, is_valid_scope, parse_scope, parse_scope_string, 4 + ScopeDefinition, ScopeError, ScopeExpansionError, ScopePermissions, expand_include_scopes, 5 + format_scope_for_display, get_required_scopes, get_scope_definition, is_valid_scope, 6 + parse_scope, parse_scope_string, 6 7 };
+1 -1
crates/tranquil-scopes/src/lib.rs
··· 13 13 AccountAction, AccountAttr, AccountScope, BlobScope, IdentityAttr, IdentityScope, IncludeScope, 14 14 ParsedScope, RepoAction, RepoScope, RpcScope, parse_scope, parse_scope_string, 15 15 }; 16 - pub use permission_set::expand_include_scopes; 16 + pub use permission_set::{ScopeExpansionError, expand_include_scopes}; 17 17 pub use permissions::ScopePermissions;
+33 -12
crates/tranquil-scopes/src/permission_set.rs
··· 4 4 use std::collections::HashMap; 5 5 use std::sync::LazyLock; 6 6 use tokio::sync::RwLock; 7 - use tracing::{debug, warn}; 7 + use tracing::debug; 8 8 9 9 #[derive(Debug, thiserror::Error)] 10 10 pub enum ScopeExpansionError { ··· 73 73 aud: Option<String>, 74 74 } 75 75 76 - pub async fn expand_include_scopes(scope_string: &str) -> String { 76 + pub async fn expand_include_scopes( 77 + scope_string: &str, 78 + ) -> Result<String, ScopeExpansionError> { 77 79 let futures: Vec<_> = scope_string 78 80 .split_whitespace() 79 81 .map(|scope| async move { 80 82 match scope.strip_prefix("include:") { 81 83 Some(rest) => { 82 84 let (nsid_base, aud) = parse_include_scope(rest); 83 - expand_permission_set(nsid_base, aud) 84 - .await 85 - .unwrap_or_else(|e| { 86 - warn!(nsid = nsid_base, error = %e, "Failed to expand permission set, keeping original"); 87 - scope.to_string() 88 - }) 85 + expand_permission_set(nsid_base, aud).await 89 86 } 90 - None => scope.to_string(), 87 + None => Ok(scope.to_string()), 91 88 } 92 89 }) 93 90 .collect(); 94 91 95 - futures::future::join_all(futures).await.join(" ") 92 + futures::future::join_all(futures) 93 + .await 94 + .into_iter() 95 + .collect::<Result<Vec<String>, ScopeExpansionError>>() 96 + .map(|v| v.join(" ")) 96 97 } 97 98 98 99 fn parse_include_scope(rest: &str) -> (&str, Option<&str>) { ··· 553 554 554 555 #[tokio::test] 555 556 async fn test_expand_include_scopes_passthrough_non_include() { 556 - let result = expand_include_scopes("atproto transition:generic").await; 557 + let result = expand_include_scopes("atproto transition:generic") 558 + .await 559 + .unwrap(); 557 560 assert_eq!(result, "atproto transition:generic"); 558 561 } 559 562 560 563 #[tokio::test] 561 564 async fn test_expand_include_scopes_mixed_with_regular() { 562 - let result = expand_include_scopes("atproto repo:app.bsky.feed.post?action=create").await; 565 + let result = expand_include_scopes("atproto repo:app.bsky.feed.post?action=create") 566 + .await 567 + .unwrap(); 563 568 assert!(result.contains("atproto")); 564 569 assert!(result.contains("repo:app.bsky.feed.post?action=create")); 570 + } 571 + 572 + #[tokio::test] 573 + async fn test_expand_include_scopes_fails_on_unresolvable_nsid() { 574 + let result = 575 + expand_include_scopes("atproto include:nonexistent.fake.permissionSet").await; 576 + assert!(result.is_err()); 577 + } 578 + 579 + #[tokio::test] 580 + async fn test_expand_include_scopes_fails_even_with_valid_scopes_present() { 581 + let result = expand_include_scopes( 582 + "atproto include:nonexistent.fake.permissionSet repo:app.bsky.feed.post?action=create", 583 + ) 584 + .await; 585 + assert!(result.is_err()); 565 586 } 566 587 567 588 #[tokio::test]