mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +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:
		| @ -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
	 Kashif
					Kashif