diff --git a/Cargo.toml b/Cargo.toml index b998dc5..00bcbb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ dotenvy = "0.15" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1" +url = "2.5" [dev-dependencies] diff --git a/src/api/payments.rs b/src/api/payments.rs index 369f7e7..571e3c1 100644 --- a/src/api/payments.rs +++ b/src/api/payments.rs @@ -127,21 +127,24 @@ pub async fn create( )); } if let Some(url) = &body.webhook_url { + if url.len() > 2048{ + return Err(AppError::bad_request( + "invalid_webhook_url", + "webhook_url exceeds max length of 2048 characters" + )); + }; let parsed_url = reqwest::Url::parse(url).map_err(|_| { AppError::bad_request("invalid_webhook_url", "webhook_url is not a valid URL") })?; - if state.config.network == "public" { - if parsed_url.scheme() != "https" { - return Err(AppError::bad_request( - "invalid_webhook_url", - "webhook_url must be an HTTPS URL on public network", - )); - } - } else if parsed_url.scheme() != "https" && parsed_url.scheme() != "http" { + if !state.config.allowed_webhook_schemes.contains(&parsed_url.scheme().to_string()){ return Err(AppError::bad_request( "invalid_webhook_url", - "webhook_url must be an HTTP or HTTPS URL", + &format!( + "webhook_url scheme '{}' not allowed. Allowed schemes: {:?}", + parsed_url.scheme(), + state.config.allowed_webhook_schemes + ) )); } diff --git a/src/config.rs b/src/config.rs index b31a7d2..675eadf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,6 +84,7 @@ pub struct Config { pub webhook_secret: String, pub webhook_retry_attempts: u32, pub webhook_retry_delay_ms: u64, + pub allowed_webhook_schemes: Vec, /// Per-attempt timeout for outbound webhook POSTs, in seconds. Each /// delivery attempt is bounded independently, so a slow receiver can't /// hold up the retry loop (or the reconciler) for more than this value. @@ -140,6 +141,7 @@ pub struct Config { /// `408 Request Timeout`, so a slow client or a stuck handler can't tie up /// a connection indefinitely. Defaults to 30 seconds. pub request_timeout_secs: u64, + pub allowed_webhook_schemes: Vec } impl Config { @@ -153,6 +155,15 @@ impl Config { std::env::var("STELLAR_GATEWAY_PUBLIC").unwrap_or_else(|_| "UNCONFIGURED".to_string()); let gateway_secret = std::env::var("STELLAR_GATEWAY_SECRET").unwrap_or_default(); let webhook_secret = Self::validate_webhook_secret(std::env::var("WEBHOOK_SECRET"))?; + let allowed_webhook_schemes: Vec = { + let raw_schemes = std::env::var("ALLOWED_WEBHOOK_SCHEMES") + .unwrap_or_else(|_| "https".to_string()); + raw_schemes + .split(',) + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + }; let cors_allowed_origins: Vec = { let raw_origins: Vec = std::env::var("CORS_ALLOWED_ORIGINS") @@ -193,6 +204,7 @@ impl Config { } }, webhook_secret, + allowed_webhook_schemes, webhook_retry_attempts: parse_env("WEBHOOK_RETRY_ATTEMPTS", 3)?, webhook_retry_delay_ms: parse_env("WEBHOOK_RETRY_DELAY_MS", 5000)?, webhook_timeout_secs: parse_env("WEBHOOK_TIMEOUT_SECS", 10)?,