release(v1.7.13-alpha): proxy app catalog server-side (CORS + CSP fix)
The Discover / Marketplace page fetched the app catalog directly from git.tx1138.com/lfg2025/app-catalog/raw/.../catalog.json in the browser. Two blockers hit the fleet simultaneously: (1) tx1138's Gitea doesn't emit Access-Control-Allow-Origin so the HTTPS fetch got CORS-blocked; (2) the HTTP IP-port fallback (http://23.182.128.160:3000/...) falls outside the node's `connect-src` CSP. Users saw the hardcoded fallback instead of the live catalog. Backend: new authenticated GET /api/app-catalog handler uses reqwest to pull catalog.json server-side (15s timeout) and returns it with application/json + 1h Cache-Control. Tries the HTTPS URL first, HTTP IP-port second. Frontend: curatedApps.ts now calls /api/app-catalog (same-origin, no CORS/CSP) with credentials included so the session cookie authenticates the proxy. Baked /catalog.json stays as the last resort. Artefacts: archipelago 0aaf7262…b979f22c 40371192 archipelago-frontend-1.7.13-alpha.tar.gz 27505811…efc6f4142 76982505 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,53 @@ impl ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Server-side fetch of the upstream app catalog so the browser can
|
||||
/// load it without fighting CORS (git.tx1138.com emits no ACAO) or
|
||||
/// CSP (the fallback IP-port URL isn't in `connect-src`). Tries the
|
||||
/// upstream URLs in the same order the frontend used, returns the
|
||||
/// first 2xx response. 15s total timeout.
|
||||
async fn handle_app_catalog_proxy() -> Result<Response<hyper::Body>> {
|
||||
const UPSTREAMS: &[&str] = &[
|
||||
"https://git.tx1138.com/lfg2025/app-catalog/raw/branch/main/catalog.json",
|
||||
"http://23.182.128.160:3000/lfg2025/app-catalog/raw/branch/main/catalog.json",
|
||||
];
|
||||
let client = match reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return Ok(build_response(
|
||||
hyper::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"text/plain",
|
||||
hyper::Body::from(format!("client build failed: {}", e)),
|
||||
));
|
||||
}
|
||||
};
|
||||
for url in UPSTREAMS {
|
||||
match client.get(*url).send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
if let Ok(bytes) = resp.bytes().await {
|
||||
return Ok(Response::builder()
|
||||
.status(hyper::StatusCode::OK)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Cache-Control", "public, max-age=3600")
|
||||
.body(hyper::Body::from(bytes))
|
||||
.unwrap_or_else(|_| {
|
||||
Response::new(hyper::Body::from("proxy response build failed"))
|
||||
}));
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(build_response(
|
||||
hyper::StatusCode::BAD_GATEWAY,
|
||||
"text/plain",
|
||||
hyper::Body::from("all upstream catalog URLs failed"),
|
||||
))
|
||||
}
|
||||
|
||||
/// Build a 401 Unauthorized JSON response.
|
||||
fn unauthorized() -> Response<hyper::Body> {
|
||||
let body = serde_json::json!({ "error": "Unauthorized" });
|
||||
@@ -352,6 +399,18 @@ impl ApiHandler {
|
||||
// Electrs status — unauthenticated (read-only sync status)
|
||||
(Method::GET, "/electrs-status") => Self::handle_electrs_status().await,
|
||||
|
||||
// App-catalog proxy — fetches catalog.json from the configured
|
||||
// upstream URLs server-side so the browser doesn't hit CORS
|
||||
// (git.tx1138.com has no ACAO header) or CSP (IP-port upstream
|
||||
// falls outside `connect-src`). Session-authenticated so only
|
||||
// the logged-in node owner can spin up fetches.
|
||||
(Method::GET, "/api/app-catalog") => {
|
||||
if !self.is_authenticated(&headers).await {
|
||||
return Ok(Self::unauthorized());
|
||||
}
|
||||
Self::handle_app_catalog_proxy().await
|
||||
}
|
||||
|
||||
// LND connect info — nginx validates session cookie (presence check),
|
||||
// backend is bound to 127.0.0.1 so only nginx can reach it.
|
||||
// No backend auth check here because the LND UI iframe fetches this
|
||||
|
||||
Reference in New Issue
Block a user