From c00ffe8d116d2a2369a10aadf4660df8c052ab22 Mon Sep 17 00:00:00 2001 From: rustmailer Date: Sun, 7 Dec 2025 00:03:21 +0800 Subject: [PATCH] feat(cors): remove default value for BICHON_CORS_ORIGINS and allow all origins when unset - Changed behavior so that when BICHON_CORS_ORIGINS is not configured, CORS now allows any origin. - Added debug logging to print incoming Origin and configured origins to help users diagnose CORS misconfiguration issues. --- src/modules/rest/mod.rs | 47 +++++++++++++++++++++++++------------ src/modules/settings/cli.rs | 4 +--- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/modules/rest/mod.rs b/src/modules/rest/mod.rs index 51f1f33..2b498eb 100644 --- a/src/modules/rest/mod.rs +++ b/src/modules/rest/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use crate::modules::common::error::ErrorCapture; use crate::modules::common::log::Tracing; use crate::modules::common::tls::rustls_config; @@ -33,13 +32,14 @@ use crate::modules::common::timeout::{Timeout, TIMEOUT_HEADER}; use crate::raise_error; use api::create_openapi_service; use assets::FrontEndAssets; -use http::HeaderValue; +use http::{HeaderValue, Method}; use poem::endpoint::EmbeddedFilesEndpoint; use poem::listener::{Listener, TcpListener}; use poem::middleware::{CatchPanic, Compression, SetHeader}; use poem::{endpoint::EmbeddedFileEndpoint, middleware::Cors, EndpointExt, Route, Server}; use poem::{get, post}; use public::oauth2::oauth2_callback; +use std::collections::HashSet; use std::time::Duration; pub mod api; @@ -62,7 +62,7 @@ pub async fn start_http_server() -> BichonResult<()> { }; let api_service = create_openapi_service() - .summary("A self-hosted IMAP/SMTP middleware designed for developers"); + .summary("A lightweight, high-performance Rust email archiver with WebUI"); let swagger = api_service.swagger_ui(); let redoc = api_service.redoc(); @@ -78,10 +78,35 @@ pub async fn start_http_server() -> BichonResult<()> { .with(Timeout) .with(Tracing); - let mut cors_origins = SETTINGS.bichon_cors_origins.clone(); - if cors_origins.is_empty() { - cors_origins = ["*".to_string()].into_iter().collect(); - } + let cors_origins: Option> = + SETTINGS.bichon_cors_origins.clone(); + + let cors_origins: Vec = cors_origins.unwrap_or_default().into_iter().collect(); + + let cors = Cors::new() + .allow_origins_fn(move |origin| { + tracing::debug!("CORS: Incoming Origin = {:?}", origin); + tracing::debug!("CORS: Configured origins = {:?}", cors_origins); + if cors_origins.is_empty() { + tracing::debug!("CORS: No origins configured, allowing all"); + return true; + } + cors_origins.iter().any(|o| o == origin) + }) + //.allow_origins(cors_origins) + .allow_credentials(true) + .allow_methods(&[ + Method::GET, + Method::POST, + Method::PUT, + Method::DELETE, + Method::OPTIONS, + Method::HEAD, + Method::PATCH, + ]) + .allow_headers(vec!["Content-Type", "Authorization", TIMEOUT_HEADER]) + .expose_headers(vec!["Accept"]) + .max_age(SETTINGS.bichon_cors_max_age); let cache_static = || { SetHeader::new().overriding( @@ -90,14 +115,6 @@ pub async fn start_http_server() -> BichonResult<()> { ) }; - let cors = Cors::new() - .allow_origins(cors_origins) - .allow_credentials(true) - .allow_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"]) - .allow_headers(vec!["Content-Type", "Authorization", TIMEOUT_HEADER]) - .expose_headers(vec!["Accept"]) - .max_age(SETTINGS.bichon_cors_max_age); - let route = Route::new() .nest("/api-docs/swagger", swagger) .nest("/api-docs/redoc", redoc) diff --git a/src/modules/settings/cli.rs b/src/modules/settings/cli.rs index 4a8ad5f..0f99b18 100644 --- a/src/modules/settings/cli.rs +++ b/src/modules/settings/cli.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use clap::{builder::ValueParser, Parser, ValueEnum}; use std::{collections::HashSet, env, fmt, path::PathBuf, sync::LazyLock}; @@ -77,7 +76,6 @@ pub struct Settings { /// CORS allowed origins (default: "*") #[clap( long, - default_value = "http://localhost:5173, http://localhost:15630, *", env, help = "Set the allowed CORS origins (comma-separated list, e.g., \"https://example.com, https://another.com\")", value_parser = ValueParser::new(|s: &str| -> Result, String> { @@ -88,7 +86,7 @@ pub struct Settings { Ok(set) }) )] - pub bichon_cors_origins: HashSet, + pub bichon_cors_origins: Option>, /// CORS max age in seconds (default: 86400) #[clap(