diff --git a/app.py b/app.py index 6619ba39..667c82c7 100755 --- a/app.py +++ b/app.py @@ -374,13 +374,16 @@ else: # Cookie 配置 - 重要:HTTPS 环境必须设置 SECURE=True app.config['SESSION_COOKIE_SECURE'] = True # 生产环境使用 HTTPS,必须为 True app.config['SESSION_COOKIE_HTTPONLY'] = True # 生产环境应设为True,防止XSS攻击 -app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # 使用'Lax'以平衡安全性和功能性 +# SameSite='None' 允许微信内置浏览器在 OAuth 重定向后携带 Cookie +# 必须配合 Secure=True 使用(已在上面配置) +app.config['SESSION_COOKIE_SAMESITE'] = 'None' # 微信浏览器兼容性:必须为 None app.config['SESSION_COOKIE_DOMAIN'] = None # 不限制域名 app.config['SESSION_COOKIE_PATH'] = '/' # 设置cookie路径 app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) # session持续7天 app.config['REMEMBER_COOKIE_DURATION'] = timedelta(days=30) # 记住登录30天 app.config['REMEMBER_COOKIE_SECURE'] = True # 生产环境使用 HTTPS,必须为 True app.config['REMEMBER_COOKIE_HTTPONLY'] = True # 防止XSS攻击 +app.config['REMEMBER_COOKIE_SAMESITE'] = 'None' # 微信浏览器兼容性 # 初始化 Flask-Session(仅在启用 Redis Session 时) if USE_REDIS_SESSION: @@ -3447,7 +3450,16 @@ def register_with_phone(): @app.route('/api/account/phone/send-code', methods=['POST']) def send_sms_bind_code(): """发送绑定手机验证码(需已登录)""" + # 调试日志:检查 session 状态 + user_agent = request.headers.get('User-Agent', '') + is_wechat = 'MicroMessenger' in user_agent + print(f"[绑定手机验证码] User-Agent: {user_agent[:100]}...") + print(f"[绑定手机验证码] 是否微信浏览器: {is_wechat}") + print(f"[绑定手机验证码] session 内容: logged_in={session.get('logged_in')}, user_id={session.get('user_id')}") + print(f"[绑定手机验证码] Cookie: {request.cookies.get('session', 'None')[:20] if request.cookies.get('session') else 'None'}...") + if not session.get('logged_in'): + print(f"[绑定手机验证码] ❌ 未登录,拒绝请求") return jsonify({'error': '未登录'}), 401 data = request.get_json() diff --git a/src/components/Navbars/components/MobileDrawer/MobileDrawer.js b/src/components/Navbars/components/MobileDrawer/MobileDrawer.js index f7091906..bc1218ed 100644 --- a/src/components/Navbars/components/MobileDrawer/MobileDrawer.js +++ b/src/components/Navbars/components/MobileDrawer/MobileDrawer.js @@ -92,7 +92,9 @@ const MobileDrawer = memo(({ /> {getDisplayName()} - {user.email} + {user.phone && ( + {user.phone} + )} diff --git a/src/components/Navbars/components/Navigation/PersonalCenterMenu.js b/src/components/Navbars/components/Navigation/PersonalCenterMenu.js index d68a1611..510f8281 100644 --- a/src/components/Navbars/components/Navigation/PersonalCenterMenu.js +++ b/src/components/Navbars/components/Navigation/PersonalCenterMenu.js @@ -61,7 +61,6 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => { {/* 用户信息区 */} {getDisplayName()} - {user.email} {user.phone && ( {user.phone} )} diff --git a/src/components/Navbars/components/UserMenu/TabletUserMenu.js b/src/components/Navbars/components/UserMenu/TabletUserMenu.js index 5ca9a8b3..dc813313 100644 --- a/src/components/Navbars/components/UserMenu/TabletUserMenu.js +++ b/src/components/Navbars/components/UserMenu/TabletUserMenu.js @@ -75,7 +75,6 @@ const TabletUserMenu = memo(({ {/* 用户信息区 */} {getDisplayName()} - {user.email} {user.phone && ( {user.phone} )} diff --git a/src/views/Settings/SettingsPage.js b/src/views/Settings/SettingsPage.js index a869ccc6..b5aa4c5d 100644 --- a/src/views/Settings/SettingsPage.js +++ b/src/views/Settings/SettingsPage.js @@ -11,16 +11,10 @@ import { Input, FormControl, FormLabel, - Switch, Card, CardBody, CardHeader, - Divider, useToast, - Alert, - AlertIcon, - AlertTitle, - AlertDescription, Modal, ModalOverlay, ModalContent, @@ -30,31 +24,17 @@ import { ModalCloseButton, useDisclosure, Badge, - IconButton, - Textarea, - Select, - useColorMode, - useColorModeValue, Tabs, TabList, TabPanels, Tab, TabPanel, - InputGroup, - InputRightElement, PinInput, - PinInputField, - SimpleGrid + PinInputField } from '@chakra-ui/react'; import { - EditIcon, - ViewIcon, - ViewOffIcon, LinkIcon, - DeleteIcon, // 替换 UnlinkIcon - WarningIcon, - CheckIcon, - CloseIcon + DeleteIcon } from '@chakra-ui/icons'; import { FaWeixin, FaMobile, FaEnvelope } from 'react-icons/fa'; import { useAuth } from '../../contexts/AuthContext'; @@ -63,8 +43,7 @@ import { logger } from '../../utils/logger'; import { useProfileEvents } from '../../hooks/useProfileEvents'; export default function SettingsPage() { - const { user, updateUser, logout } = useAuth(); - const { colorMode, toggleColorMode } = useColorMode(); + const { user, updateUser } = useAuth(); const toast = useToast(); // 深色模式固定颜色(Settings 页面始终使用深色主题) @@ -78,27 +57,11 @@ export default function SettingsPage() { const profileEvents = useProfileEvents({ pageType: 'settings' }); // 模态框状态 - const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure(); const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure(); const { isOpen: isEmailOpen, onOpen: onEmailOpen, onClose: onEmailClose } = useDisclosure(); - const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); // 表单状态 const [isLoading, setIsLoading] = useState(false); - const [showPassword, setShowPassword] = useState(false); - const [passwordForm, setPasswordForm] = useState({ - currentPassword: '', - newPassword: '', - confirmPassword: '' - }); - - // 密码状态 - 默认假设是普通用户,获取到数据后再更新 - const [passwordStatus, setPasswordStatus] = useState({ - isWechatUser: false, - hasPassword: true, - needsFirstTimeSetup: false - }); - const [passwordStatusLoading, setPasswordStatusLoading] = useState(true); const [phoneForm, setPhoneForm] = useState({ phone: '', verificationCode: '' @@ -107,162 +70,6 @@ export default function SettingsPage() { email: '', verificationCode: '' }); - const [blockedKeywords, setBlockedKeywords] = useState(user?.blocked_keywords || ''); - - // 通知设置状态 - const [notifications, setNotifications] = useState({ - email_notifications: user?.email_notifications ?? true, - sms_notifications: user?.sms_notifications ?? false, - wechat_notifications: user?.wechat_notifications ?? false, - system_updates: true, - investment_alerts: true, - community_activities: true, - marketing_emails: false - }); - - // 隐私设置状态 - const [privacy, setPrivacy] = useState({ - privacy_level: user?.privacy_level || 'public', - show_investment_data: true, - show_trading_history: false, - allow_friend_requests: true - }); - - // 获取密码状态 - const fetchPasswordStatus = async () => { - try { - const API_BASE_URL = getApiBase(); - - logger.api.request('GET', '/api/account/password-status', null); - - const response = await fetch(`${API_BASE_URL}/api/account/password-status`, { - method: 'GET', - credentials: 'include' - }); - - if (response.ok) { - const data = await response.json(); - logger.api.response('GET', '/api/account/password-status', response.status, data); - - if (data.success) { - logger.debug('SettingsPage', '密码状态获取成功', data.data); - setPasswordStatus(data.data); - } - } - } catch (error) { - logger.error('SettingsPage', 'fetchPasswordStatus', error); - } finally { - setPasswordStatusLoading(false); - } - }; - - // 组件加载时获取密码状态 - React.useEffect(() => { - fetchPasswordStatus(); - }, []); - - // 修改密码 - const handlePasswordChange = async () => { - const isFirstTimeSetup = passwordStatus.needsFirstTimeSetup; - - // 如果不是首次设置且未提供当前密码 - if (!isFirstTimeSetup && !passwordForm.currentPassword) { - toast({ - title: "请输入当前密码", - description: "修改密码需要验证当前密码", - status: "error", - duration: 3000, - isClosable: true, - }); - return; - } - - if (passwordForm.newPassword !== passwordForm.confirmPassword) { - toast({ - title: "密码不匹配", - description: "新密码与确认密码不一致", - status: "error", - duration: 3000, - isClosable: true, - }); - return; - } - - if (passwordForm.newPassword.length < 6) { - toast({ - title: "密码太短", - description: "密码至少需要6个字符", - status: "error", - duration: 3000, - isClosable: true, - }); - return; - } - - setIsLoading(true); - try { - // 调用后端API修改密码 - const API_BASE_URL = getApiBase(); - - const response = await fetch(`${API_BASE_URL}/api/account/change-password`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', // 包含认证信息 - body: JSON.stringify({ - currentPassword: passwordForm.currentPassword, - newPassword: passwordForm.newPassword, - isFirstSet: passwordStatus.needsFirstTimeSetup - }) - }); - - const data = await response.json(); - - if (response.ok && data.success) { - const isFirstSet = passwordStatus.needsFirstTimeSetup; - - // 🎯 追踪密码修改成功 - profileEvents.trackPasswordChanged(true); - - toast({ - title: isFirstSet ? "密码设置成功" : "密码修改成功", - description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录", - status: "success", - duration: 3000, - isClosable: true, - }); - - setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); - onPasswordClose(); - - // 刷新密码状态 - fetchPasswordStatus(); - - // 如果是修改密码(非首次设置),需要重新登录 - if (!isFirstSet) { - setTimeout(() => { - logout(); - }, 1000); - } - } else { - throw new Error(data.error || '密码修改失败'); - } - } catch (error) { - // 🎯 追踪密码修改失败 - profileEvents.trackPasswordChanged(false, error.message); - - toast({ - title: "修改失败", - description: error.message, - status: "error", - duration: 3000, - isClosable: true, - }); - } finally { - setIsLoading(false); - } - }; // 发送验证码 const sendVerificationCode = async (type) => { @@ -410,101 +217,6 @@ export default function SettingsPage() { } }; - // 保存通知设置 - const saveNotificationSettings = async () => { - setIsLoading(true); - try { - logger.debug('SettingsPage', '保存通知设置', notifications); - - // 这里应该调用后端API保存设置 - await new Promise(resolve => setTimeout(resolve, 1000)); - - updateUser(notifications); - - // 🎯 追踪通知偏好更改 - profileEvents.trackNotificationPreferencesChanged({ - email: notifications.email_notifications, - push: notifications.system_updates, - sms: notifications.sms_notifications - }); - - // ❌ 移除设置保存成功toast - logger.info('SettingsPage', '通知设置已保存'); - } catch (error) { - logger.error('SettingsPage', 'saveNotificationSettings', error); - - toast({ - title: "保存失败", - description: error.message, - status: "error", - duration: 3000, - isClosable: true, - }); - } finally { - setIsLoading(false); - } - }; - - // 保存隐私设置 - const savePrivacySettings = async () => { - setIsLoading(true); - try { - logger.debug('SettingsPage', '保存隐私设置', { privacy, blockedKeywords }); - - // 这里应该调用后端API保存设置 - await new Promise(resolve => setTimeout(resolve, 1000)); - - updateUser({ - privacy_level: privacy.privacy_level, - blocked_keywords: blockedKeywords - }); - - // ❌ 移除设置保存成功toast - logger.info('SettingsPage', '隐私设置已保存'); - } catch (error) { - logger.error('SettingsPage', 'savePrivacySettings', error); - - toast({ - title: "保存失败", - description: error.message, - status: "error", - duration: 3000, - isClosable: true, - }); - } finally { - setIsLoading(false); - } - }; - - // 注销账户 - const handleDeleteAccount = async () => { - setIsLoading(true); - try { - // 这里应该调用后端API注销账户 - await new Promise(resolve => setTimeout(resolve, 2000)); - - toast({ - title: "账户已注销", - description: "感谢您的使用", - status: "info", - duration: 3000, - isClosable: true, - }); - - logout(); - } catch (error) { - toast({ - title: "注销失败", - description: error.message, - status: "error", - duration: 3000, - isClosable: true, - }); - } finally { - setIsLoading(false); - } - }; - return ( @@ -513,51 +225,13 @@ export default function SettingsPage() { - 账户安全 - 通知设置 - 隐私设置 - 界面设置 - 危险操作 + 账户绑定 - {/* 账户安全 */} + {/* 账户绑定 */} - {/* 密码设置 */} - - - 密码设置 - - - - - - {passwordStatus.needsFirstTimeSetup ? '设置登录密码' : '登录密码'} - - - {passwordStatus.needsFirstTimeSetup - ? '您通过微信登录,建议设置密码以便其他方式登录' - : '定期更换密码,保护账户安全' - } - - {passwordStatus.isWechatUser && ( - - 微信用户 - - )} - - - - - - {/* 手机号绑定 */} @@ -731,458 +405,12 @@ export default function SettingsPage() { - {/* 两步验证 */} - - - 两步验证 - - - - - 安全验证 - - 开启两步验证,提高账户安全性 - - - - - - - - - - {/* 通知设置 */} - - - - - 通知方式 - - - - - - 邮件通知 - - 接收邮件通知 - - - setNotifications(prev => ({ - ...prev, - email_notifications: e.target.checked - }))} - /> - - - - - 短信通知 - - 接收短信通知(需绑定手机号) - - - setNotifications(prev => ({ - ...prev, - sms_notifications: e.target.checked - }))} - /> - - - - - 微信通知 - - 接收微信通知(需绑定微信) - - - setNotifications(prev => ({ - ...prev, - wechat_notifications: e.target.checked - }))} - /> - - - - - - - - 通知类型 - - - - - 系统更新通知 - setNotifications(prev => ({ - ...prev, - system_updates: e.target.checked - }))} - /> - - - - 投资提醒 - setNotifications(prev => ({ - ...prev, - investment_alerts: e.target.checked - }))} - /> - - - - 社区动态 - setNotifications(prev => ({ - ...prev, - community_activities: e.target.checked - }))} - /> - - - - 营销邮件 - setNotifications(prev => ({ - ...prev, - marketing_emails: e.target.checked - }))} - /> - - - - - - - - - - - - {/* 隐私设置 */} - - - - - 隐私级别 - - - - - 个人资料可见性 - - - - - - - - 显示投资数据 - setPrivacy(prev => ({ - ...prev, - show_investment_data: e.target.checked - }))} - /> - - - - 显示交易历史 - setPrivacy(prev => ({ - ...prev, - show_trading_history: e.target.checked - }))} - /> - - - - 允许好友请求 - setPrivacy(prev => ({ - ...prev, - allow_friend_requests: e.target.checked - }))} - /> - - - - - - - - - 屏蔽设置 - - - - 屏蔽关键词 -