mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
The integration harness dropped response headers (returned only status + JSON body),
which is exactly why CORS/security-header behavior couldn't be tested. Add:
- request_full(method, uri, extra_headers, body) -> (status, HeaderMap, body):
sends arbitrary request headers (e.g. an Origin) and returns response headers.
- try_app_with(|cfg| ...): tweak the validated Config before building the router
(e.g. restrict cors_allowed_origins). try_app now delegates to it.
New tests/cors.rs proves the well-known fix end-to-end: with the API CORS restricted
to one origin, /.well-known/nostr.json still serves ACAO: * to a foreign origin
(federation works) while /api/v1/* does NOT — the two policies are genuinely
distinct. Verified against Postgres (passes); existing auth_flow/capabilities unaffected.
65 lines
2.1 KiB
Rust
65 lines
2.1 KiB
Rust
//! CORS policy per route group. The NIP-05 well-known directory is public data that
|
|
//! MUST be cross-origin fetchable for federation; the authenticated API honors the
|
|
//! operator's `CORS_ALLOWED_ORIGINS`. Regression guard for the per-route CORS split
|
|
//! in `build_router` (a single global layer used to let a restrictive API policy
|
|
//! silently break NIP-05 resolution).
|
|
|
|
mod common;
|
|
|
|
use axum::http::StatusCode;
|
|
|
|
fn acao(headers: &axum::http::HeaderMap) -> Option<String> {
|
|
headers
|
|
.get("access-control-allow-origin")
|
|
.and_then(|v| v.to_str().ok())
|
|
.map(str::to_string)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn nip05_well_known_is_cross_origin_even_when_api_cors_restricted() {
|
|
// Operator restricts the API to their own frontend only.
|
|
let app = match common::try_app_with(|cfg| {
|
|
cfg.cors_allowed_origins = vec!["https://app.smirk.cash".to_string()];
|
|
})
|
|
.await
|
|
{
|
|
Some(a) => a,
|
|
None => {
|
|
eprintln!("skipping: TEST_DATABASE_URL not set");
|
|
return;
|
|
}
|
|
};
|
|
|
|
// The well-known, fetched from a FOREIGN origin (a Goblin domain), must be `*`.
|
|
let (status, headers, _body) = app
|
|
.request_full(
|
|
"GET",
|
|
"/.well-known/nostr.json?name=whoever",
|
|
&[("origin", "https://goblin.st")],
|
|
None,
|
|
)
|
|
.await;
|
|
assert_eq!(status, StatusCode::OK);
|
|
assert_eq!(
|
|
acao(&headers).as_deref(),
|
|
Some("*"),
|
|
"NIP-05 directory must serve ACAO: * so any domain can resolve names (federation)"
|
|
);
|
|
|
|
// The restricted API, from that same foreign origin, must NOT get `*` — proving
|
|
// the two CORS policies are genuinely distinct (not both permissive).
|
|
let (_s, api_headers, _b) = app
|
|
.request_full(
|
|
"GET",
|
|
"/api/v1/capabilities",
|
|
&[("origin", "https://goblin.st")],
|
|
None,
|
|
)
|
|
.await;
|
|
assert_ne!(
|
|
acao(&api_headers).as_deref(),
|
|
Some("*"),
|
|
"the API must honor the operator's restricted CORS, not fall back to *"
|
|
);
|
|
}
|