diff --git a/crates/ui-egui/src/live.rs b/crates/ui-egui/src/live.rs index bcc0bc77..c4ea3ed7 100644 --- a/crates/ui-egui/src/live.rs +++ b/crates/ui-egui/src/live.rs @@ -98,10 +98,11 @@ pub struct LiveUi { pub advanced: bool, pub this_computer: bool, pub port: String, - /// The server key for online sharing (a server that needs one; empty: none). Passed to - /// `collab.share` so a keyed server can be used from the Share popover without the Publish - /// dialog. - pub share_key: String, + /// The server key for online sharing, and the server address it was entered for (a server + /// that needs one; `None`: none). Passed to `collab.share` so a keyed server can be used + /// from the Share popover without the Publish dialog. The key goes out only to the server it + /// belongs to: switching the server in Preferences must not leak the old one to it. + pub share_key: Option<(String, String)>, /// What is typed in the join field of the Share popover and the home screen. pub code: String, /// The join started from the home screen: what it is doing shows under the field, and its @@ -750,8 +751,14 @@ fn start_ui(app: &mut FigApp, ui: &mut Ui, t: &Tokens) { if let (true, Ok(port)) = (local, app.ui.live.port.trim().parse::()) { params["port"] = json!(port); } - if !local && !app.ui.live.share_key.trim().is_empty() { - params["key"] = json!(app.ui.live.share_key.trim()); + // The key goes out only to the server it belongs to (the server in Preferences): changing + // the server must not send the old server's key to a new one. + if !local + && let Some((at, key)) = app.ui.live.share_key.as_ref() + && *at == server + && !key.is_empty() + { + params["key"] = json!(key); } app.run("collab.share", params); } @@ -770,12 +777,13 @@ fn start_ui(app: &mut FigApp, ui: &mut Ui, t: &Tokens) { } else { widgets::label(ui, tr("Another server, or one that needs its key. The usual server is set in Preferences."), t.text_secondary); // The server key (a server that needs one; the address in Preferences is without it). + // It is remembered for the server it was typed for, so it never leaks to another one. ui.horizontal(|ui| { widgets::label(ui, tr("Server key"), t.text); - if let Some(key) = - widgets::secret_field_named(ui, egui::Id::unique("live_share_key"), &app.ui.live.share_key, 200.0, tr("Server key")) - { - app.ui.live.share_key = key.trim().chars().filter(|c| !c.is_whitespace() && !c.is_control()).take(128).collect(); + let shown = app.ui.live.share_key.as_ref().map(|(_, key)| key.as_str()).unwrap_or_default(); + if let Some(key) = widgets::secret_field_named(ui, egui::Id::unique("live_share_key"), shown, 200.0, tr("Server key")) { + let key: String = key.trim().chars().filter(|c| !c.is_whitespace() && !c.is_control()).take(128).collect(); + app.ui.live.share_key = if key.is_empty() { None } else { Some((server.trim().to_string(), key)) }; } }); if widgets::button(ui, tr("Publish to another server…"), false, true).clicked() { diff --git a/crates/ui-egui/src/live_share_tests.rs b/crates/ui-egui/src/live_share_tests.rs index 6c5bdb84..edef3acc 100644 --- a/crates/ui-egui/src/live_share_tests.rs +++ b/crates/ui-egui/src/live_share_tests.rs @@ -237,7 +237,7 @@ fn the_server_key_shows_as_dots_while_shown_and_typed() { let (mut h, _) = harness(); let shapes = open(&mut h); click(&mut h, &shapes, "Advanced"); - h.app.ui.live.share_key = "s3cret-key".into(); + h.app.ui.live.share_key = Some(("wss://share.example/".to_string(), "s3cret-key".to_string())); let shapes = h.frames(2); let dots = "•".repeat(10); assert!(has_text(&shapes, &dots) && !has_text(&shapes, "s3cret-key")); @@ -505,6 +505,22 @@ fn the_server_preference_resets_and_turns_online_sharing_off() { assert!(s.published.is_empty() && s.hosted.len() == 1, "shared on the local network"); } +#[test] +fn a_key_typed_for_one_server_is_not_sent_to_another() { + // The Server key field remembers the server it was typed for: it goes out only to that + // server. Changing the server in Preferences must not send the old server's key to the new + // one. (The key is set as the field editor would, bound to the server it was typed for.) + let (mut h, script) = harness(); + h.app.ui.live.share_key = Some(("wss://share.example/".to_string(), "KeyKeyKeyKeyKeyKeyKey0".to_string())); + h.frames(2); + // Change the server in Preferences to another one, then share: the old key must not go to it. + h.app.session.execute("pref.publishServer", &json!({"url": "wss://other.example/"})).unwrap(); + h.frames(2); + let shapes = open(&mut h); + let shapes = click(&mut h, &shapes, "Start sharing"); + assert_eq!(script.lock().unwrap().published.last().unwrap().key, None, "the old server's key is not sent to the new server"); +} + #[test] fn an_open_popover_asks_for_no_frames() { let (mut h, script) = harness();