默认密码禁止添加账号

This commit is contained in:
zhinianboke 2025-12-24 10:36:51 +08:00
parent 446320b62c
commit 0132b627a7
6 changed files with 188 additions and 21 deletions

View File

@ -67,8 +67,9 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
})
setAuthState('authenticated')
// 检查是否已同意免责声明
const disclaimerAccepted = localStorage.getItem('disclaimer_accepted')
// 检查是否已同意免责声明(针对每个用户)
const disclaimerKey = `disclaimer_accepted_${result.user_id}`
const disclaimerAccepted = localStorage.getItem(disclaimerKey)
if (!disclaimerAccepted) {
setShowDisclaimer(true)
}
@ -88,7 +89,11 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
}, [_hasHydrated, isAuthenticated, storeToken, setAuth, clearAuth])
const handleDisclaimerAgree = () => {
localStorage.setItem('disclaimer_accepted', 'true')
// 使用用户ID存储免责声明同意状态
const userId = useAuthStore.getState().user?.user_id
if (userId) {
localStorage.setItem(`disclaimer_accepted_${userId}`, 'true')
}
setShowDisclaimer(false)
}

View File

@ -100,9 +100,9 @@ export const testEmailSend = async (email: string): Promise<ApiResponse> => {
}
}
// 修改密码(管理员
// 修改密码(普通用户
export const changePassword = async (data: { current_password: string; new_password: string }): Promise<ApiResponse> => {
return post('/change-admin-password', data)
return post('/change-password', data)
}
// 获取备份文件列表(管理员)
@ -171,3 +171,15 @@ export const getUserSetting = async (key: string): Promise<{ success: boolean; v
export const updateUserSetting = async (key: string, value: string, description?: string): Promise<ApiResponse> => {
return put(`/user-settings/${key}`, { value, description })
}
// 检查是否使用默认密码
export const checkDefaultPassword = async (): Promise<{ using_default: boolean }> => {
try {
const result = await get<{ using_default: boolean }>('/api/check-default-password')
console.log('checkDefaultPassword result:', result)
return result
} catch (error) {
console.error('checkDefaultPassword error:', error)
return { using_default: false }
}
}

View File

@ -1,8 +1,9 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import type { FormEvent } from 'react'
import { Plus, RefreshCw, QrCode, Key, Edit2, Trash2, Power, PowerOff, X, Loader2, Clock, CheckCircle, MessageSquare, Bot, Eye, EyeOff } from 'lucide-react'
import { Plus, RefreshCw, QrCode, Key, Edit2, Trash2, Power, PowerOff, X, Loader2, Clock, CheckCircle, MessageSquare, Bot, Eye, EyeOff, AlertTriangle } from 'lucide-react'
import { getAccountDetails, deleteAccount, updateAccountCookie, updateAccountStatus, updateAccountRemark, addAccount, generateQRLogin, checkQRLoginStatus, passwordLogin, updateAccountAutoConfirm, updateAccountPauseDuration, getAllAIReplySettings, getAIReplySettings, updateAIReplySettings, updateAccountLoginInfo, type AIReplySettings } from '@/api/accounts'
import { getKeywords, getDefaultReply, updateDefaultReply } from '@/api/keywords'
import { checkDefaultPassword } from '@/api/settings'
import { useUIStore } from '@/store/uiStore'
import { useAuthStore } from '@/store/authStore'
import { PageLoading } from '@/components/common/Loading'
@ -17,10 +18,14 @@ interface AccountWithKeywordCount extends AccountDetail {
export function Accounts() {
const { addToast } = useUIStore()
const { isAuthenticated, token, _hasHydrated } = useAuthStore()
const { isAuthenticated, token, _hasHydrated, user } = useAuthStore()
const [loading, setLoading] = useState(true)
const [accounts, setAccounts] = useState<AccountWithKeywordCount[]>([])
const [activeModal, setActiveModal] = useState<ModalType>(null)
// 默认密码检查状态
const [usingDefaultPassword, setUsingDefaultPassword] = useState(false)
const [showPasswordWarning, setShowPasswordWarning] = useState(false)
// 默认回复管理状态
const [defaultReplyAccount, setDefaultReplyAccount] = useState<AccountWithKeywordCount | null>(null)
@ -114,6 +119,20 @@ export function Accounts() {
loadAccounts()
}, [_hasHydrated, isAuthenticated, token])
// 单独的 useEffect 检查默认密码
useEffect(() => {
if (!_hasHydrated || !isAuthenticated || !token || !user) return
// 检查是否使用默认密码
const checkPassword = async () => {
if (user.is_admin) {
const result = await checkDefaultPassword()
setUsingDefaultPassword(result.using_default)
}
}
checkPassword()
}, [_hasHydrated, isAuthenticated, token, user])
// 清理扫码检查定时器
const clearQrCheck = useCallback(() => {
if (qrCheckIntervalRef.current) {
@ -139,6 +158,12 @@ export function Accounts() {
// ==================== 扫码登录 ====================
const startQRCodeLogin = async () => {
// 检查是否使用默认密码
if (usingDefaultPassword) {
setShowPasswordWarning(true)
return
}
setActiveModal('qrcode')
setQrStatus('loading')
try {
@ -158,6 +183,15 @@ export function Accounts() {
addToast({ type: 'error', message: '生成二维码失败' })
}
}
// 检查默认密码后打开弹窗
const handleOpenModal = (modal: ModalType) => {
if (usingDefaultPassword && (modal === 'password' || modal === 'manual')) {
setShowPasswordWarning(true)
return
}
setActiveModal(modal)
}
const startQrCheck = (sessionId: string) => {
clearQrCheck()
@ -519,7 +553,7 @@ export function Accounts() {
{/* 账号密码登录 */}
<button
onClick={() => setActiveModal('password')}
onClick={() => handleOpenModal('password')}
className="flex items-center gap-3 p-4 rounded-md border border-slate-200 dark:border-slate-700
hover:border-blue-300 dark:hover:border-blue-700 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors text-left"
>
@ -534,7 +568,7 @@ export function Accounts() {
{/* 手动输入 */}
<button
onClick={() => setActiveModal('manual')}
onClick={() => handleOpenModal('manual')}
className="flex items-center gap-3 p-4 rounded-md border border-slate-200 dark:border-slate-700
hover:border-blue-300 dark:hover:border-blue-700 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors text-left"
>
@ -1194,6 +1228,48 @@ export function Accounts() {
</div>
</div>
)}
{/* 默认密码警告弹窗 */}
{showPasswordWarning && (
<div className="modal-overlay">
<div className="modal-content max-w-md">
<div className="modal-header">
<h2 className="modal-title flex items-center gap-2 text-amber-600">
<AlertTriangle className="w-5 h-5" />
</h2>
<button onClick={() => setShowPasswordWarning(false)} className="modal-close">
<X className="w-4 h-4" />
</button>
</div>
<div className="modal-body">
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4">
<p className="text-amber-800 dark:text-amber-200 text-sm">
使 <code className="bg-amber-100 dark:bg-amber-800 px-1 rounded">admin123</code>
</p>
</div>
<p className="text-slate-600 dark:text-slate-400 text-sm">
<strong></strong>
</p>
</div>
<div className="modal-footer">
<button onClick={() => setShowPasswordWarning(false)} className="btn-ios-secondary">
</button>
<button
onClick={() => {
setShowPasswordWarning(false)
window.location.href = '/settings'
}}
className="btn-ios-primary"
>
</button>
</div>
</div>
</div>
)}
</div>
)
}

View File

@ -251,29 +251,36 @@ export function Settings() {
return <PageLoading />
}
const isAdmin = user?.is_admin
return (
<div className="space-y-4">
{/* Header */}
<div className="page-header flex-between flex-wrap gap-4">
<div>
<h1 className="page-title"></h1>
<p className="page-description"></p>
<p className="page-description">{isAdmin ? '配置系统全局设置' : '修改个人密码'}</p>
</div>
<div className="flex gap-2">
<button onClick={loadSettings} className="btn-ios-secondary">
<RefreshCw className="w-4 h-4" />
</button>
<button onClick={handleSave} disabled={saving} className="btn-ios-primary">
{saving ? <ButtonLoading /> : <Save className="w-4 h-4" />}
</button>
{isAdmin && (
<>
<button onClick={loadSettings} className="btn-ios-secondary">
<RefreshCw className="w-4 h-4" />
</button>
<button onClick={handleSave} disabled={saving} className="btn-ios-primary">
{saving ? <ButtonLoading /> : <Save className="w-4 h-4" />}
</button>
</>
)}
</div>
</div>
{/* 双列布局 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* 左列 */}
{/* 左列 - 仅管理员可见 */}
{isAdmin && (
<div className="space-y-4">
{/* General Settings */}
<div className="vben-card">
@ -424,10 +431,12 @@ export function Settings() {
</div>
</div>
</div>
)}
{/* 右列 */}
<div className="space-y-4">
{/* Email Settings */}
{/* Email Settings - 仅管理员可见 */}
{isAdmin && (
<div className="vben-card">
<div className="vben-card-header">
<h2 className="vben-card-title">
@ -526,6 +535,7 @@ export function Settings() {
</button>
</div>
</div>
)}
{/* 密码修改 */}
<div className="vben-card">

View File

@ -167,6 +167,10 @@ export default defineConfig(({ command }) => ({
target: 'http://localhost:8080',
changeOrigin: true,
},
'/check-default-password': {
target: 'http://localhost:8080',
changeOrigin: true,
},
'/logout': {
target: 'http://localhost:8080',
changeOrigin: true,

View File

@ -547,6 +547,7 @@ async def login(request: LoginRequest):
SESSION_TOKENS[token] = {
'user_id': user['id'],
'username': user['username'],
'is_admin': user.get('is_admin', False) or user['username'] == ADMIN_USERNAME,
'timestamp': time.time()
}
@ -582,6 +583,7 @@ async def login(request: LoginRequest):
SESSION_TOKENS[token] = {
'user_id': user['id'],
'username': user['username'],
'is_admin': user.get('is_admin', False) or user['username'] == ADMIN_USERNAME,
'timestamp': time.time()
}
@ -628,6 +630,7 @@ async def login(request: LoginRequest):
SESSION_TOKENS[token] = {
'user_id': user['id'],
'username': user['username'],
'is_admin': user.get('is_admin', False) or user['username'] == ADMIN_USERNAME,
'timestamp': time.time()
}
@ -694,6 +697,63 @@ async def change_admin_password(request: ChangePasswordRequest, admin_user: Dict
return {"success": False, "message": "系统错误"}
# 普通用户修改密码接口
@app.post('/change-password')
async def change_user_password(request: ChangePasswordRequest, current_user: Dict[str, Any] = Depends(get_current_user)):
from db_manager import db_manager
try:
username = current_user.get('username')
user_id = current_user.get('user_id')
if not username:
return {"success": False, "message": "无法获取用户信息"}
# 验证当前密码
if not db_manager.verify_user_password(username, request.current_password):
return {"success": False, "message": "当前密码错误"}
# 更新密码
success = db_manager.update_user_password(username, request.new_password)
if success:
logger.info(f"{username}#{user_id}】用户密码修改成功")
return {"success": True, "message": "密码修改成功"}
else:
return {"success": False, "message": "密码修改失败"}
except Exception as e:
logger.error(f"修改用户密码异常: {e}")
return {"success": False, "message": "系统错误"}
# 检查是否使用默认密码
@app.get('/api/check-default-password')
async def check_default_password(current_user: Dict[str, Any] = Depends(get_current_user)):
from db_manager import db_manager
try:
username = current_user.get('username')
is_admin = current_user.get('is_admin', False)
logger.info(f"检查默认密码: username={username}, is_admin={is_admin}")
# 只检查admin用户
if not is_admin or username != 'admin':
logger.info(f"非admin用户跳过检查")
return {"using_default": False}
# 检查是否使用默认密码
using_default = db_manager.verify_user_password('admin', DEFAULT_ADMIN_PASSWORD)
logger.info(f"默认密码检查结果: {using_default}, DEFAULT_ADMIN_PASSWORD={DEFAULT_ADMIN_PASSWORD}")
return {"using_default": using_default}
except Exception as e:
logger.error(f"检查默认密码异常: {e}")
return {"using_default": False}
# 生成图形验证码接口
@app.post('/generate-captcha')
async def generate_captcha(request: CaptchaRequest):
@ -5915,7 +5975,7 @@ def delete_order(order_id: str, current_user: Dict[str, Any] = Depends(get_curre
# 然后由 React Router 在客户端处理路由
# 定义不需要返回前端页面的路径前缀API 路径)
API_PREFIXES = ['/api/', '/static/', '/health', '/login', '/logout', '/register', '/verify']
API_PREFIXES = ['/api/', '/static/', '/health', '/login', '/logout', '/register', '/verify', '/check-default-password', '/change-password', '/change-admin-password']
@app.get('/{path:path}', response_class=HTMLResponse)
async def catch_all_route(path: str):