mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(payout_link): secure payout links using server side validations and client side headers (#5219)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -99,5 +99,21 @@ pub const MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH: u8 = 64;
|
||||
/// Minimum allowed length for MerchantReferenceId
|
||||
pub const MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH: u8 = 1;
|
||||
|
||||
/// Regex for matching a domain
|
||||
/// Eg -
|
||||
/// http://www.example.com
|
||||
/// https://www.example.com
|
||||
/// www.example.com
|
||||
/// example.io
|
||||
pub const STRICT_DOMAIN_REGEX: &str = r"^(https?://)?(([A-Za-z0-9][-A-Za-z0-9]\.)*[A-Za-z0-9][-A-Za-z0-9]*|(\d{1,3}\.){3}\d{1,3})+(:[0-9]{2,4})?$";
|
||||
|
||||
/// Regex for matching a wildcard domain
|
||||
/// Eg -
|
||||
/// *.example.com
|
||||
/// *.subdomain.domain.com
|
||||
/// *://example.com
|
||||
/// *example.com
|
||||
pub const WILDCARD_DOMAIN_REGEX: &str = r"^((\*|https?)?://)?((\*\.|[A-Za-z0-9][-A-Za-z0-9]*\.)*[A-Za-z0-9][-A-Za-z0-9]*|((\d{1,3}|\*)\.){3}(\d{1,3}|\*)|\*)(:\*|:[0-9]{2,4})?(/\*)?$";
|
||||
|
||||
/// Maximum allowed length for MerchantName
|
||||
pub const MAX_ALLOWED_MERCHANT_NAME_LENGTH: usize = 64;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! Common
|
||||
//! This module has common utilities for links in HyperSwitch
|
||||
|
||||
use std::{collections::HashSet, primitive::i64};
|
||||
|
||||
@ -13,10 +13,13 @@ use diesel::{
|
||||
};
|
||||
use error_stack::{report, ResultExt};
|
||||
use masking::Secret;
|
||||
use regex::Regex;
|
||||
#[cfg(feature = "logs")]
|
||||
use router_env::logger;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{errors::ParsingError, id_type, types::MinorUnit};
|
||||
use crate::{consts, errors::ParsingError, id_type, types::MinorUnit};
|
||||
|
||||
#[derive(
|
||||
Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq, FromSqlRow, AsExpression, ToSchema,
|
||||
@ -162,6 +165,8 @@ pub struct PayoutLinkData {
|
||||
pub amount: MinorUnit,
|
||||
/// Payout currency
|
||||
pub currency: enums::Currency,
|
||||
/// A list of allowed domains (glob patterns) where this link can be embedded / opened from
|
||||
pub allowed_domains: HashSet<String>,
|
||||
}
|
||||
|
||||
crate::impl_to_sql_from_sql_json!(PayoutLinkData);
|
||||
@ -209,3 +214,140 @@ pub struct EnabledPaymentMethod {
|
||||
#[schema(value_type = HashSet<PaymentMethodType>)]
|
||||
pub payment_method_types: HashSet<enums::PaymentMethodType>,
|
||||
}
|
||||
|
||||
/// Util function for validating a domain without any wildcard characters.
|
||||
pub fn validate_strict_domain(domain: &str) -> bool {
|
||||
Regex::new(consts::STRICT_DOMAIN_REGEX)
|
||||
.map(|regex| regex.is_match(domain))
|
||||
.map_err(|err| {
|
||||
let err_msg = format!("Invalid strict domain regex: {err:?}");
|
||||
#[cfg(feature = "logs")]
|
||||
logger::error!(err_msg);
|
||||
err_msg
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Util function for validating a domain with "*" wildcard characters.
|
||||
pub fn validate_wildcard_domain(domain: &str) -> bool {
|
||||
Regex::new(consts::WILDCARD_DOMAIN_REGEX)
|
||||
.map(|regex| regex.is_match(domain))
|
||||
.map_err(|err| {
|
||||
let err_msg = format!("Invalid strict domain regex: {err:?}");
|
||||
#[cfg(feature = "logs")]
|
||||
logger::error!(err_msg);
|
||||
err_msg
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod domain_tests {
|
||||
use regex::Regex;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_validate_strict_domain_regex() {
|
||||
assert!(
|
||||
Regex::new(consts::STRICT_DOMAIN_REGEX).is_ok(),
|
||||
"Strict domain regex is invalid"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_wildcard_domain_regex() {
|
||||
assert!(
|
||||
Regex::new(consts::WILDCARD_DOMAIN_REGEX).is_ok(),
|
||||
"Wildcard domain regex is invalid"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_strict_domain() {
|
||||
let valid_domains = vec![
|
||||
"example.com",
|
||||
"example.subdomain.com",
|
||||
"https://example.com:8080",
|
||||
"http://example.com",
|
||||
"example.com:8080",
|
||||
"example.com:443",
|
||||
"localhost:443",
|
||||
"127.0.0.1:443",
|
||||
];
|
||||
|
||||
for domain in valid_domains {
|
||||
assert!(
|
||||
validate_strict_domain(domain),
|
||||
"Could not validate strict domain: {}",
|
||||
domain
|
||||
);
|
||||
}
|
||||
|
||||
let invalid_domains = vec![
|
||||
"",
|
||||
"invalid.domain.",
|
||||
"not_a_domain",
|
||||
"http://example.com/path?query=1#fragment",
|
||||
"127.0.0.1.2:443",
|
||||
];
|
||||
|
||||
for domain in invalid_domains {
|
||||
assert!(
|
||||
!validate_strict_domain(domain),
|
||||
"Could not validate invalid strict domain: {}",
|
||||
domain
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_wildcard_domain() {
|
||||
let valid_domains = vec![
|
||||
"example.com",
|
||||
"example.subdomain.com",
|
||||
"https://example.com:8080",
|
||||
"http://example.com",
|
||||
"example.com:8080",
|
||||
"example.com:443",
|
||||
"localhost:443",
|
||||
"127.0.0.1:443",
|
||||
"*.com",
|
||||
"example.*.com",
|
||||
"example.com:*",
|
||||
"*:443",
|
||||
"localhost:*",
|
||||
"127.0.0.*:*",
|
||||
"*:*",
|
||||
];
|
||||
|
||||
for domain in valid_domains {
|
||||
assert!(
|
||||
validate_wildcard_domain(domain),
|
||||
"Could not validate wildcard domain: {}",
|
||||
domain
|
||||
);
|
||||
}
|
||||
|
||||
let invalid_domains = vec![
|
||||
"",
|
||||
"invalid.domain.",
|
||||
"not_a_domain",
|
||||
"http://example.com/path?query=1#fragment",
|
||||
"*.",
|
||||
".*",
|
||||
"example.com:*:",
|
||||
"*:443:",
|
||||
":localhost:*",
|
||||
"127.00.*:*",
|
||||
];
|
||||
|
||||
for domain in invalid_domains {
|
||||
assert!(
|
||||
!validate_wildcard_domain(domain),
|
||||
"Could not validate invalid wildcard domain: {}",
|
||||
domain
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user