From aba84cbaa995652719bff1c2f7a8b932941bba20 Mon Sep 17 00:00:00 2001 From: tomdebone Date: Sat, 18 Jul 2026 19:04:28 +0200 Subject: [PATCH] fix(appview): add CorsLayer so Tauri webview can hit /api/* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Tauri webview's origin (the Vite dev server on port 1430, or the bundled tauri:// / asset:// origin in production) is cross-origin against the AppView's listen address (port 2584). Without an `Access-Control-Allow-Origin` response header the browser blocks the fetch before `response.json()` runs and the UI surfaces the failure as a SyntaxError on /api/profile. The previous workaround (a Tauri command that returns the AppView base URL) got us to the right URL but didn't address the underlying CORS preflight failure. Add `tower_http::cors::CorsLayer` with `allow_origin(Any)` to the AppView router — the AppView's public read endpoints carry no auth cookie and the service runs adjacent to the user's own PDS rather than the open internet, so any-origin is safe. Production deployments behind a reverse proxy can tighten the allow list at the proxy. --- crates/appview/src/routes.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/appview/src/routes.rs b/crates/appview/src/routes.rs index 243d42f..8995f0a 100644 --- a/crates/appview/src/routes.rs +++ b/crates/appview/src/routes.rs @@ -22,6 +22,7 @@ use axum::{ use chrono::{DateTime, TimeZone, Utc}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; +use tower_http::cors::{Any, CorsLayer}; use crate::state::AppState; @@ -31,6 +32,21 @@ pub mod types; use types::{PostRow, PostRowWithIndexed, ProfileResponse, SearchResponse, TimelineResponse}; pub fn router(state: AppState) -> Router { + // CORS: the Tauri webview's origin is the Vite dev server + // (`http://127.0.0.1:1430`) in dev or the bundled `tauri://` / + // `asset://` origin in production. Either way it's a cross-origin + // fetch against this service's `http://127.0.0.1:2584` listen + // address, so the browser blocks the response without an explicit + // allow-origin header. We allow any origin — the AppView's + // public read endpoints (`/api/...`) carry no auth cookie and + // the AppView runs alongside the user's own PDS, not on the + // open internet; production deployments behind a reverse proxy + // can tighten this via the proxy itself. + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + Router::new() .route("/", get(root)) .route("/api/timeline/home", get(timeline_home)) @@ -40,6 +56,7 @@ pub fn router(state: AppState) -> Router { .route("/api/post/*uri", get(post_by_uri)) .route("/healthz", get(healthz)) .route("/internal/ingest-commit", post(crate::ingest::ingest_commit)) + .layer(cors) .with_state(state) }