diff --git a/config/config.example.toml b/config/config.example.toml index 4dc950d74d..3f1e789dd5 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -327,6 +327,12 @@ paypal = { currency = "USD,INR", country = "US" } key_id = "" # The AWS key ID used by the KMS SDK for decrypting data. region = "" # The AWS region used by the KMS SDK for decrypting data. +[cors] +max_age = 30 # Maximum time (in seconds) for which this CORS request may be cached. +origins = "http://localhost:8080" # List of origins that are allowed to make requests. +allowed_methods = "GET,POST,PUT,DELETE" # List of methods that are allowed +wildcard_origin = false # If true, allows any origin to make requests + # EmailClient configuration. Only applicable when the `email` feature flag is enabled. [email] sender_email = "example@example.com" # Sender email diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 0483137605..39bf7060b6 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -45,6 +45,12 @@ partner_id = "paypal_partner_id" [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = ["merchant_id_1", "merchant_id_2", "etc.,"] +[cors] +max_age = 30 # Maximum time (in seconds) for which this CORS request may be cached. +origins = "http://localhost:8080" # List of origins that are allowed to make requests. +allowed_methods = "GET,POST,PUT,DELETE" # List of methods that are allowed +wildcard_origin = false # If true, allows any origin to make requests + # EmailClient configuration. Only applicable when the `email` feature flag is enabled. [email] sender_email = "example@example.com" # Sender email diff --git a/config/development.toml b/config/development.toml index 34acb9ce48..ad5944c34c 100644 --- a/config/development.toml +++ b/config/development.toml @@ -233,6 +233,12 @@ port = 3000 host = "127.0.0.1" workers = 1 +[cors] +max_age = 30 +origins = "http://localhost:8080" +allowed_methods = "GET,POST,PUT,DELETE" +wildcard_origin = false + [email] sender_email = "example@example.com" aws_region = "" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index fa5c3bf5e6..4f1f34d1be 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -81,6 +81,11 @@ max_in_flight_commands = 5000 default_command_timeout = 0 max_feed_count = 200 +[cors] +max_age = 30 +origins = "http://localhost:8080" +allowed_methods = "GET,POST,PUT,DELETE" +wildcard_origin = false [refund] max_attempts = 10 diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 73985b2d57..cafaf8f2ab 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -19,6 +19,20 @@ impl Default for super::settings::Server { } } +impl Default for super::settings::CorsSettings { + fn default() -> Self { + Self { + origins: HashSet::from_iter(["http://localhost:8080".to_string()]), + allowed_methods: HashSet::from_iter( + ["GET", "PUT", "POST", "DELETE"] + .into_iter() + .map(ToString::to_string), + ), + wildcard_origin: false, + max_age: 30, + } + } +} impl Default for super::settings::Database { fn default() -> Self { Self { diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 13c3ba23e1..8ed2daef5f 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -107,6 +107,7 @@ pub struct Settings { pub dummy_connector: DummyConnector, #[cfg(feature = "email")] pub email: EmailSettings, + pub cors: CorsSettings, pub mandates: Mandates, pub required_fields: RequiredFields, pub delayed_session_response: DelayedSessionConfig, @@ -242,6 +243,17 @@ pub struct DummyConnector { pub discord_invite_url: String, } +#[derive(Debug, Deserialize, Clone)] +pub struct CorsSettings { + #[serde(default, deserialize_with = "deserialize_hashset")] + pub origins: HashSet, + #[serde(default)] + pub wildcard_origin: bool, + pub max_age: usize, + #[serde(deserialize_with = "deserialize_hashset")] + pub allowed_methods: HashSet, +} + #[derive(Debug, Deserialize, Clone)] pub struct Mandates { pub supported_payment_methods: SupportedPaymentMethodsForMandate, @@ -714,6 +726,8 @@ impl Settings { self.locker.validate()?; self.connectors.validate("connectors")?; + self.cors.validate()?; + self.scheduler .as_ref() .map(|scheduler_settings| scheduler_settings.validate()) diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index 655dca3338..21ef4037d8 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -124,6 +124,22 @@ impl super::settings::SupportedConnectors { } } +impl super::settings::CorsSettings { + pub fn validate(&self) -> Result<(), ApplicationError> { + common_utils::fp_utils::when(self.wildcard_origin && !self.origins.is_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Allowed Origins must be empty when wildcard origin is true".to_string(), + )) + })?; + + common_utils::fp_utils::when(!self.wildcard_origin && self.origins.is_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Allowed origins must not be empty. Please either enable wildcard origin or provide Allowed Origin".to_string(), + )) + }) + } +} + #[cfg(feature = "kv_store")] impl super::settings::DrainerSettings { pub fn validate(&self) -> Result<(), ApplicationError> { diff --git a/crates/router/src/cors.rs b/crates/router/src/cors.rs index 07e12b0d3f..9baa4484ee 100644 --- a/crates/router/src/cors.rs +++ b/crates/router/src/cors.rs @@ -1,19 +1,21 @@ // use actix_web::http::header; -pub fn cors() -> actix_cors::Cors { - actix_cors::Cors::permissive() // FIXME : Never use in production +use crate::configs::settings; - /* - .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"]) - .allowed_headers(vec![header::AUTHORIZATION, header::CONTENT_TYPE]); - if CONFIG.profile == "debug" { // --------->>> FIXME: It should be conditional - cors.allowed_origin_fn(|origin, _req_head| { - origin.as_bytes().starts_with(b"http://localhost") - }) +pub fn cors(config: settings::CorsSettings) -> actix_cors::Cors { + let allowed_methods = config.allowed_methods.iter().map(|s| s.as_str()); + + let mut cors = actix_cors::Cors::default() + .allowed_methods(allowed_methods) + .max_age(config.max_age); + + if config.wildcard_origin { + cors = cors.allow_any_origin() } else { - - FIXME : I don't know what to put here - .allowed_origin_fn(|origin, _req_head| origin.as_bytes().starts_with(b"http://localhost")) + for origin in &config.origins { + cors = cors.allowed_origin(origin); + } } - */ + + cors } diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index ed443c9e41..19bd28b8db 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -91,7 +91,7 @@ pub fn mk_app( InitError = (), >, > { - let mut server_app = get_application_builder(request_body_limit); + let mut server_app = get_application_builder(request_body_limit, state.conf.cors.clone()); #[cfg(feature = "dummy_connector")] { @@ -231,6 +231,7 @@ impl Stop for mpsc::Sender<()> { pub fn get_application_builder( request_body_limit: usize, + cors: settings::CorsSettings, ) -> actix_web::App< impl ServiceFactory< ServiceRequest, @@ -257,7 +258,7 @@ pub fn get_application_builder( )) .wrap(middleware::default_response_headers()) .wrap(middleware::RequestId) - .wrap(cors::cors()) + .wrap(cors::cors(cors)) // this middleware works only for Http1.1 requests .wrap(middleware::Http400RequestDetailsLogger) .wrap(middleware::LogSpanInitializer) diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index b480f27b3d..d9b5433ab6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -257,6 +257,12 @@ bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"} bank_redirect.giropay = {connector_list = "adyen,globalpay"} +[cors] +max_age = 30 +origins = "http://localhost:8080" +allowed_methods = "GET,POST,PUT,DELETE" +wildcard_origin = false + [mandates.update_mandate_supported] card.credit ={connector_list ="cybersource"} card.debit = {connector_list ="cybersource"}