diff --git a/src/main.rs b/src/main.rs index f8cbdc3c16..76c4b07c69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -770,7 +770,7 @@ async fn async_main() -> anyhow::Result<()> { .tunnel .public_url .clone() - .unwrap_or_else(|| format!("http://{}:{}", gw_config.host, gw_config.port)); + .unwrap_or_else(|| oauth_base_url(&gw_config.host, gw_config.port)); ext_mgr.enable_gateway_mode(gw_base).await; gw = gw.with_extension_manager(Arc::clone(ext_mgr)); } @@ -1441,3 +1441,45 @@ async fn async_main() -> anyhow::Result<()> { Ok(()) } + +/// Build the OAuth base URL from the gateway bind address and port. +/// +/// Unspecified addresses (`0.0.0.0`, `::`, `[::]`) are mapped to `localhost` +/// because they are valid bind addresses but not valid OAuth redirect hosts. +fn oauth_base_url(host: &str, port: u16) -> String { + let trimmed = host.trim_start_matches('[').trim_end_matches(']'); + let is_unspecified = trimmed + .parse::() + .is_ok_and(|ip| ip.is_unspecified()); + if is_unspecified { + format!("http://localhost:{}", port) + } else { + format!("http://{}:{}", host, port) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn oauth_base_url_maps_unspecified_to_localhost() { + assert_eq!(oauth_base_url("0.0.0.0", 3033), "http://localhost:3033"); + assert_eq!(oauth_base_url("::", 3033), "http://localhost:3033"); + assert_eq!(oauth_base_url("[::]", 3033), "http://localhost:3033"); + assert_eq!( + oauth_base_url("0:0:0:0:0:0:0:0", 3033), + "http://localhost:3033" + ); + } + + #[test] + fn oauth_base_url_preserves_explicit_host() { + assert_eq!(oauth_base_url("127.0.0.1", 3000), "http://127.0.0.1:3000"); + assert_eq!( + oauth_base_url("my-server.example.com", 8080), + "http://my-server.example.com:8080" + ); + assert_eq!(oauth_base_url("::1", 3000), "http://::1:3000"); + } +}