mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 16:20:25 +00:00
* refactor: 将默认密码相关逻辑重构为后端处理 * refactor: 日志路由进行脱敏,生成随机密码使用node:crypto.randomBytes * feat: 更新密码功能增强,添加新密码强度验证和旧密码检查 * feat: 给文件管理添加WebUI配置文件的脱敏处理和验证逻辑 * refactor: 优化网络显示卡片按钮样式和行为,调整按钮属性以提升用户体验 * feat: 增强路径处理逻辑,添加安全验证以防止路径遍历攻击 * feat: 增强文件路径处理逻辑,添加安全验证以防止路径遍历攻击,并优化查询参数提取 * feat: CodeQL不认可 受不了
182 lines
6.1 KiB
TypeScript
182 lines
6.1 KiB
TypeScript
import { Button } from '@heroui/button'
|
||
import { CardBody, CardHeader } from '@heroui/card'
|
||
import { Image } from '@heroui/image'
|
||
import { Input } from '@heroui/input'
|
||
import { useLocalStorage } from '@uidotdev/usehooks'
|
||
import { useEffect, useState } from 'react'
|
||
import { toast } from 'react-hot-toast'
|
||
import { IoKeyOutline } from 'react-icons/io5'
|
||
import { useNavigate } from 'react-router-dom'
|
||
|
||
import key from '@/const/key'
|
||
|
||
import HoverEffectCard from '@/components/effect_card'
|
||
import { title } from '@/components/primitives'
|
||
import { ThemeSwitch } from '@/components/theme-switch'
|
||
|
||
import logo from '@/assets/images/logo.png'
|
||
import WebUIManager from '@/controllers/webui_manager'
|
||
import PureLayout from '@/layouts/pure'
|
||
|
||
export default function WebLoginPage() {
|
||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||
const token = urlSearchParams.get('token')
|
||
const navigate = useNavigate()
|
||
const [tokenValue, setTokenValue] = useState<string>(token || '')
|
||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||
const [, setLocalToken] = useLocalStorage<string>(key.token, '')
|
||
const onSubmit = async () => {
|
||
if (!tokenValue) {
|
||
toast.error('请输入token')
|
||
|
||
return
|
||
}
|
||
setIsLoading(true)
|
||
try {
|
||
const data = await WebUIManager.loginWithToken(tokenValue)
|
||
|
||
if (data) {
|
||
setLocalToken(data)
|
||
navigate('/qq_login', { replace: true })
|
||
}
|
||
} catch (error) {
|
||
toast.error((error as Error).message)
|
||
} finally {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
// 处理全局键盘事件
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === 'Enter' && !isLoading) {
|
||
onSubmit()
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
document.addEventListener('keydown', handleKeyDown)
|
||
|
||
// 清理函数
|
||
return () => {
|
||
document.removeEventListener('keydown', handleKeyDown)
|
||
}
|
||
}, [tokenValue, isLoading]) // 依赖项包含用于登录的状态
|
||
|
||
useEffect(() => {
|
||
if (token) {
|
||
onSubmit()
|
||
}
|
||
}, [])
|
||
|
||
return (
|
||
<>
|
||
<title>WebUI登录 - NapCat WebUI</title>
|
||
<PureLayout>
|
||
<div className="w-[608px] max-w-full py-8 px-2 md:px-8 overflow-hidden">
|
||
<HoverEffectCard
|
||
className="items-center gap-4 pt-0 pb-6 bg-default-50"
|
||
maxXRotation={3}
|
||
maxYRotation={3}
|
||
>
|
||
<CardHeader className="inline-block max-w-lg text-center justify-center">
|
||
<div className="flex items-center justify-center w-full gap-2 pt-10">
|
||
<Image alt="logo" height="7em" src={logo} />
|
||
<div>
|
||
<span className={title()}>Web </span>
|
||
<span className={title({ color: 'violet' })}>
|
||
Login
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<ThemeSwitch className="absolute right-4 top-4" />
|
||
</CardHeader>
|
||
|
||
<CardBody className="flex gap-5 py-5 px-5 md:px-10">
|
||
<form
|
||
onSubmit={(e) => {
|
||
e.preventDefault()
|
||
onSubmit()
|
||
}}
|
||
>
|
||
{/* 隐藏的用户名字段,帮助浏览器识别登录表单 */}
|
||
<input
|
||
type="text"
|
||
name="username"
|
||
value="napcat-webui"
|
||
autoComplete="username"
|
||
className="absolute -left-[9999px] opacity-0 pointer-events-none"
|
||
readOnly
|
||
tabIndex={-1}
|
||
aria-label="Username"
|
||
/>
|
||
<Input
|
||
isClearable
|
||
type="password"
|
||
name="password"
|
||
autoComplete="current-password"
|
||
classNames={{
|
||
label: 'text-black/50 dark:text-white/90',
|
||
input: [
|
||
'bg-transparent',
|
||
'text-black/90 dark:text-white/90',
|
||
'placeholder:text-default-700/50 dark:placeholder:text-white/60'
|
||
],
|
||
innerWrapper: 'bg-transparent',
|
||
inputWrapper: [
|
||
'shadow-xl',
|
||
'bg-default-100/70',
|
||
'dark:bg-default/60',
|
||
'backdrop-blur-xl',
|
||
'backdrop-saturate-200',
|
||
'hover:bg-default-0/70',
|
||
'dark:hover:bg-default/70',
|
||
'group-data-[focus=true]:bg-default-100/50',
|
||
'dark:group-data-[focus=true]:bg-default/60',
|
||
'!cursor-text'
|
||
]
|
||
}}
|
||
isDisabled={isLoading}
|
||
label="Token"
|
||
placeholder="请输入token"
|
||
radius="lg"
|
||
size="lg"
|
||
startContent={
|
||
<IoKeyOutline className="text-black/50 mb-0.5 dark:text-white/90 text-slate-400 pointer-events-none flex-shrink-0" />
|
||
}
|
||
value={tokenValue}
|
||
onChange={(e) => setTokenValue(e.target.value)}
|
||
onClear={() => setTokenValue('')}
|
||
/>
|
||
</form>
|
||
<div className="text-center text-small text-default-600 dark:text-default-400 px-2">
|
||
💡 提示:请从 NapCat 启动日志中查看登录密钥
|
||
</div>
|
||
<Button
|
||
className="mx-10 mt-10 text-lg py-7"
|
||
color="primary"
|
||
isLoading={isLoading}
|
||
radius="full"
|
||
size="lg"
|
||
variant="shadow"
|
||
onPress={onSubmit}
|
||
>
|
||
{!isLoading && (
|
||
<Image
|
||
alt="logo"
|
||
classNames={{
|
||
wrapper: '-ml-8'
|
||
}}
|
||
height="2em"
|
||
src={logo}
|
||
/>
|
||
)}
|
||
登录
|
||
</Button>
|
||
</CardBody>
|
||
</HoverEffectCard>
|
||
</div>
|
||
</PureLayout>
|
||
</>
|
||
)
|
||
}
|