|
4 | 4 | //! including URLs and weights for load balancing. |
5 | 5 |
|
6 | 6 | use crate::constants::DEFAULT_RPC_WEIGHT; |
| 7 | +use crate::utils::mask_url; |
7 | 8 | use eyre::eyre; |
8 | 9 | use serde::{ |
9 | 10 | de::Error as DeError, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer, |
@@ -155,49 +156,6 @@ impl RpcConfig { |
155 | 156 | } |
156 | 157 | } |
157 | 158 |
|
158 | | -/// Masks a URL by showing only the scheme and host, hiding the path and query parameters. |
159 | | -/// |
160 | | -/// This is used to safely display RPC URLs in API responses without exposing |
161 | | -/// sensitive API keys that are often embedded in the URL path or query string. |
162 | | -/// |
163 | | -/// # Examples |
164 | | -/// - `https://eth-mainnet.g.alchemy.com/v2/abc123` → `https://eth-mainnet.g.alchemy.com/***` |
165 | | -/// - `https://mainnet.infura.io/v3/PROJECT_ID` → `https://mainnet.infura.io/***` |
166 | | -/// - `http://localhost:8545` → `http://localhost:8545` (no path to mask) |
167 | | -/// - `invalid-url` → `***` (fallback for unparseable URLs) |
168 | | -pub fn mask_url(url: &str) -> String { |
169 | | - // Find the scheme separator "://" |
170 | | - let Some(scheme_end) = url.find("://") else { |
171 | | - // No valid scheme, mask entirely for safety |
172 | | - return "***".to_string(); |
173 | | - }; |
174 | | - |
175 | | - // Find where the host ends (first "/" after "://") |
176 | | - let host_start = scheme_end + 3; // Skip "://" |
177 | | - let rest = &url[host_start..]; |
178 | | - |
179 | | - // Find the first "/" which marks the start of the path |
180 | | - if let Some(path_start) = rest.find('/') { |
181 | | - // Check if there's actually content in the path (not just "/") |
182 | | - let path_and_beyond = &rest[path_start..]; |
183 | | - if path_and_beyond.len() > 1 || url.contains('?') { |
184 | | - // There's a path or query to mask |
185 | | - let host_end = host_start + path_start; |
186 | | - format!("{}/***", &url[..host_end]) |
187 | | - } else { |
188 | | - // Just a trailing "/" with no real path content |
189 | | - url.to_string() |
190 | | - } |
191 | | - } else if url.contains('?') { |
192 | | - // No path but has query parameters - mask those |
193 | | - let query_start = url.find('?').unwrap(); |
194 | | - format!("{}?***", &url[..query_start]) |
195 | | - } else { |
196 | | - // No path or query to mask, return original |
197 | | - url.to_string() |
198 | | - } |
199 | | -} |
200 | | - |
201 | 159 | /// RPC configuration with masked URL for API responses. |
202 | 160 | /// |
203 | 161 | /// This type is used in API responses to prevent exposing sensitive API keys |
@@ -705,90 +663,6 @@ mod tests { |
705 | 663 | assert_eq!(urls[0].url, " https://rpc.example.com "); |
706 | 664 | } |
707 | 665 |
|
708 | | - // ========================================================================= |
709 | | - // Tests for mask_url function |
710 | | - // ========================================================================= |
711 | | - |
712 | | - #[test] |
713 | | - fn test_mask_url_alchemy_with_api_key() { |
714 | | - let url = "https://eth-mainnet.g.alchemy.com/v2/abc123xyz"; |
715 | | - let masked = super::mask_url(url); |
716 | | - assert_eq!(masked, "https://eth-mainnet.g.alchemy.com/***"); |
717 | | - } |
718 | | - |
719 | | - #[test] |
720 | | - fn test_mask_url_infura_with_project_id() { |
721 | | - let url = "https://mainnet.infura.io/v3/my-project-id"; |
722 | | - let masked = super::mask_url(url); |
723 | | - assert_eq!(masked, "https://mainnet.infura.io/***"); |
724 | | - } |
725 | | - |
726 | | - #[test] |
727 | | - fn test_mask_url_quicknode_with_api_key() { |
728 | | - let url = "https://my-node.quiknode.pro/secret-api-key/"; |
729 | | - let masked = super::mask_url(url); |
730 | | - assert_eq!(masked, "https://my-node.quiknode.pro/***"); |
731 | | - } |
732 | | - |
733 | | - #[test] |
734 | | - fn test_mask_url_localhost_no_path() { |
735 | | - // No path to mask, should return original |
736 | | - let url = "http://localhost:8545"; |
737 | | - let masked = super::mask_url(url); |
738 | | - assert_eq!(masked, "http://localhost:8545"); |
739 | | - } |
740 | | - |
741 | | - #[test] |
742 | | - fn test_mask_url_localhost_with_trailing_slash() { |
743 | | - // Just a trailing slash with no real path content |
744 | | - let url = "http://localhost:8545/"; |
745 | | - let masked = super::mask_url(url); |
746 | | - assert_eq!(masked, "http://localhost:8545/"); |
747 | | - } |
748 | | - |
749 | | - #[test] |
750 | | - fn test_mask_url_with_query_params() { |
751 | | - let url = "https://rpc.example.com/v1?api_key=secret123&network=mainnet"; |
752 | | - let masked = super::mask_url(url); |
753 | | - assert_eq!(masked, "https://rpc.example.com/***"); |
754 | | - } |
755 | | - |
756 | | - #[test] |
757 | | - fn test_mask_url_query_params_no_path() { |
758 | | - let url = "https://rpc.example.com?api_key=secret123"; |
759 | | - let masked = super::mask_url(url); |
760 | | - assert_eq!(masked, "https://rpc.example.com?***"); |
761 | | - } |
762 | | - |
763 | | - #[test] |
764 | | - fn test_mask_url_invalid_url_no_scheme() { |
765 | | - // Invalid URL without scheme should be fully masked for safety |
766 | | - let url = "invalid-url"; |
767 | | - let masked = super::mask_url(url); |
768 | | - assert_eq!(masked, "***"); |
769 | | - } |
770 | | - |
771 | | - #[test] |
772 | | - fn test_mask_url_empty_string() { |
773 | | - let url = ""; |
774 | | - let masked = super::mask_url(url); |
775 | | - assert_eq!(masked, "***"); |
776 | | - } |
777 | | - |
778 | | - #[test] |
779 | | - fn test_mask_url_with_port_and_path() { |
780 | | - let url = "https://rpc.example.com:8080/api/v1/secret"; |
781 | | - let masked = super::mask_url(url); |
782 | | - assert_eq!(masked, "https://rpc.example.com:8080/***"); |
783 | | - } |
784 | | - |
785 | | - #[test] |
786 | | - fn test_mask_url_ankr_with_api_key() { |
787 | | - let url = "https://rpc.ankr.com/eth/my-api-key-here"; |
788 | | - let masked = super::mask_url(url); |
789 | | - assert_eq!(masked, "https://rpc.ankr.com/***"); |
790 | | - } |
791 | | - |
792 | 666 | // ========================================================================= |
793 | 667 | // Tests for MaskedRpcConfig |
794 | 668 | // ========================================================================= |
|
0 commit comments