默认密码禁止添加账号
This commit is contained in:
parent
446320b62c
commit
0132b627a7
@ -67,8 +67,9 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|||||||
})
|
})
|
||||||
setAuthState('authenticated')
|
setAuthState('authenticated')
|
||||||
|
|
||||||
// 检查是否已同意免责声明
|
// 检查是否已同意免责声明(针对每个用户)
|
||||||
const disclaimerAccepted = localStorage.getItem('disclaimer_accepted')
|
const disclaimerKey = `disclaimer_accepted_${result.user_id}`
|
||||||
|
const disclaimerAccepted = localStorage.getItem(disclaimerKey)
|
||||||
if (!disclaimerAccepted) {
|
if (!disclaimerAccepted) {
|
||||||
setShowDisclaimer(true)
|
setShowDisclaimer(true)
|
||||||
}
|
}
|
||||||
@ -88,7 +89,11 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|||||||
}, [_hasHydrated, isAuthenticated, storeToken, setAuth, clearAuth])
|
}, [_hasHydrated, isAuthenticated, storeToken, setAuth, clearAuth])
|
||||||
|
|
||||||
const handleDisclaimerAgree = () => {
|
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)
|
setShowDisclaimer(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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> => {
|
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> => {
|
export const updateUserSetting = async (key: string, value: string, description?: string): Promise<ApiResponse> => {
|
||||||
return put(`/user-settings/${key}`, { value, description })
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import type { FormEvent } 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 { 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 { getKeywords, getDefaultReply, updateDefaultReply } from '@/api/keywords'
|
||||||
|
import { checkDefaultPassword } from '@/api/settings'
|
||||||
import { useUIStore } from '@/store/uiStore'
|
import { useUIStore } from '@/store/uiStore'
|
||||||
import { useAuthStore } from '@/store/authStore'
|
import { useAuthStore } from '@/store/authStore'
|
||||||
import { PageLoading } from '@/components/common/Loading'
|
import { PageLoading } from '@/components/common/Loading'
|
||||||
@ -17,10 +18,14 @@ interface AccountWithKeywordCount extends AccountDetail {
|
|||||||
|
|
||||||
export function Accounts() {
|
export function Accounts() {
|
||||||
const { addToast } = useUIStore()
|
const { addToast } = useUIStore()
|
||||||
const { isAuthenticated, token, _hasHydrated } = useAuthStore()
|
const { isAuthenticated, token, _hasHydrated, user } = useAuthStore()
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [accounts, setAccounts] = useState<AccountWithKeywordCount[]>([])
|
const [accounts, setAccounts] = useState<AccountWithKeywordCount[]>([])
|
||||||
const [activeModal, setActiveModal] = useState<ModalType>(null)
|
const [activeModal, setActiveModal] = useState<ModalType>(null)
|
||||||
|
|
||||||
|
// 默认密码检查状态
|
||||||
|
const [usingDefaultPassword, setUsingDefaultPassword] = useState(false)
|
||||||
|
const [showPasswordWarning, setShowPasswordWarning] = useState(false)
|
||||||
|
|
||||||
// 默认回复管理状态
|
// 默认回复管理状态
|
||||||
const [defaultReplyAccount, setDefaultReplyAccount] = useState<AccountWithKeywordCount | null>(null)
|
const [defaultReplyAccount, setDefaultReplyAccount] = useState<AccountWithKeywordCount | null>(null)
|
||||||
@ -114,6 +119,20 @@ export function Accounts() {
|
|||||||
loadAccounts()
|
loadAccounts()
|
||||||
}, [_hasHydrated, isAuthenticated, token])
|
}, [_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(() => {
|
const clearQrCheck = useCallback(() => {
|
||||||
if (qrCheckIntervalRef.current) {
|
if (qrCheckIntervalRef.current) {
|
||||||
@ -139,6 +158,12 @@ export function Accounts() {
|
|||||||
|
|
||||||
// ==================== 扫码登录 ====================
|
// ==================== 扫码登录 ====================
|
||||||
const startQRCodeLogin = async () => {
|
const startQRCodeLogin = async () => {
|
||||||
|
// 检查是否使用默认密码
|
||||||
|
if (usingDefaultPassword) {
|
||||||
|
setShowPasswordWarning(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setActiveModal('qrcode')
|
setActiveModal('qrcode')
|
||||||
setQrStatus('loading')
|
setQrStatus('loading')
|
||||||
try {
|
try {
|
||||||
@ -158,6 +183,15 @@ export function Accounts() {
|
|||||||
addToast({ type: 'error', message: '生成二维码失败' })
|
addToast({ type: 'error', message: '生成二维码失败' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查默认密码后打开弹窗
|
||||||
|
const handleOpenModal = (modal: ModalType) => {
|
||||||
|
if (usingDefaultPassword && (modal === 'password' || modal === 'manual')) {
|
||||||
|
setShowPasswordWarning(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setActiveModal(modal)
|
||||||
|
}
|
||||||
|
|
||||||
const startQrCheck = (sessionId: string) => {
|
const startQrCheck = (sessionId: string) => {
|
||||||
clearQrCheck()
|
clearQrCheck()
|
||||||
@ -519,7 +553,7 @@ export function Accounts() {
|
|||||||
|
|
||||||
{/* 账号密码登录 */}
|
{/* 账号密码登录 */}
|
||||||
<button
|
<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
|
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"
|
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
|
<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
|
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"
|
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>
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -251,29 +251,36 @@ export function Settings() {
|
|||||||
return <PageLoading />
|
return <PageLoading />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAdmin = user?.is_admin
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="page-header flex-between flex-wrap gap-4">
|
<div className="page-header flex-between flex-wrap gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">系统设置</h1>
|
<h1 className="page-title">系统设置</h1>
|
||||||
<p className="page-description">配置系统全局设置</p>
|
<p className="page-description">{isAdmin ? '配置系统全局设置' : '修改个人密码'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button onClick={loadSettings} className="btn-ios-secondary">
|
{isAdmin && (
|
||||||
<RefreshCw className="w-4 h-4" />
|
<>
|
||||||
刷新
|
<button onClick={loadSettings} className="btn-ios-secondary">
|
||||||
</button>
|
<RefreshCw className="w-4 h-4" />
|
||||||
<button onClick={handleSave} disabled={saving} className="btn-ios-primary">
|
刷新
|
||||||
{saving ? <ButtonLoading /> : <Save className="w-4 h-4" />}
|
</button>
|
||||||
保存设置
|
<button onClick={handleSave} disabled={saving} className="btn-ios-primary">
|
||||||
</button>
|
{saving ? <ButtonLoading /> : <Save className="w-4 h-4" />}
|
||||||
|
保存设置
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 双列布局 */}
|
{/* 双列布局 */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{/* 左列 */}
|
{/* 左列 - 仅管理员可见 */}
|
||||||
|
{isAdmin && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* General Settings */}
|
{/* General Settings */}
|
||||||
<div className="vben-card">
|
<div className="vben-card">
|
||||||
@ -424,10 +431,12 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 右列 */}
|
{/* 右列 */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Email Settings */}
|
{/* Email Settings - 仅管理员可见 */}
|
||||||
|
{isAdmin && (
|
||||||
<div className="vben-card">
|
<div className="vben-card">
|
||||||
<div className="vben-card-header">
|
<div className="vben-card-header">
|
||||||
<h2 className="vben-card-title">
|
<h2 className="vben-card-title">
|
||||||
@ -526,6 +535,7 @@ export function Settings() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 密码修改 */}
|
{/* 密码修改 */}
|
||||||
<div className="vben-card">
|
<div className="vben-card">
|
||||||
|
|||||||
@ -167,6 +167,10 @@ export default defineConfig(({ command }) => ({
|
|||||||
target: 'http://localhost:8080',
|
target: 'http://localhost:8080',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
'/check-default-password': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
'/logout': {
|
'/logout': {
|
||||||
target: 'http://localhost:8080',
|
target: 'http://localhost:8080',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|||||||
@ -547,6 +547,7 @@ async def login(request: LoginRequest):
|
|||||||
SESSION_TOKENS[token] = {
|
SESSION_TOKENS[token] = {
|
||||||
'user_id': user['id'],
|
'user_id': user['id'],
|
||||||
'username': user['username'],
|
'username': user['username'],
|
||||||
|
'is_admin': user.get('is_admin', False) or user['username'] == ADMIN_USERNAME,
|
||||||
'timestamp': time.time()
|
'timestamp': time.time()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,6 +583,7 @@ async def login(request: LoginRequest):
|
|||||||
SESSION_TOKENS[token] = {
|
SESSION_TOKENS[token] = {
|
||||||
'user_id': user['id'],
|
'user_id': user['id'],
|
||||||
'username': user['username'],
|
'username': user['username'],
|
||||||
|
'is_admin': user.get('is_admin', False) or user['username'] == ADMIN_USERNAME,
|
||||||
'timestamp': time.time()
|
'timestamp': time.time()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,6 +630,7 @@ async def login(request: LoginRequest):
|
|||||||
SESSION_TOKENS[token] = {
|
SESSION_TOKENS[token] = {
|
||||||
'user_id': user['id'],
|
'user_id': user['id'],
|
||||||
'username': user['username'],
|
'username': user['username'],
|
||||||
|
'is_admin': user.get('is_admin', False) or user['username'] == ADMIN_USERNAME,
|
||||||
'timestamp': time.time()
|
'timestamp': time.time()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,6 +697,63 @@ async def change_admin_password(request: ChangePasswordRequest, admin_user: Dict
|
|||||||
return {"success": False, "message": "系统错误"}
|
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')
|
@app.post('/generate-captcha')
|
||||||
async def generate_captcha(request: CaptchaRequest):
|
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 在客户端处理路由
|
# 然后由 React Router 在客户端处理路由
|
||||||
|
|
||||||
# 定义不需要返回前端页面的路径前缀(API 路径)
|
# 定义不需要返回前端页面的路径前缀(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)
|
@app.get('/{path:path}', response_class=HTMLResponse)
|
||||||
async def catch_all_route(path: str):
|
async def catch_all_route(path: str):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user