mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-20 16:12:17 +00:00
fix: eliminate OS keychain prompt on startup by persisting pairing token in DB
The root cause was hydrate_extension_pairing_token accessing the keychain when keychainAccessGranted was true in the DB. Any keychain access on macOS triggers the system prompt when the binary signature changes after an update. Architecture change: two-store model. - The SQLite settings DB is now the primary store for the token. hydrate_extension_pairing_token reads from it exclusively -- it never touches the OS keychain. No system prompt on startup. - The OS keychain remains defence-in-depth: grant_keychain_access still writes the token there, but it is only reached from the explicit Grant Access button, so any system prompt is user-initiated. DB helpers: load_pairing_token_from_settings / save_pairing_token_to_settings hydrate_extension_pairing_token: reads from DB, skips keychain entirely grant_keychain_access: syncs token to DB after keychain access Frontend: extensionPairingToken included in partialize for auto-persist
This commit is contained in:
+46
-1
@@ -811,7 +811,7 @@ fn decide_pairing_token(
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_pairing_token() -> String {
|
||||
pub(crate) fn generate_pairing_token() -> String {
|
||||
format!(
|
||||
"{}{}",
|
||||
uuid::Uuid::new_v4().simple(),
|
||||
@@ -819,6 +819,51 @@ fn generate_pairing_token() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// Read the extension pairing token from the persisted settings JSON.
|
||||
/// Returns `None` when the field is missing, empty, or the settings haven't
|
||||
/// been saved yet. This is the **primary read path** — it does not touch the
|
||||
/// OS keychain and therefore never triggers a system credential prompt.
|
||||
pub fn load_pairing_token_from_settings(connection: &Connection) -> Result<Option<String>, String> {
|
||||
let Some(settings_json) = load_settings(connection)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let value: serde_json::Value = serde_json::from_str(&settings_json)
|
||||
.map_err(|error| format!("failed to decode settings: {error}"))?;
|
||||
let state = value.get("state").unwrap_or(&value);
|
||||
let token = state
|
||||
.get("extensionPairingToken")
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string());
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Write (or update) the extension pairing token inside the persisted settings
|
||||
/// JSON document. Keeps all other settings fields intact.
|
||||
pub fn save_pairing_token_to_settings(
|
||||
connection: &Connection,
|
||||
token: &str,
|
||||
) -> Result<(), String> {
|
||||
let Some(settings_json) = load_settings(connection)? else {
|
||||
// Settings haven't been persisted yet — nothing to update.
|
||||
return Ok(());
|
||||
};
|
||||
let mut value: serde_json::Value = serde_json::from_str(&settings_json)
|
||||
.map_err(|error| format!("failed to decode settings: {error}"))?;
|
||||
let state = if value.get("state").is_some() {
|
||||
value
|
||||
.get_mut("state")
|
||||
.expect("state is an object")
|
||||
} else {
|
||||
&mut value
|
||||
};
|
||||
state["extensionPairingToken"] =
|
||||
serde_json::Value::String(token.to_string());
|
||||
let updated = serde_json::to_string(&value)
|
||||
.map_err(|error| format!("failed to encode settings: {error}"))?;
|
||||
save_settings(connection, &updated)
|
||||
}
|
||||
|
||||
pub fn set_keychain_password(id: &str, password: &str) -> Result<(), String> {
|
||||
let entry = keyring::Entry::new(KEYCHAIN_SERVICE, id).map_err(|error| error.to_string())?;
|
||||
entry
|
||||
|
||||
Reference in New Issue
Block a user