mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-07 18:03:23 +00:00
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.
This commit is contained in:
@@ -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<string>('get_keychain_password', { id: login.id });
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch keychain password:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const jsonStr = await invoke<string>('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<string>('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' };
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
<p className="text-text-secondary text-xs">User: {login.username}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
try {
|
||||
await invoke('delete_keychain_password', { id: login.id });
|
||||
} catch (e) {
|
||||
console.warn("Could not delete password from keychain:", e);
|
||||
}
|
||||
settings.removeSiteLogin(login.id);
|
||||
showToast("Deleted credential");
|
||||
}}
|
||||
|
||||
@@ -291,6 +291,14 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
||||
try {
|
||||
const settings = useSettingsStore.getState();
|
||||
const login = getSiteLogin(item.url, settings);
|
||||
let keychainPassword = null;
|
||||
if (login) {
|
||||
try {
|
||||
keychainPassword = await invoke<string>('get_keychain_password', { id: login.id });
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch keychain password for login:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const destPath = item.destination ||
|
||||
(settings.downloadDirectories && settings.downloadDirectories[item.category]) ||
|
||||
@@ -311,7 +319,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
||||
formatSelector: item.mediaFormatSelector || null,
|
||||
speedLimit,
|
||||
username: item.username || (login ? login.username : null),
|
||||
password: item.password || (login ? login.password : null)
|
||||
password: item.password || keychainPassword
|
||||
});
|
||||
} else {
|
||||
const speedLimit = effectiveSpeedLimit(
|
||||
@@ -327,7 +335,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
||||
connections: item.connections || settings.perServerConnections || null,
|
||||
speedLimit,
|
||||
username: item.username || (login ? login.username : null),
|
||||
password: item.password || (login ? login.password : null),
|
||||
password: item.password || keychainPassword,
|
||||
headers: item.headers || null,
|
||||
userAgent: settings.customUserAgent || null,
|
||||
maxTries: settings.maxAutomaticRetries,
|
||||
|
||||
@@ -5,7 +5,6 @@ export interface SiteLogin {
|
||||
id: string;
|
||||
urlPattern: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export type AppFontSize = 'small' | 'standard' | 'large';
|
||||
|
||||
Reference in New Issue
Block a user