Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
21 changes: 12 additions & 9 deletions src/api/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
));
}

Expand Down
12 changes: 12 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
/// 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.
Expand Down Expand Up @@ -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<String>
}

impl Config {
Expand All @@ -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<String> = {
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<String> = {
let raw_origins: Vec<String> = std::env::var("CORS_ALLOWED_ORIGINS")
Expand Down Expand Up @@ -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)?,
Expand Down
Loading