Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions crates/buzz-relay/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ pub struct Config {
pub require_auth_token: bool,
/// Comma-separated list of allowed CORS origins.
/// If empty, permissive CORS is used (dev mode).
/// Example: "tauri://localhost,http://localhost:3000"
/// Example: "tauri://localhost,http://tauri.localhost,https://tauri.localhost,http://localhost:3000"
///
/// First-party Desktop origins (`tauri://localhost`, `http://tauri.localhost`,
/// `https://tauri.localhost`) are always merged in when this list is
/// non-empty — Windows WebView2 uses `http://tauri.localhost`, and omitting
/// it produces a silent "Failed to fetch" on Join community.
pub cors_origins: Vec<String>,
/// Optional hex-encoded private key for the relay's signing keypair.
/// If absent, a fresh keypair is generated at startup.
Expand Down Expand Up @@ -270,6 +275,25 @@ pub struct Config {
pub serve_git_web_gui: bool,
}

/// Lockdown mode (non-empty CORS list) must still admit the fixed Desktop
/// WebView origins. Windows WebView2 speaks `http://tauri.localhost`;
/// macOS/Linux use `tauri://localhost`. An empty list stays empty so
/// permissive CORS (dev mode) is unchanged.
fn merge_desktop_cors_origins(cors_origins: &mut Vec<String>) {
if cors_origins.is_empty() {
return;
}
for origin in [
"tauri://localhost",
"http://tauri.localhost",
"https://tauri.localhost",
] {
if !cors_origins.iter().any(|existing| existing == origin) {
cors_origins.push(origin.to_string());
}
}
}

fn parse_bind_addr(raw: &str) -> Result<SocketAddr, ConfigError> {
raw.parse::<SocketAddr>()
.map_err(|e| ConfigError::InvalidBindAddr(e.to_string()))
Expand Down Expand Up @@ -606,12 +630,13 @@ impl Config {
);
}

let cors_origins = std::env::var("BUZZ_CORS_ORIGINS")
let mut cors_origins: Vec<String> = std::env::var("BUZZ_CORS_ORIGINS")
.unwrap_or_default()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
merge_desktop_cors_origins(&mut cors_origins);

let relay_private_key = std::env::var("BUZZ_RELAY_PRIVATE_KEY").ok();

Expand Down Expand Up @@ -1306,6 +1331,23 @@ mod tests {
);
}

#[test]
fn merge_desktop_cors_origins_fills_windows_webview_origin() {
let mut origins = vec!["https://buzz.example.com".to_string()];
merge_desktop_cors_origins(&mut origins);
assert!(origins.contains(&"https://buzz.example.com".to_string()));
assert!(origins.contains(&"tauri://localhost".to_string()));
assert!(origins.contains(&"http://tauri.localhost".to_string()));
assert!(origins.contains(&"https://tauri.localhost".to_string()));
}

#[test]
fn merge_desktop_cors_origins_leaves_empty_list_for_permissive_mode() {
let mut origins = Vec::new();
merge_desktop_cors_origins(&mut origins);
assert!(origins.is_empty());
}

#[test]
fn invalid_bind_addr_returns_error() {
assert!(matches!(
Expand Down
2 changes: 1 addition & 1 deletion deploy/compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ BUZZ_DOMAIN=buzz.example.com
RELAY_URL=wss://buzz.example.com
BUZZ_MEDIA_BASE_URL=https://buzz.example.com/media
BUZZ_MEDIA_SERVER_DOMAIN=buzz.example.com
BUZZ_CORS_ORIGINS=https://buzz.example.com
BUZZ_CORS_ORIGINS=https://buzz.example.com,tauri://localhost,http://tauri.localhost,https://tauri.localhost

# Production defaults. Closed relay mode requires RELAY_OWNER_PUBKEY and a stable relay key.
BUZZ_REQUIRE_AUTH_TOKEN=true
Expand Down