Bug: [FIX] create a CORS rule for hyperswitch




diff --git a/config/config.example.toml b/config/config.example.toml
index 9551fe4f7a2..2b189ea4022 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 04831376050..39bf7060b66 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 1bff54d8f43..d3b94f1e7e6 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 7aabf3bb56e..90c9aa94fb7 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 e9b4de40d27..a7e36921961 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 13c3ba23e1d..8ed2daef5f3 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<String>,
+    #[serde(default)]
+    pub wildcard_origin: bool,
+    pub max_age: usize,
+    #[serde(deserialize_with = "deserialize_hashset")]
+    pub allowed_methods: HashSet<String>,
+}
+
 #[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 655dca33384..21ef4037d81 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 07e12b0d3fd..9baa4484ee4 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")
-        })
-    } else {
+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);
 
-    FIXME : I don't know what to put here
-    .allowed_origin_fn(|origin, _req_head| origin.as_bytes().starts_with(b"http://localhost"))
+    if config.wildcard_origin {
+        cors = cors.allow_any_origin()
+    } else {
+        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 ed443c9e418..19bd28b8dbc 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 85dfec5232e..85cf404ed2e 100644
--- a/loadtest/config/development.toml
+++ b/loadtest/config/development.toml
@@ -254,6 +254,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"}

## Type of Change
<!-- Put an `x` in the boxes that apply -->
- [x] Enhancement

## Description
<!-- Describe your changes in detail -->
Adds cors rules to actix

### Additional Changes
- [x] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->
- Added `CorsSettings` config

## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
NA

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
- Configure allowed origins in `ROUTER__CORS__ORIGINS`.
- Add `Origin` header to that configured to your config and the request should return 200
```bash
curl --location 'http://localhost:8080/health' \
--header 'Origin: http://localhost:8080'
```
- And configure origin header to something else like `google.com`. It should return `Bad Request` with Response
`Origin is not allowed to make this request`

```bash
curl --location 'http://localhost:8080/health' \
--header 'Origin: https://google.com'
```

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code