diff --git a/crates/tranquil-api/src/identity/provision.rs b/crates/tranquil-api/src/identity/provision.rs index 207d5c7..5d4d387 100644 --- a/crates/tranquil-api/src/identity/provision.rs +++ b/crates/tranquil-api/src/identity/provision.rs @@ -164,6 +164,13 @@ pub async fn resolve_signing_key( } } +#[cfg_attr( + not(feature = "bsky"), + expect( + unused_variables, + reason = "only the bsky block writes display_name into the default profile record" + ) +)] pub async fn sequence_new_account( state: &AppState, did: &Did, diff --git a/crates/tranquil-pds/src/api/proxy.rs b/crates/tranquil-pds/src/api/proxy.rs index 5e2af72..c51a2e3 100644 --- a/crates/tranquil-pds/src/api/proxy.rs +++ b/crates/tranquil-pds/src/api/proxy.rs @@ -335,27 +335,29 @@ async fn proxy_handler( }; // BSKY: getFeed must be audienced to the feed generator, not the AppView. - let (token_aud, token_lxm) = - if cfg!(feature = "bsky-support") && method == "app.bsky.feed.getFeed" { - match resolve_feed_generator_did(&resolved.url, query.as_deref()).await { - Some(feed_did) => ( - feed_did, - "app.bsky.feed.getFeedSkeleton" - .parse::() - .expect("getFeedSkeleton is a valid NSID"), - ), - None => { - warn!( - "getFeed proxy: could not resolve feed generator DID; refusing \ - to mint an AppView-audienced token" - ); - return ApiError::InvalidRequest("Could not resolve feed".into()) - .into_response(); - } + #[cfg(feature = "bsky-support")] + let (token_aud, token_lxm) = if method == "app.bsky.feed.getFeed" { + match resolve_feed_generator_did(&resolved.url, query.as_deref()).await { + Some(feed_did) => ( + feed_did, + "app.bsky.feed.getFeedSkeleton" + .parse::() + .expect("getFeedSkeleton is a valid NSID"), + ), + None => { + warn!( + "getFeed proxy refuses to mint an AppView-audienced token \ + because feed generator DID resolution failed" + ); + return ApiError::InvalidRequest("Couldn't resolve feed".into()) + .into_response(); } - } else { - (resolved.did.clone(), method_nsid.clone()) - }; + } + } else { + (resolved.did.clone(), method_nsid.clone()) + }; + #[cfg(not(feature = "bsky-support"))] + let (token_aud, token_lxm) = (resolved.did.clone(), method_nsid.clone()); match crate::auth::create_service_token( &auth_user.did, diff --git a/crates/tranquil-pds/src/lib.rs b/crates/tranquil-pds/src/lib.rs index 8bf5e31..a3b2bd7 100644 --- a/crates/tranquil-pds/src/lib.rs +++ b/crates/tranquil-pds/src/lib.rs @@ -35,7 +35,7 @@ use serde_json::json; use state::AppState; use tower::ServiceBuilder; use tower_http::{ - cors::{Any, CorsLayer}, + cors::{AllowHeaders, Any, CorsLayer}, services::{ServeDir, ServeFile}, }; pub use tranquil_db_traits::AccountStatus; @@ -106,17 +106,20 @@ pub fn app_with_routes(state: AppState, external: ExternalRoutes) -> Router { CorsLayer::new() .allow_origin(Any) .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) - .allow_headers([ - http::header::AUTHORIZATION, - http::header::CONTENT_TYPE, - http::header::CONTENT_ENCODING, - http::header::ACCEPT_ENCODING, - http::header::USER_AGENT, - util::HEADER_DPOP, - util::HEADER_ATPROTO_PROXY, - util::HEADER_ATPROTO_ACCEPT_LABELERS, - util::HEADER_X_BSKY_TOPICS, - ]) + .allow_headers(AllowHeaders::list( + [ + http::header::AUTHORIZATION, + http::header::CONTENT_TYPE, + http::header::CONTENT_ENCODING, + http::header::ACCEPT_ENCODING, + http::header::USER_AGENT, + util::HEADER_DPOP, + util::HEADER_ATPROTO_PROXY, + util::HEADER_ATPROTO_ACCEPT_LABELERS, + ] + .into_iter() + .chain(util::CORS_BSKY_ALLOW_HEADERS), + )) .expose_headers([ http::header::WWW_AUTHENTICATE, util::HEADER_DPOP_NONCE, diff --git a/crates/tranquil-pds/src/types.rs b/crates/tranquil-pds/src/types.rs index 9a184f2..5d1d0ed 100644 --- a/crates/tranquil-pds/src/types.rs +++ b/crates/tranquil-pds/src/types.rs @@ -1,5 +1,6 @@ pub use tranquil_types::*; +#[cfg(feature = "bsky")] use std::sync::LazyLock; #[cfg(feature = "bsky")] diff --git a/crates/tranquil-pds/src/util.rs b/crates/tranquil-pds/src/util.rs index 2b1a977..4492f30 100644 --- a/crates/tranquil-pds/src/util.rs +++ b/crates/tranquil-pds/src/util.rs @@ -89,6 +89,10 @@ pub const HEADER_ATPROTO_CONTENT_LABELERS: HeaderName = HeaderName::from_static("atproto-content-labelers"); #[cfg(feature = "bsky-support")] pub const HEADER_X_BSKY_TOPICS: HeaderName = HeaderName::from_static("x-bsky-topics"); +#[cfg(feature = "bsky-support")] +pub const CORS_BSKY_ALLOW_HEADERS: [HeaderName; 1] = [HEADER_X_BSKY_TOPICS]; +#[cfg(not(feature = "bsky-support"))] +pub const CORS_BSKY_ALLOW_HEADERS: [HeaderName; 0] = []; pub fn get_header_str( headers: &HeaderMap, @@ -250,11 +254,7 @@ pub fn build_full_url(path: &str) -> String { && (path.starts_with("/com.atproto.") // BSKY: Bluesky requires that the PDS implement some app.bsky.* endpoints so we need to deal with those here too. // TODO: surely we can figure out a way to do this more generically? - || (if cfg!(feature = "bsky-support") { - path.starts_with("/app.bsky.") - } else { - true - }) + || (cfg!(feature = "bsky-support") && path.starts_with("/app.bsky.")) || path.starts_with("/_")) { format!("/xrpc{path}") @@ -798,7 +798,10 @@ mod tests { ); assert_eq!( build_full_url("/app.bsky.feed.getTimeline"), - "https://example.com/xrpc/app.bsky.feed.getTimeline" + match cfg!(feature = "bsky-support") { + true => "https://example.com/xrpc/app.bsky.feed.getTimeline", + false => "https://example.com/app.bsky.feed.getTimeline", + } ); assert_eq!( build_full_url("/_health"), diff --git a/crates/tranquil-pds/src/validation/mod.rs b/crates/tranquil-pds/src/validation/mod.rs index b94ab83..fca4766 100644 --- a/crates/tranquil-pds/src/validation/mod.rs +++ b/crates/tranquil-pds/src/validation/mod.rs @@ -132,6 +132,10 @@ fn validate_preamble<'a>( Ok((record_type, obj)) } +#[cfg_attr( + not(feature = "bsky"), + expect(unused_variables, reason = "only bsky record checks read obj and rkey") +)] fn check_banned_content( record_type: &str, obj: &serde_json::Map, @@ -211,6 +215,7 @@ fn check_post_banned_content(obj: &serde_json::Map) -> Result<(), Ok(()) } +#[cfg(feature = "bsky")] fn check_string_field( obj: &serde_json::Map, field: &str,