fix(desktop): preserve remote proxy base paths
Keep proxied requests under the configured remote base path and fail fast when the local HTTPS proxy certificate cannot be trusted, instead of starting a broken HTTPS proxy.
This commit is contained in:
@@ -309,7 +309,9 @@ async fn open_remote_window(app: AppHandle, payload: RemoteWindowPayload) -> Res
|
||||
let tls_config = match cert_manager::ensure_local_cert() {
|
||||
Ok(local_cert) => {
|
||||
if let Err(err) = cert_manager::trust_cert_in_store(&local_cert.cert_der) {
|
||||
eprintln!("[tauri] failed to trust proxy cert: {err}");
|
||||
return Err(format!(
|
||||
"Failed to trust the local proxy certificate. Accept the certificate installation prompt and try again: {err}"
|
||||
));
|
||||
}
|
||||
Some(ProxyTlsConfig {
|
||||
cert_pem: local_cert.cert_pem,
|
||||
|
||||
@@ -188,11 +188,40 @@ async fn proxy_request(
|
||||
|
||||
fn build_upstream_url(base_url: &Url, uri: &Uri) -> Result<Url, url::ParseError> {
|
||||
let mut url = base_url.clone();
|
||||
url.set_path(uri.path());
|
||||
url.set_path(&rewrite_request_path(base_url, uri.path()));
|
||||
url.set_query(strip_proxy_token_query(uri.query()).as_deref());
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
fn rewrite_request_path(base_url: &Url, request_path: &str) -> String {
|
||||
let base_path = normalized_base_path(base_url);
|
||||
if base_path == "/" {
|
||||
return request_path.to_string();
|
||||
}
|
||||
|
||||
if request_path == "/" {
|
||||
return base_path.to_string();
|
||||
}
|
||||
|
||||
if path_has_base_prefix(base_path, request_path) {
|
||||
return request_path.to_string();
|
||||
}
|
||||
|
||||
format!("{base_path}{request_path}")
|
||||
}
|
||||
|
||||
fn normalized_base_path(base_url: &Url) -> &str {
|
||||
let path = base_url.path();
|
||||
if path.is_empty() { "/" } else { path }
|
||||
}
|
||||
|
||||
fn path_has_base_prefix(base_path: &str, request_path: &str) -> bool {
|
||||
request_path == base_path
|
||||
|| request_path
|
||||
.strip_prefix(base_path)
|
||||
.is_some_and(|suffix| suffix.starts_with('/'))
|
||||
}
|
||||
|
||||
fn generate_session_token() -> String {
|
||||
let mut bytes = [0_u8; 16];
|
||||
rand::thread_rng().fill_bytes(&mut bytes);
|
||||
@@ -284,7 +313,7 @@ fn rewrite_referer_header(headers: &HeaderMap, target_base_url: &Url) -> Option<
|
||||
let parsed = Url::parse(referer).ok()?;
|
||||
|
||||
let mut rewritten = target_base_url.clone();
|
||||
rewritten.set_path(parsed.path());
|
||||
rewritten.set_path(&rewrite_request_path(target_base_url, parsed.path()));
|
||||
rewritten.set_query(parsed.query());
|
||||
rewritten.set_fragment(parsed.fragment());
|
||||
Some(rewritten.to_string())
|
||||
@@ -380,3 +409,52 @@ fn is_hop_by_hop_header(name: &HeaderName) -> bool {
|
||||
})
|
||||
.contains(name.as_str())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn build_upstream_url_prefixes_root_relative_requests_under_base_path() {
|
||||
let base = Url::parse("https://example.com/app").unwrap();
|
||||
let uri = "/api/auth/status?foo=bar".parse::<Uri>().unwrap();
|
||||
|
||||
let upstream = build_upstream_url(&base, &uri).unwrap();
|
||||
|
||||
assert_eq!(upstream.as_str(), "https://example.com/app/api/auth/status?foo=bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_upstream_url_keeps_requests_already_under_base_path() {
|
||||
let base = Url::parse("https://example.com/app").unwrap();
|
||||
let uri = "/app/api/auth/status?foo=bar".parse::<Uri>().unwrap();
|
||||
|
||||
let upstream = build_upstream_url(&base, &uri).unwrap();
|
||||
|
||||
assert_eq!(upstream.as_str(), "https://example.com/app/api/auth/status?foo=bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_upstream_url_maps_root_to_base_path() {
|
||||
let base = Url::parse("https://example.com/app").unwrap();
|
||||
let uri = "/".parse::<Uri>().unwrap();
|
||||
|
||||
let upstream = build_upstream_url(&base, &uri).unwrap();
|
||||
|
||||
assert_eq!(upstream.as_str(), "https://example.com/app");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_referer_header_prefixes_root_relative_path_under_base_path() {
|
||||
let target = Url::parse("https://example.com/app").unwrap();
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
axum::http::header::REFERER,
|
||||
HeaderValue::from_static("https://127.0.0.1:3000/api/auth/status?foo=bar"),
|
||||
);
|
||||
|
||||
let referer = rewrite_referer_header(&headers, &target).unwrap();
|
||||
|
||||
assert_eq!(referer, "https://example.com/app/api/auth/status?foo=bar");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user