From 963b2971ec2f43b1c2f669a969c294fc1d291d3b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 26 Nov 2025 03:53:31 +0000 Subject: add cors support --- src/http/mod.rs | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/http/mod.rs b/src/http/mod.rs index c676bda..85b72f4 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -24,6 +24,19 @@ use base64::Engine; use crate::config::Config; use crate::git; +/// CORS headers required by GRASP-01 specification (lines 40-47) +const CORS_ALLOW_ORIGIN: &str = "*"; +const CORS_ALLOW_METHODS: &str = "GET, POST"; +const CORS_ALLOW_HEADERS: &str = "Content-Type"; + +/// Add CORS headers to a response builder +fn add_cors_headers(builder: hyper::http::response::Builder) -> hyper::http::response::Builder { + builder + .header("Access-Control-Allow-Origin", CORS_ALLOW_ORIGIN) + .header("Access-Control-Allow-Methods", CORS_ALLOW_METHODS) + .header("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS) +} + /// HTTP Service that serves both WebSocket (relay) and HTML landing page struct HttpService { relay: LocalRelay, @@ -47,12 +60,23 @@ impl Service> for HttpService { type Future = Pin> + Send>>; fn call(&self, req: Request) -> Self::Future { - let base = Response::builder().header("server", "ngit-grasp"); + let base = add_cors_headers(Response::builder().header("server", "ngit-grasp")); let path = req.uri().path().to_string(); let query = req.uri().query().map(|s| s.to_string()); let method = req.method().clone(); let git_data_path = self.config.git_data_path.clone(); + // Handle OPTIONS preflight requests (CORS) + // GRASP-01 spec line 47: Respond to OPTIONS with 204 No Content + if method == Method::OPTIONS { + return Box::pin(async move { + Ok(add_cors_headers(Response::builder().header("server", "ngit-grasp")) + .status(204) + .body(Full::new(Bytes::new())) + .unwrap()) + }); + } + // Check for Git HTTP requests first if let Some((npub, identifier, subpath)) = git::parse_git_url(&path) { let npub = npub.to_string(); @@ -104,11 +128,24 @@ impl Service> for HttpService { }; match result { - Ok(response) => Ok(response), + Ok(response) => { + // Add CORS headers to successful Git responses + let (parts, body) = response.into_parts(); + Ok(add_cors_headers(Response::builder() + .status(parts.status)) + .header("content-type", parts.headers.get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream")) + .header("cache-control", parts.headers.get("cache-control") + .and_then(|v| v.to_str().ok()) + .unwrap_or("no-cache")) + .body(body) + .unwrap()) + } Err(e) => { tracing::error!("Git handler error: {}", e); let error_msg = format!("Git error: {}", e); - Ok(Response::builder() + Ok(add_cors_headers(Response::builder()) .status(e.status_code()) .body(Full::new(Bytes::from(error_msg))) .unwrap()) @@ -133,10 +170,9 @@ impl Service> for HttpService { tracing::debug!("Serving NIP-11 relay information document to {}", self.remote); return Box::pin(async move { - Ok(base + Ok(add_cors_headers(Response::builder().header("server", "ngit-grasp")) .status(200) .header("content-type", "application/nostr+json") - .header("access-control-allow-origin", "*") .body(Full::new(Bytes::from(json))) .unwrap()) }); -- cgit v1.2.3