import React, { useState, useEffect } from 'react'; import { Lock, Key, Globe, AlertCircle, Loader2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { isTauri } from '../../utils/env'; /** * AdminAuthGuard * 针对 Docker/Web 模式的强制鉴权保护层。 * 如果检测到没有存储的 API Key 或后端返回 401,将拦截 UI 并要求输入 Key。 */ export const AdminAuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { t, i18n } = useTranslation(); const [isAuthenticated, setIsAuthenticated] = useState(isTauri()); const [apiKey, setApiKey] = useState(''); const [showLangMenu, setShowLangMenu] = useState(false); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); useEffect(() => { if (isTauri()) return; // 检查 Session 存储 (优先) const sessionKey = sessionStorage.getItem('abv_admin_api_key'); if (sessionKey) { setIsAuthenticated(true); setApiKey(sessionKey); return; } // 检查本地存储 (迁移逻辑) const savedKey = localStorage.getItem('abv_admin_api_key'); if (savedKey) { // 迁移到 sessionStorage 并清理 localStorage sessionStorage.setItem('abv_admin_api_key', savedKey); localStorage.removeItem('abv_admin_api_key'); setIsAuthenticated(true); setApiKey(savedKey); } // 监听全局 401 事件 const handleUnauthorized = () => { sessionStorage.removeItem('abv_admin_api_key'); localStorage.removeItem('abv_admin_api_key'); // 双重清理确保万一 setIsAuthenticated(false); }; window.addEventListener('abv-unauthorized', handleUnauthorized); return () => window.removeEventListener('abv-unauthorized', handleUnauthorized); }, []); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); const trimmedKey = apiKey.trim(); if (!trimmedKey) return; setIsLoading(true); setError(''); try { // 先临时存储 key,用于验证请求 sessionStorage.setItem('abv_admin_api_key', trimmedKey); // 调用一个需要认证的 API 来验证密码是否正确 const response = await fetch('/api/accounts', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${trimmedKey}`, 'x-api-key': trimmedKey } }); if (response.ok || response.status === 204) { // 验证成功 localStorage.removeItem('abv_admin_api_key'); setIsAuthenticated(true); window.location.reload(); } else if (response.status === 401) { // 密码错误 sessionStorage.removeItem('abv_admin_api_key'); setError(t('login.error_invalid_key')); } else { // 其他错误,但可能密码是对的 setIsAuthenticated(true); window.location.reload(); } } catch (err) { // 网络错误等 sessionStorage.removeItem('abv_admin_api_key'); setError(t('login.error_network')); } finally { setIsLoading(false); } }; const changeLanguage = (lng: string) => { i18n.changeLanguage(lng); setShowLangMenu(false); }; const languages = [ { code: 'zh', name: '简体中文' }, { code: 'zh-TW', name: '繁體中文' }, { code: 'en', name: 'English' }, { code: 'ja', name: '日本語' }, { code: 'ko', name: '한국어' }, { code: 'ru', name: 'Русский' }, { code: 'tr', name: 'Türkçe' }, { code: 'vi', name: 'Tiếng Việt' }, { code: 'pt', name: 'Português' }, { code: 'ar', name: 'العربية' }, { code: 'es', name: 'Español' }, { code: 'my', name: 'Bahasa Melayu' }, ]; if (isAuthenticated) { return <>{children}; } return (
{/* 语言切换按钮 */}
{showLangMenu && (
{languages.map((lang) => ( ))}
)}

{t('login.title')}

{t('login.desc')}

{ setApiKey(e.target.value); setError(''); }} autoFocus disabled={isLoading} />
{error && (
{error}
)}

{t('login.note')}
{t('login.lookup_hint')}
{t('login.config_hint')}

); };