fix(keychain): persist access state, update granted UI, and resolve build errors

This commit is contained in:
NimBold
2026-06-24 15:45:21 +03:30
parent af2c101ca8
commit 6f426c1a18
6 changed files with 28 additions and 8 deletions
+2
View File
@@ -278,6 +278,8 @@ pub struct PersistedSettings {
// frontend. The field is kept on legacy persisted JSON only; serde ignores
// unknown fields when decoding, so existing installs migrate cleanly.
pub auto_check_updates: bool,
#[serde(default)]
pub keychain_access_granted: bool,
}
#[derive(Clone, Debug, Serialize, TS)]
+1
View File
@@ -289,6 +289,7 @@ fn default_settings() -> PersistedSettings {
media_cookie_source: MediaCookieSource::None,
site_logins: Vec::new(),
auto_check_updates: true,
keychain_access_granted: false,
}
}
+1 -1
View File
@@ -8,4 +8,4 @@ import type { SettingsTab } from "./SettingsTab";
import type { SiteLogin } from "./SiteLogin";
import type { Theme } from "./Theme";
export type PersistedSettings = { theme: Theme, baseDownloadFolder: string, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, isSidebarVisible: boolean, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, };
export type PersistedSettings = { theme: Theme, baseDownloadFolder: string, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, isSidebarVisible: boolean, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
@@ -19,6 +19,7 @@ export const KeychainPermissionModal: React.FC = () => {
try {
const result = await invoke('grant_keychain_access');
if (result.persistent) {
useSettingsStore.setState({ keychainAccessGranted: true });
await useSettingsStore.getState().hydratePairingToken();
setShowKeychainModal(false);
} else {
+15 -3
View File
@@ -8,7 +8,7 @@ import {
} from '../store/useSettingsStore';
import {
Download, Palette, Globe, Folder, Key,
Moon, Terminal, Puzzle, Info, Plus, Trash2, Copy, RefreshCw, Code, ShieldAlert
Moon, Terminal, Puzzle, Info, Plus, Trash2, Copy, RefreshCw, Code, ShieldAlert, Check
} from 'lucide-react';
import { open } from '@tauri-apps/plugin-dialog';
import { getVersion } from '@tauri-apps/api/app';
@@ -1055,7 +1055,19 @@ className="app-button px-3 py-1.5 text-[12px] flex items-center gap-1.5 disabled
</div>
</div>
{!settings.isPairingTokenPersistent && (
{settings.isPairingTokenPersistent ? (
<div className="bg-green-500/10 border border-green-500/20 rounded-lg p-3 flex items-center gap-3">
<div className="p-1.5 bg-green-500/20 rounded-full text-green-500 flex-shrink-0">
<Check size={16} strokeWidth={2.5} />
</div>
<div className="flex-1">
<h4 className="text-sm font-semibold text-green-500 m-0">Keychain Access Granted</h4>
<p className="text-xs text-text-secondary m-0 mt-0.5">
Your pairing token is securely saved and will persist across restarts.
</p>
</div>
</div>
) : (
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-4 flex items-start gap-3">
<ShieldAlert className="w-5 h-5 text-orange-500 flex-shrink-0 mt-0.5" />
<div className="flex-1">
@@ -1066,7 +1078,7 @@ className="app-button px-3 py-1.5 text-[12px] flex items-center gap-1.5 disabled
</p>
<button
onClick={() => settings.setShowKeychainModal(true)}
className="app-button primary text-xs py-1"
className="px-4 py-1.5 rounded-md text-xs font-medium transition-colors bg-accent text-white hover:bg-accent/90 shadow-sm"
>
Grant Keychain Access
</button>
+8 -4
View File
@@ -106,6 +106,7 @@ export interface SettingsState {
siteLogins: SiteLogin[];
extensionPairingToken: string;
isPairingTokenPersistent: boolean;
keychainAccessGranted: boolean;
autoCheckUpdates: boolean;
showKeychainModal: boolean;
@@ -178,7 +179,7 @@ const generateSecureToken = () => {
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
(set, get) => ({
theme: 'system',
baseDownloadFolder: '~/Downloads',
categorySubfolders: { ...DEFAULT_CATEGORY_SUBFOLDERS },
@@ -224,6 +225,7 @@ export const useSettingsStore = create<SettingsState>()(
siteLogins: [],
extensionPairingToken: '',
isPairingTokenPersistent: true,
keychainAccessGranted: false,
autoCheckUpdates: true,
showKeychainModal: false,
@@ -314,7 +316,8 @@ export const useSettingsStore = create<SettingsState>()(
set({ extensionPairingToken: token });
},
hydratePairingToken: async () => {
const result = await invoke('hydrate_extension_pairing_token');
const granted = get().keychainAccessGranted;
const result = await invoke(granted ? 'grant_keychain_access' : 'hydrate_extension_pairing_token');
set({
extensionPairingToken: result.token,
isPairingTokenPersistent: result.persistent
@@ -324,8 +327,8 @@ export const useSettingsStore = create<SettingsState>()(
}
return result.tokenChanged;
},
setAutoCheckUpdates: (autoCheckUpdates) => set({ autoCheckUpdates }),
setShowKeychainModal: (show) => set({ showKeychainModal: show }),
setAutoCheckUpdates: (autoCheckUpdates: boolean) => set({ autoCheckUpdates }),
setShowKeychainModal: (show: boolean) => set({ showKeychainModal: show }),
}),
{
name: 'firelink-settings',
@@ -393,6 +396,7 @@ export const useSettingsStore = create<SettingsState>()(
preventsSleepWhileDownloading: state.preventsSleepWhileDownloading,
mediaCookieSource: state.mediaCookieSource,
siteLogins: state.siteLogins,
keychainAccessGranted: state.keychainAccessGranted,
autoCheckUpdates: state.autoCheckUpdates
}),
merge: (persistedState: unknown, currentState) => {