mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
fix: prevent WebDAV password from being silently cleared by unrelated saves
Two components (ProviderList, UsageScriptModal) directly spread the full settings object from the query into settingsApi.save(), which includes webdavSync with an empty password (cleared by get_settings_for_frontend for security). The backend merge_settings_for_save only preserved existing WebDAV config when the incoming field was None, not when it was present with an empty password. Frontend fix: strip webdavSync before saving in both components, matching the pattern already used by useSettings hook. Backend defense-in-depth: merge_settings_for_save now backfills the existing password when the incoming one is empty, preventing future regressions from similar oversights.
This commit is contained in:
@@ -6,8 +6,20 @@ fn merge_settings_for_save(
|
||||
mut incoming: crate::settings::AppSettings,
|
||||
existing: &crate::settings::AppSettings,
|
||||
) -> crate::settings::AppSettings {
|
||||
if incoming.webdav_sync.is_none() {
|
||||
incoming.webdav_sync = existing.webdav_sync.clone();
|
||||
match (&mut incoming.webdav_sync, &existing.webdav_sync) {
|
||||
// incoming 没有 webdav → 保留现有
|
||||
(None, _) => {
|
||||
incoming.webdav_sync = existing.webdav_sync.clone();
|
||||
}
|
||||
// incoming 有 webdav 但密码为空,且现有有密码 → 填回现有密码
|
||||
// (get_settings_for_frontend 总是清空密码,所以通过 save_settings
|
||||
// 传入的空密码意味着"保持现有"而非"用户主动清空")
|
||||
(Some(incoming_sync), Some(existing_sync))
|
||||
if incoming_sync.password.is_empty() && !existing_sync.password.is_empty() =>
|
||||
{
|
||||
incoming_sync.password = existing_sync.password.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
incoming
|
||||
}
|
||||
@@ -116,6 +128,66 @@ mod tests {
|
||||
Some("https://dav.new.example.com")
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression test: frontend always receives empty password from
|
||||
/// get_settings_for_frontend(). If a component accidentally spreads
|
||||
/// the full settings object into save_settings, the empty password
|
||||
/// must NOT overwrite the existing one.
|
||||
#[test]
|
||||
fn save_settings_should_preserve_password_when_incoming_has_empty_password() {
|
||||
let mut existing = AppSettings::default();
|
||||
existing.webdav_sync = Some(WebDavSyncSettings {
|
||||
base_url: "https://dav.example.com".to_string(),
|
||||
username: "alice".to_string(),
|
||||
password: "secret".to_string(),
|
||||
..WebDavSyncSettings::default()
|
||||
});
|
||||
|
||||
// Simulate frontend sending settings with cleared password
|
||||
let mut incoming = AppSettings::default();
|
||||
incoming.webdav_sync = Some(WebDavSyncSettings {
|
||||
base_url: "https://dav.example.com".to_string(),
|
||||
username: "alice".to_string(),
|
||||
password: "".to_string(),
|
||||
..WebDavSyncSettings::default()
|
||||
});
|
||||
|
||||
let merged = merge_settings_for_save(incoming, &existing);
|
||||
|
||||
assert_eq!(
|
||||
merged.webdav_sync.as_ref().map(|v| v.password.as_str()),
|
||||
Some("secret"),
|
||||
"empty password from frontend must not overwrite existing password"
|
||||
);
|
||||
}
|
||||
|
||||
/// When both incoming and existing have no password, merge should
|
||||
/// work without panicking and keep the empty state.
|
||||
#[test]
|
||||
fn save_settings_should_handle_both_empty_passwords() {
|
||||
let mut existing = AppSettings::default();
|
||||
existing.webdav_sync = Some(WebDavSyncSettings {
|
||||
base_url: "https://dav.example.com".to_string(),
|
||||
username: "alice".to_string(),
|
||||
password: "".to_string(),
|
||||
..WebDavSyncSettings::default()
|
||||
});
|
||||
|
||||
let mut incoming = AppSettings::default();
|
||||
incoming.webdav_sync = Some(WebDavSyncSettings {
|
||||
base_url: "https://dav.example.com".to_string(),
|
||||
username: "alice".to_string(),
|
||||
password: "".to_string(),
|
||||
..WebDavSyncSettings::default()
|
||||
});
|
||||
|
||||
let merged = merge_settings_for_save(incoming, &existing);
|
||||
|
||||
assert_eq!(
|
||||
merged.webdav_sync.as_ref().map(|v| v.password.as_str()),
|
||||
Some("")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取开机自启状态
|
||||
|
||||
Reference in New Issue
Block a user