From 610eda203e0bf260bb0a2dc78542e2f5838f7008 Mon Sep 17 00:00:00 2001 From: NimBold Date: Fri, 12 Jun 2026 23:27:57 +0330 Subject: [PATCH] feat: implement native OS keyring for site logins - Replaced plaintext local storage password fields with keyring v3 OS native integration. - Tauri backend securely sets, gets, and deletes credentials via system keychains. - Dynamic password injection occurs seamlessly on download start and metadata parsing. --- apps/desktop/src-tauri/Cargo.lock | 11 +++++++++ apps/desktop/src-tauri/Cargo.toml | 1 + apps/desktop/src-tauri/src/lib.rs | 23 ++++++++++++++++++- .../src/components/AddDownloadsModal.tsx | 20 ++++++++++++++-- apps/desktop/src/components/SettingsView.tsx | 23 +++++++++++++++---- apps/desktop/src/store/useDownloadStore.ts | 12 ++++++++-- apps/desktop/src/store/useSettingsStore.ts | 1 - 7 files changed, 81 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 996a46b..32eb6e1 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -1949,6 +1949,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "log", + "zeroize", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -3813,6 +3823,7 @@ dependencies = [ name = "tauri-app" version = "0.1.0" dependencies = [ + "keyring", "regex", "reqwest 0.12.28", "serde", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index aee9d4e..c4fc3aa 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -28,4 +28,5 @@ regex = "1.10" reqwest = "0.12" tauri-plugin-notification = "2.3.3" sysinfo = "0.39.3" +keyring = "3" diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index f587f07..067e49c 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -902,6 +902,26 @@ fn get_free_space(app_handle: tauri::AppHandle, path: String) -> Result Result<(), String> { + let entry = keyring::Entry::new("com.firelink.app", &id).map_err(|e| e.to_string())?; + entry.set_password(&password).map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +fn get_keychain_password(id: String) -> Result { + let entry = keyring::Entry::new("com.firelink.app", &id).map_err(|e| e.to_string())?; + entry.get_password().map_err(|e| e.to_string()) +} + +#[tauri::command] +fn delete_keychain_password(id: String) -> Result<(), String> { + let entry = keyring::Entry::new("com.firelink.app", &id).map_err(|e| e.to_string())?; + let _ = entry.delete_credential(); // Ignore error if it doesn't exist + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -915,7 +935,8 @@ pub fn run() { greet, test_ytdlp, test_aria2c, test_ffmpeg, test_deno, open_file, show_in_folder, start_download, start_media_download, pause_download, fetch_metadata, fetch_media_metadata, update_dock_badge, set_prevent_sleep, get_free_space, perform_system_action, - request_automation_permission, open_automation_settings + request_automation_permission, open_automation_settings, + set_keychain_password, get_keychain_password, delete_keychain_password ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/apps/desktop/src/components/AddDownloadsModal.tsx b/apps/desktop/src/components/AddDownloadsModal.tsx index 14d013d..79b2936 100644 --- a/apps/desktop/src/components/AddDownloadsModal.tsx +++ b/apps/desktop/src/components/AddDownloadsModal.tsx @@ -326,12 +326,20 @@ export const AddDownloadsModal = () => { const { mediaCookieSource } = settingsStore; const browserArg = mediaCookieSource !== 'none' ? mediaCookieSource : null; const login = getSiteLogin(url, settingsStore); + let keychainPassword = null; + if (login) { + try { + keychainPassword = await invoke('get_keychain_password', { id: login.id }); + } catch (e) { + console.warn("Could not fetch keychain password:", e); + } + } const jsonStr = await invoke('fetch_media_metadata', { url, cookieBrowser: browserArg, username: login?.username || null, - password: login?.password || null + password: keychainPassword }); const mediaData = parseMediaFormats(jsonStr); if (mediaData && mediaData.formats.length > 0) { @@ -351,10 +359,18 @@ export const AddDownloadsModal = () => { } else { const settingsStore = useSettingsStore.getState(); const login = getSiteLogin(url, settingsStore); + let keychainPassword = null; + if (login) { + try { + keychainPassword = await invoke('get_keychain_password', { id: login.id }); + } catch (e) { + console.warn("Could not fetch keychain password:", e); + } + } const meta = await invoke<{filename: string, size: string, size_bytes: number}>('fetch_metadata', { url, username: login?.username || null, - password: login?.password || null + password: keychainPassword }); updatedItems[i] = { url, file: meta.filename, size: meta.size, sizeBytes: meta.size_bytes, status: 'Ready' }; } diff --git a/apps/desktop/src/components/SettingsView.tsx b/apps/desktop/src/components/SettingsView.tsx index f43e917..3c92ba0 100644 --- a/apps/desktop/src/components/SettingsView.tsx +++ b/apps/desktop/src/components/SettingsView.tsx @@ -116,17 +116,27 @@ export default function SettingsView() { } }; - const handleAddLogin = () => { + const handleAddLogin = async () => { if (!loginPattern.trim() || !loginUser.trim()) { setLoginError("Please enter a URL pattern and a username."); return; } const id = crypto.randomUUID(); + + if (loginPass) { + try { + await invoke('set_keychain_password', { id, password: loginPass }); + } catch (e) { + console.error("Failed to save password to keychain:", e); + setLoginError("Failed to save password securely."); + return; + } + } + settings.addSiteLogin({ id, urlPattern: loginPattern.trim(), - username: loginUser.trim(), - password: loginPass + username: loginUser.trim() }); setLoginPattern(''); setLoginUser(''); @@ -534,7 +544,12 @@ export default function SettingsView() {

User: {login.username}