@@ -12,6 +12,7 @@ use crate::error::TrustedServerError;
1212use crate :: settings:: Settings ;
1313
1414const PERMUTIVE_API_BASE : & str = "https://api.permutive.com" ;
15+ const PERMUTIVE_SECURE_SIGNALS_BASE : & str = "https://secure-signals.permutive.app" ;
1516
1617/// Handles transparent proxying of Permutive API requests.
1718///
@@ -93,6 +94,86 @@ pub async fn handle_permutive_api_proxy(
9394 Ok ( permutive_response)
9495}
9596
97+ /// Handles transparent proxying of Permutive Secure Signals requests.
98+ ///
99+ /// This function:
100+ /// 1. Extracts the path after `/permutive/secure-signal/`
101+ /// 2. Preserves query parameters
102+ /// 3. Copies request headers and body
103+ /// 4. Forwards to `secure-signals.permutive.app`
104+ /// 5. Returns response transparently
105+ ///
106+ /// # Example Request Flow
107+ ///
108+ /// ```text
109+ /// Browser: GET /permutive/secure-signal/data/v1/cohorts
110+ /// ↓
111+ /// Trusted Server processes and forwards:
112+ /// ↓
113+ /// Permutive: GET https://secure-signals.permutive.app/data/v1/cohorts
114+ /// ```
115+ ///
116+ /// # Errors
117+ ///
118+ /// Returns a [`TrustedServerError`] if:
119+ /// - Path extraction fails
120+ /// - Backend communication fails
121+ /// - Request forwarding fails
122+ pub async fn handle_permutive_secure_signals_proxy (
123+ _settings : & Settings ,
124+ mut req : Request ,
125+ ) -> Result < Response , Report < TrustedServerError > > {
126+ let original_path = req. get_path ( ) ;
127+ let method = req. get_method ( ) ;
128+
129+ log:: info!(
130+ "Proxying Permutive Secure Signals request: {} {}" ,
131+ method,
132+ original_path
133+ ) ;
134+
135+ // Extract the path after /permutive/secure-signal
136+ let signals_path = original_path
137+ . strip_prefix ( "/permutive/secure-signal" )
138+ . ok_or_else ( || TrustedServerError :: PermutiveApi {
139+ message : format ! ( "Invalid Permutive Secure Signals path: {}" , original_path) ,
140+ } ) ?;
141+
142+ // Build the full Permutive Secure Signals URL with query parameters
143+ let permutive_url = build_secure_signals_url ( signals_path, & req) ?;
144+
145+ log:: info!( "Forwarding to Permutive Secure Signals: {}" , permutive_url) ;
146+
147+ // Create new request to Permutive
148+ let mut permutive_req = Request :: new ( method. clone ( ) , & permutive_url) ;
149+
150+ // Copy relevant headers
151+ copy_request_headers ( & req, & mut permutive_req) ;
152+
153+ // Copy body for POST requests
154+ if has_body ( method) {
155+ let body = req. take_body ( ) ;
156+ permutive_req. set_body ( body) ;
157+ }
158+
159+ // Get backend and forward request
160+ let backend_name = ensure_backend_from_url ( PERMUTIVE_SECURE_SIGNALS_BASE ) ?;
161+
162+ let permutive_response = permutive_req
163+ . send ( backend_name)
164+ . change_context ( TrustedServerError :: PermutiveApi {
165+ message : format ! ( "Failed to forward request to {}" , permutive_url) ,
166+ } ) ?;
167+
168+ log:: info!(
169+ "Permutive Secure Signals responded with status: {}" ,
170+ permutive_response. get_status( )
171+ ) ;
172+
173+ // Return response transparently
174+ Ok ( permutive_response)
175+ }
176+
96177/// Builds the full Permutive API URL including query parameters.
97178fn build_permutive_url (
98179 api_path : & str ,
@@ -111,6 +192,24 @@ fn build_permutive_url(
111192 Ok ( url)
112193}
113194
195+ /// Builds the full Permutive Secure Signals URL including query parameters.
196+ fn build_secure_signals_url (
197+ signals_path : & str ,
198+ req : & Request ,
199+ ) -> Result < String , Report < TrustedServerError > > {
200+ // Get query string if present
201+ let query = req
202+ . get_url ( )
203+ . query ( )
204+ . map ( |q| format ! ( "?{}" , q) )
205+ . unwrap_or_default ( ) ;
206+
207+ // Build full URL
208+ let url = format ! ( "{}{}{}" , PERMUTIVE_SECURE_SIGNALS_BASE , signals_path, query) ;
209+
210+ Ok ( url)
211+ }
212+
114213/// Copies relevant headers from the original request to the Permutive request.
115214fn copy_request_headers ( from : & Request , to : & mut Request ) {
116215 // Headers that should be forwarded to Permutive
@@ -193,4 +292,38 @@ mod tests {
193292 assert ! ( !has_body( & Method :: HEAD ) ) ;
194293 assert ! ( !has_body( & Method :: OPTIONS ) ) ;
195294 }
295+
296+ #[ test]
297+ fn test_secure_signals_path_extraction ( ) {
298+ let test_cases = vec ! [
299+ ( "/permutive/secure-signal/data/v1/cohorts" , "/data/v1/cohorts" ) ,
300+ ( "/permutive/secure-signal/v1/track" , "/v1/track" ) ,
301+ ( "/permutive/secure-signal/" , "/" ) ,
302+ ( "/permutive/secure-signal" , "" ) ,
303+ ] ;
304+
305+ for ( input, expected) in test_cases {
306+ let result = input. strip_prefix ( "/permutive/secure-signal" ) . unwrap_or ( "" ) ;
307+ assert_eq ! ( result, expected, "Failed for input: {}" , input) ;
308+ }
309+ }
310+
311+ #[ test]
312+ fn test_secure_signals_url_building ( ) {
313+ let signals_path = "/data/v1/cohorts" ;
314+ let expected = "https://secure-signals.permutive.app/data/v1/cohorts" ;
315+
316+ let url = format ! ( "{}{}" , PERMUTIVE_SECURE_SIGNALS_BASE , signals_path) ;
317+ assert_eq ! ( url, expected) ;
318+ }
319+
320+ #[ test]
321+ fn test_secure_signals_url_building_with_query ( ) {
322+ let signals_path = "/data/v1/cohorts" ;
323+ let query = "?key=123" ;
324+ let expected = "https://secure-signals.permutive.app/data/v1/cohorts?key=123" ;
325+
326+ let url = format ! ( "{}{}{}" , PERMUTIVE_SECURE_SIGNALS_BASE , signals_path, query) ;
327+ assert_eq ! ( url, expected) ;
328+ }
196329}
0 commit comments