/** * SecureStorage - OS Keyring/Keychain Management UI * * Allows users to view, add, and delete securely stored credentials * using the OS keyring (Windows DPAPI, macOS Keychain, Linux Secret Service). */ import { useState, useEffect } from 'react'; import { Key, Plus, Trash2, Eye, EyeOff, RefreshCw, AlertCircle, CheckCircle, Shield, ShieldOff, } from 'lucide-react'; import { secureStorage, isSecureStorageAvailable } from '../../lib/secure-storage'; interface StoredKey { key: string; hasValue: boolean; preview?: string; } // Known storage keys used by the application const KNOWN_KEYS = [ { key: 'zclaw_api_key', label: 'API Key', description: 'LLM API 密钥' }, { key: 'zclaw_device_keys_private', label: 'Device Private Key', description: '设备私钥 (Ed25519)' }, { key: 'zclaw_gateway_token', label: 'Gateway Token', description: 'Gateway 认证令牌' }, { key: 'zclaw_feishu_secret', label: '飞书 Secret', description: '飞书应用密钥' }, { key: 'zclaw_discord_token', label: 'Discord Token', description: 'Discord Bot Token' }, { key: 'zclaw_slack_token', label: 'Slack Token', description: 'Slack Bot Token' }, { key: 'zclaw_telegram_token', label: 'Telegram Token', description: 'Telegram Bot Token' }, ]; export function SecureStorage() { const [isAvailable, setIsAvailable] = useState(null); const [storedKeys, setStoredKeys] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showAddForm, setShowAddForm] = useState(false); const [newKey, setNewKey] = useState(''); const [newValue, setNewValue] = useState(''); const [showValue, setShowValue] = useState>({}); const [revealedValues, setRevealedValues] = useState>({}); const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(null); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const loadStoredKeys = async () => { setIsLoading(true); try { const available = await isSecureStorageAvailable(); setIsAvailable(available); const keys: StoredKey[] = []; for (const knownKey of KNOWN_KEYS) { const value = await secureStorage.get(knownKey.key); keys.push({ key: knownKey.key, hasValue: !!value, preview: value ? `${value.slice(0, 8)}${value.length > 8 ? '...' : ''}` : undefined, }); } setStoredKeys(keys); } catch (error) { console.error('Failed to load stored keys:', error); } finally { setIsLoading(false); } }; useEffect(() => { loadStoredKeys(); }, []); const handleReveal = async (key: string) => { if (revealedValues[key]) { // Hide if already revealed setRevealedValues((prev) => { const next = { ...prev }; delete next[key]; return next; }); setShowValue((prev) => ({ ...prev, [key]: false })); } else { // Reveal the value const value = await secureStorage.get(key); if (value) { setRevealedValues((prev) => ({ ...prev, [key]: value })); setShowValue((prev) => ({ ...prev, [key]: true })); } } }; const handleAddKey = async () => { if (!newKey.trim() || !newValue.trim()) { setMessage({ type: 'error', text: '请填写密钥名称和值' }); return; } setIsSaving(true); setMessage(null); try { await secureStorage.set(newKey.trim(), newValue.trim()); setMessage({ type: 'success', text: '密钥已保存' }); setNewKey(''); setNewValue(''); setShowAddForm(false); await loadStoredKeys(); } catch (error) { setMessage({ type: 'error', text: `保存失败: ${error instanceof Error ? error.message : '未知错误'}` }); } finally { setIsSaving(false); } }; const handleDeleteKey = async (key: string) => { if (!confirm(`确定要删除密钥 "${key}" 吗?此操作无法撤销。`)) { return; } setIsDeleting(key); setMessage(null); try { await secureStorage.delete(key); setMessage({ type: 'success', text: '密钥已删除' }); setRevealedValues((prev) => { const next = { ...prev }; delete next[key]; return next; }); await loadStoredKeys(); } catch (error) { setMessage({ type: 'error', text: `删除失败: ${error instanceof Error ? error.message : '未知错误'}` }); } finally { setIsDeleting(null); } }; const getKeyLabel = (key: string) => { const known = KNOWN_KEYS.find((k) => k.key === key); return known ? known.label : key; }; const getKeyDescription = (key: string) => { const known = KNOWN_KEYS.find((k) => k.key === key); return known?.description; }; return (

安全存储

使用系统密钥库 (Keyring/Keychain) 安全存储敏感信息

{isAvailable !== null && ( {isAvailable ? ( <> Keyring 可用 ) : ( <> 使用加密本地存储 )} )}
{/* Status Banner */} {isAvailable === false && (

Keyring 不可用

系统密钥库不可用,将使用 AES-GCM 加密的本地存储作为后备方案。 建议在 Tauri 环境中运行以获得最佳安全性。

)} {/* Message */} {message && (
{message.type === 'success' ? ( ) : ( )} {message.text}
)} {/* Stored Keys List */}
{isLoading ? (
加载中...
) : storedKeys.length > 0 ? (
{storedKeys.map((item) => (
{getKeyLabel(item.key)}
{getKeyDescription(item.key) || item.key}
{item.hasValue && (
{showValue[item.key] ? ( {revealedValues[item.key]} ) : ( {item.preview} )}
)}
{item.hasValue && ( <> )} {!item.hasValue && ( 未设置 )}
))}
) : (
暂无存储的密钥
)}
{/* Add New Key */}
{!showAddForm ? ( ) : (

添加新密钥

setNewKey(e.target.value)} placeholder="例如: zclaw_custom_key" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
setNewValue(e.target.value)} placeholder="输入密钥值" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
)}
{/* Info Section */}

关于安全存储

  • • Windows: 使用 DPAPI 加密
  • • macOS: 使用 Keychain 存储
  • • Linux: 使用 Secret Service API (gnome-keyring, kwallet 等)
  • • 后备方案: AES-GCM 加密的 localStorage
); }