pds: compile bsky-specific proxy, CORS, & validation out under bsky features

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>
This commit is contained in:
Lewis
2026-08-16 17:15:23 +00:00
committed by Tangled
parent 18455f54f2
commit 0b8787d1de
6 changed files with 59 additions and 38 deletions
@@ -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,
+22 -20
View File
@@ -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::<Nsid>()
.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::<Nsid>()
.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,
+15 -12
View File
@@ -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,
+1
View File
@@ -1,5 +1,6 @@
pub use tranquil_types::*;
#[cfg(feature = "bsky")]
use std::sync::LazyLock;
#[cfg(feature = "bsky")]
+9 -6
View File
@@ -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"),
@@ -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<String, Value>,
@@ -211,6 +215,7 @@ fn check_post_banned_content(obj: &serde_json::Map<String, Value>) -> Result<(),
Ok(())
}
#[cfg(feature = "bsky")]
fn check_string_field(
obj: &serde_json::Map<String, Value>,
field: &str,