diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 06ac3b3..32768d0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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) } diff --git a/frontend/src/api/settings.ts b/frontend/src/api/settings.ts index 26b39a0..589ffa2 100644 --- a/frontend/src/api/settings.ts +++ b/frontend/src/api/settings.ts @@ -100,9 +100,9 @@ export const testEmailSend = async (email: string): Promise => { } } -// 修改密码(管理员) +// 修改密码(普通用户) export const changePassword = async (data: { current_password: string; new_password: string }): Promise => { - 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 => { 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 } + } +} diff --git a/frontend/src/pages/accounts/Accounts.tsx b/frontend/src/pages/accounts/Accounts.tsx index ea7ed83..17627eb 100644 --- a/frontend/src/pages/accounts/Accounts.tsx +++ b/frontend/src/pages/accounts/Accounts.tsx @@ -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([]) const [activeModal, setActiveModal] = useState(null) + + // 默认密码检查状态 + const [usingDefaultPassword, setUsingDefaultPassword] = useState(false) + const [showPasswordWarning, setShowPasswordWarning] = useState(false) // 默认回复管理状态 const [defaultReplyAccount, setDefaultReplyAccount] = useState(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() { {/* 账号密码登录 */} + +
+
+

+ 检测到您正在使用默认密码 admin123, + 为了账号安全,请先修改密码后再添加闲鱼账号。 +

+
+

+ 请前往 系统设置 页面修改您的登录密码。 +

+
+
+ + +
+ + + )} ) } diff --git a/frontend/src/pages/settings/Settings.tsx b/frontend/src/pages/settings/Settings.tsx index 4589b5b..72c6eaf 100644 --- a/frontend/src/pages/settings/Settings.tsx +++ b/frontend/src/pages/settings/Settings.tsx @@ -251,29 +251,36 @@ export function Settings() { return } + const isAdmin = user?.is_admin + return (
{/* Header */}

系统设置

-

配置系统全局设置

+

{isAdmin ? '配置系统全局设置' : '修改个人密码'}

- - + {isAdmin && ( + <> + + + + )}
{/* 双列布局 */}
- {/* 左列 */} + {/* 左列 - 仅管理员可见 */} + {isAdmin && (
{/* General Settings */}
@@ -424,10 +431,12 @@ export function Settings() {
+ )} {/* 右列 */}
- {/* Email Settings */} + {/* Email Settings - 仅管理员可见 */} + {isAdmin && (

@@ -526,6 +535,7 @@ export function Settings() {

+ )} {/* 密码修改 */}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 69080af..901aba2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -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, diff --git a/reply_server.py b/reply_server.py index e37b11e..c7808db 100644 --- a/reply_server.py +++ b/reply_server.py @@ -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):