WEB: Base Path: security + reload coverage fixes&tests

This commit is contained in:
Alexey
2026-09-26 14:25:24 +03:00
parent 5feded2919
commit 38eabc50e3
25 changed files with 1294 additions and 56 deletions
+31 -9
View File
@@ -93,12 +93,9 @@ pub(crate) fn print_web_proxy_links(config: &ProxyConfig) {
"User: {} ({:?})",
profile.user, profile.secret_mode
));
if let Some(link) = format_web_proxy_link(
&profile.host,
&vhost.base_path,
secret,
profile.secret_mode,
) {
if let Some(link) =
format_web_proxy_link(&profile.host, &vhost.base_path, secret, profile.secret_mode)
{
print_maestro_line(format!("WEB: {link}"));
}
}
@@ -129,9 +126,7 @@ fn format_web_proxy_link(
let server = url::form_urlencoded::byte_serialize(format!("{host}/{base_path}").as_bytes())
.collect::<String>();
let marked = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(marked);
Some(format!(
"tg://webproxy?server={server}&secret={marked}"
))
Some(format!("tg://webproxy?server={server}&secret={marked}"))
}
/// Durably replaces one Beobachten snapshot without following Unix symlinks.
@@ -451,4 +446,31 @@ mod tests {
Some("tg://webproxy?server=proxy.example.com%2Fdobry-cola%2Fsuper_app&secret=cN0AAQIDBAUGBwgJCgsMDQ4P".to_string())
);
}
#[test]
fn path_web_proxy_link_round_trips_through_the_tdesktop_grammar() {
for (mode, expected_secret) in [
(WebSecretMode::Plain, hex::decode(SECRET).unwrap()),
(
WebSecretMode::Dd,
[vec![0xdd], hex::decode(SECRET).unwrap()].concat(),
),
] {
let link = format_web_proxy_link("proxy.example.com", "MixedCase/a_b-9", SECRET, mode)
.unwrap();
let parsed = url::Url::parse(&link).unwrap();
let query = parsed
.query_pairs()
.collect::<std::collections::BTreeMap<_, _>>();
assert_eq!(
query["server"].as_ref(),
"proxy.example.com/MixedCase/a_b-9"
);
let marked = base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(query["secret"].as_bytes())
.unwrap();
assert_eq!(marked.first(), Some(&0x70));
assert_eq!(&marked[1..], expected_secret.as_slice());
}
}
}
+22
View File
@@ -295,6 +295,28 @@ fn web_decoy_fasttrack_mode_is_deferred_without_runtime_publication() {
assert!(!resolved.runtime_changed);
}
#[test]
fn base_path_change_is_runtime_owned_and_rebuilds_route_identity() {
let old = web_config_with_fasttrack("off");
let old_runtime = old.web.runtime.as_ref().unwrap();
let old_capability = old_runtime.vhosts["proxy.example.com"].capabilities[0];
let mut desired = old.clone();
desired.web.vhosts[0].base_path = "MixedCase/path".to_string();
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert!(resolved.deferred_process_fields.is_empty());
assert!(resolved.runtime_changed);
assert_eq!(resolved.effective.web.vhosts[0].base_path, "MixedCase/path");
let runtime = resolved.effective.web.runtime.as_ref().unwrap();
let vhost = &runtime.vhosts["proxy.example.com"];
assert_eq!(vhost.base, "/MixedCase/path/");
assert_ne!(vhost.capabilities[0], old_capability);
assert_eq!(vhost.capabilities[0], vhost.profiles[0].capability);
assert_eq!(runtime.capabilities.as_ref(), vhost.capabilities.as_ref());
assert!(!runtime.capabilities.contains(&old_capability));
}
#[test]
fn enabling_learning_is_deferred_when_retained_capacity_is_too_small() {
let mut old = ProxyConfig::default();