/** * 访问认证页 — 复用同一组件处理「首次设密码」和「登录」两种状态。 * * 根据后端 /api/auth/status 的 configured 字段决定显示: * - configured=false → 显示「设置访问密码」(首次) * - configured=true → 显示「登录」 * * 安全: * - 设密码接口后端限本机/内网; 公网用户设密码会被 403 拒绝, 页面据此提示。 * - 登录失败由后端限流(5次锁5分钟), 429 时前端显示等待提示。 */ import { useEffect, useState, type FormEvent } from 'react' import { useNavigate } from 'react-router-dom' import { useMutation } from '@tanstack/react-query' import { motion } from 'framer-motion' import { Eye, EyeOff, Loader2, Lock, ShieldCheck, ShieldAlert, Sparkles } from 'lucide-react' import { api } from '@/lib/api' import { Logo } from '@/components/Logo' import { cn } from '@/lib/cn' export function Auth() { const navigate = useNavigate() const [username, setUsername] = useState('admin') const [password, setPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('') // 仅设密码时用 const [showPwd, setShowPwd] = useState(false) const [localError, setLocalError] = useState('') // 取认证状态(是否已设密码) const [status, setStatus] = useState<{ configured: boolean } | null>(null) useEffect(() => { api.authStatus().then(s => { setStatus(s) // 已登录的话直接进面板(避免登录页死循环) if (s.authenticated) navigate('/', { replace: true }) }).catch(() => setStatus({ configured: false })) }, [navigate]) const isSetup = !status?.configured // configured=false → 设密码模式 // 登录 / 设密码 共用一个 mutation(按 isSetup 调不同接口) const submitMut = useMutation({ mutationFn: async () => { if (isSetup) { return api.authSetup(password) } return api.authLogin(username, password) }, onSuccess: () => { // 成功: 跳回原页面(或首页) const redirect = new URLSearchParams(window.location.search).get('redirect') || '/' navigate(redirect, { replace: true }) }, onError: (err: any) => { const msg = err?.message || (isSetup ? '设置失败' : '登录失败') // 设密码/登录失败必须显示: 401(密码错)/403(公网设密码被拒)/429(限流) 都要提示 setLocalError(msg) }, }) const handleSubmit = (e: FormEvent) => { e.preventDefault() setLocalError('') if (isSetup) { if (password.length < 6) { setLocalError('密码至少 6 位'); return } if (password !== confirmPassword) { setLocalError('两次密码不一致'); return } } submitMut.mutate() } if (!status) { return (
出于安全考虑, 首次设置密码需在服务器本机或内网访问时操作。公网环境下仅可登录。
详细配置说明见{' '} 访问密码部署文档