Skip to content

Commit c338e7e

Browse files
host rewriting
1 parent 9ca2673 commit c338e7e

File tree

1 file changed

+148
-3
lines changed

1 file changed

+148
-3
lines changed

crates/common/src/integrations/lockr.rs

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
//!
33
//! This module provides transparent proxying for Lockr's SDK and API,
44
//! enabling first-party identity resolution while maintaining privacy controls.
5+
//!
6+
//! ## Host Rewriting
7+
//!
8+
//! The integration can rewrite the Lockr SDK JavaScript to replace the hardcoded
9+
//! API host with a relative URL pointing to the first-party proxy. This ensures
10+
//! all API calls from the SDK go through the trusted server instead of directly
11+
//! to Lockr's servers, improving privacy and enabling additional controls.
12+
//!
13+
//! The rewriting finds the obfuscated host assignment pattern in the SDK and
14+
//! replaces it with: `'host': '/integrations/lockr/api'`
515
616
use std::sync::Arc;
717

@@ -19,6 +29,7 @@ use crate::integrations::{
1929
IntegrationEndpoint, IntegrationProxy, IntegrationRegistration,
2030
};
2131
use crate::settings::{IntegrationConfig as IntegrationConfigTrait, Settings};
32+
use crate::streaming_replacer::StreamingReplacer;
2233

2334
const LOCKR_INTEGRATION_ID: &str = "lockr";
2435

@@ -51,6 +62,10 @@ pub struct LockrConfig {
5162
/// Whether to rewrite Lockr SDK URLs in HTML
5263
#[serde(default = "default_rewrite_sdk")]
5364
pub rewrite_sdk: bool,
65+
66+
/// Whether to rewrite the host variable in the Lockr SDK JavaScript
67+
#[serde(default = "default_rewrite_sdk_host")]
68+
pub rewrite_sdk_host: bool,
5469
}
5570

5671
impl IntegrationConfigTrait for LockrConfig {
@@ -85,6 +100,26 @@ impl LockrIntegration {
85100
&& lower.ends_with(".js")
86101
}
87102

103+
/// Rewrite the host variable in the Lockr SDK JavaScript.
104+
///
105+
/// Replaces the obfuscated host assignment with a direct assignment to the
106+
/// first-party API proxy endpoint.
107+
fn rewrite_sdk_host(&self, sdk_body: Vec<u8>) -> Result<Vec<u8>, Report<TrustedServerError>> {
108+
// Pattern to find: 'host': _0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e)
109+
// This is the obfuscated way Lockr sets the API host
110+
// We'll replace it with a relative URL to use the first-party proxy
111+
112+
let mut replacer = StreamingReplacer::new_single(
113+
"'host': _0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e)",
114+
"'host': '/integrations/lockr/api'",
115+
);
116+
117+
// Process the entire SDK in one chunk
118+
let processed = replacer.process_chunk(&sdk_body, true);
119+
120+
Ok(processed)
121+
}
122+
88123
/// Handle SDK serving - fetch from Lockr CDN and serve through first-party domain.
89124
async fn handle_sdk_serving(
90125
&self,
@@ -125,9 +160,15 @@ impl LockrIntegration {
125160
))));
126161
}
127162

128-
let sdk_body = lockr_response.take_body_bytes();
163+
let mut sdk_body = lockr_response.take_body_bytes();
129164
log::info!("Successfully fetched Lockr SDK: {} bytes", sdk_body.len());
130165

166+
// Rewrite the host variable in the SDK if enabled
167+
if self.config.rewrite_sdk_host {
168+
sdk_body = self.rewrite_sdk_host(sdk_body)?;
169+
log::info!("Rewrote SDK host variable: {} bytes", sdk_body.len());
170+
}
171+
131172
// TODO: Cache in KV store (future enhancement)
132173

133174
Ok(Response::from_status(StatusCode::OK)
@@ -144,6 +185,10 @@ impl LockrIntegration {
144185
)
145186
.with_header("X-Lockr-SDK-Proxy", "true")
146187
.with_header("X-SDK-Source", sdk_url)
188+
.with_header(
189+
"X-Lockr-Host-Rewritten",
190+
self.config.rewrite_sdk_host.to_string(),
191+
)
147192
.with_body(sdk_body))
148193
}
149194

@@ -190,10 +235,15 @@ impl LockrIntegration {
190235
let backend_name = ensure_backend_from_url(&self.config.api_endpoint)
191236
.change_context(Self::error("Failed to determine backend for API proxy"))?;
192237

193-
let response = match target_req .send(backend_name) {
238+
let response = match target_req.send(backend_name) {
194239
Ok(res) => res,
195240
Err(e) => {
196-
return Err(Self::error(format!("failed to forward request to {}, {}", target_url, e.root_cause())).into());
241+
return Err(Self::error(format!(
242+
"failed to forward request to {}, {}",
243+
target_url,
244+
e.root_cause()
245+
))
246+
.into());
197247
}
198248
};
199249

@@ -340,6 +390,10 @@ fn default_rewrite_sdk() -> bool {
340390
true
341391
}
342392

393+
fn default_rewrite_sdk_host() -> bool {
394+
true
395+
}
396+
343397
#[cfg(test)]
344398
mod tests {
345399
use super::*;
@@ -353,6 +407,7 @@ mod tests {
353407
sdk_url: default_sdk_url(),
354408
cache_ttl_seconds: 3600,
355409
rewrite_sdk: true,
410+
rewrite_sdk_host: true,
356411
};
357412
let integration = LockrIntegration::new(config);
358413

@@ -373,6 +428,7 @@ mod tests {
373428
sdk_url: default_sdk_url(),
374429
cache_ttl_seconds: 3600,
375430
rewrite_sdk: true,
431+
rewrite_sdk_host: true,
376432
};
377433
let integration = LockrIntegration::new(config);
378434

@@ -403,6 +459,7 @@ mod tests {
403459
sdk_url: default_sdk_url(),
404460
cache_ttl_seconds: 3600,
405461
rewrite_sdk: false, // Disabled
462+
rewrite_sdk_host: true,
406463
};
407464
let integration = LockrIntegration::new(config);
408465

@@ -460,6 +517,7 @@ mod tests {
460517
sdk_url: default_sdk_url(),
461518
cache_ttl_seconds: 3600,
462519
rewrite_sdk: true,
520+
rewrite_sdk_host: true,
463521
};
464522
let integration = LockrIntegration::new(config);
465523

@@ -479,4 +537,91 @@ mod tests {
479537
.iter()
480538
.any(|r| r.path == "/integrations/lockr/api/*" && r.method == Method::GET));
481539
}
540+
541+
#[test]
542+
fn test_sdk_host_rewriting() {
543+
let config = LockrConfig {
544+
enabled: true,
545+
app_id: "test-app-id".to_string(),
546+
api_endpoint: default_api_endpoint(),
547+
sdk_url: default_sdk_url(),
548+
cache_ttl_seconds: 3600,
549+
rewrite_sdk: true,
550+
rewrite_sdk_host: true,
551+
};
552+
let integration = LockrIntegration::new(config);
553+
554+
// Mock obfuscated SDK JavaScript with the host pattern
555+
let mock_sdk = r#"
556+
const identityLockr = {
557+
'host': _0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e),
558+
'app_id': null,
559+
'expiryDateKeys': localStorage['getItem']('identityLockr_expiryDateKeys') ? JSON['parse'](localStorage['getItem']('identityLockr_expiryDateKeys')) : [],
560+
'firstPartyCookies': [],
561+
'canRefreshToken': !![]
562+
};
563+
"#;
564+
565+
let result = integration.rewrite_sdk_host(mock_sdk.as_bytes().to_vec());
566+
assert!(result.is_ok());
567+
568+
let rewritten = String::from_utf8(result.unwrap()).unwrap();
569+
570+
// Verify the host was rewritten to the proxy endpoint
571+
assert!(rewritten.contains("'host': '/integrations/lockr/api'"));
572+
573+
// Verify the obfuscated pattern was removed
574+
assert!(!rewritten.contains("_0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e)"));
575+
576+
// Verify other parts of the code remain intact
577+
assert!(rewritten.contains("'app_id': null"));
578+
assert!(rewritten.contains("'firstPartyCookies': []"));
579+
}
580+
581+
#[test]
582+
fn test_sdk_host_rewriting_disabled() {
583+
let config = LockrConfig {
584+
enabled: true,
585+
app_id: "test-app-id".to_string(),
586+
api_endpoint: default_api_endpoint(),
587+
sdk_url: default_sdk_url(),
588+
cache_ttl_seconds: 3600,
589+
rewrite_sdk: true,
590+
rewrite_sdk_host: false, // Disabled
591+
};
592+
593+
// When rewrite_sdk_host is false, the handle_sdk_serving function
594+
// won't call rewrite_sdk_host at all, so the SDK is served as-is
595+
assert!(!config.rewrite_sdk_host);
596+
}
597+
598+
#[test]
599+
fn test_sdk_host_rewriting_no_match() {
600+
let config = LockrConfig {
601+
enabled: true,
602+
app_id: "test-app-id".to_string(),
603+
api_endpoint: default_api_endpoint(),
604+
sdk_url: default_sdk_url(),
605+
cache_ttl_seconds: 3600,
606+
rewrite_sdk: true,
607+
rewrite_sdk_host: true,
608+
};
609+
let integration = LockrIntegration::new(config);
610+
611+
// Test with SDK that doesn't have the expected pattern
612+
let mock_sdk = r#"
613+
const identityLockr = {
614+
'host': 'https://example.com',
615+
'app_id': null
616+
};
617+
"#;
618+
619+
let result = integration.rewrite_sdk_host(mock_sdk.as_bytes().to_vec());
620+
assert!(result.is_ok());
621+
622+
let rewritten = String::from_utf8(result.unwrap()).unwrap();
623+
624+
// When pattern doesn't match, content should be unchanged
625+
assert!(rewritten.contains("'host': 'https://example.com'"));
626+
}
482627
}

0 commit comments

Comments
 (0)