feat: credentials page, master password change and a Diagnostics tab

- Settings → Security lists every profile's stored credentials (kind and
  whether it's in the keychain or the vault; the keychain is probed per
  kind on a background thread since it can't be listed) with Forget.
- Change Master Password re-encrypts every vault entry under a fresh
  salt and key with a new verifier, after checking the current password,
  in one atomic write (Vault::change_password; tested that the old
  password stops working and every value survives).
- Settings → Diagnostics shows version, platform, data/database/log
  paths and sizes, keychain and vault state, counts, sync and launch
  settings, with Copy Diagnostics; it never includes credentials.
  Openable with --open settings:diagnostics.
This commit is contained in:
Andy Dixon
2026-10-12 19:26:52 +01:00
parent 1c590f47ff
commit 5acb41158c
7 changed files with 244 additions and 14 deletions
+17
View File
@@ -122,6 +122,23 @@ impl Service {
self.secrets.set_checked(SYNC_PASSWORD, password)
}
/// Every profile's stored credentials: (profile id, profile name, kind, "keychain"/"vault").
/// Slow-ish (keychain probes); run off the UI thread.
pub fn credential_inventory(&self) -> Vec<(String, String, crate::secrets::SecretKind, &'static str)> {
let mut out = vec![];
for p in self.profiles() {
for (kind, at) in self.secrets.stored_for(&p.id) {
out.push((p.id.clone(), p.display_name.clone(), kind, at));
}
}
out
}
pub fn forget_credential(&self, profile_id: &str, kind: crate::secrets::SecretKind) {
self.secrets.delete(&kind.key(profile_id));
self.store.add_audit("secret.delete", profile_id, "", &format!("Forgot {} credential", kind.as_str()));
}
pub fn has_sync_password(&self) -> bool {
self.secrets.get(SYNC_PASSWORD).is_some()
}