From 3ce7ea40de149bba1fb503aebadfc72aad1b464d Mon Sep 17 00:00:00 2001 From: zhang96110 Date: Wed, 1 Apr 2026 06:26:03 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E5=A4=B1=E6=95=88=E3=80=81=E6=9D=83=E9=99=90=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E3=80=81=E7=9B=98=E7=82=B9=E9=A1=B5=E9=9D=A2=E5=B4=A9=E6=BA=83?= =?UTF-8?q?=E7=AD=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - secureStorage.js: 移除硬编码密钥,改为运行时动态生成密钥 - AuthContext.jsx: 修复 hasPermission() 始终返回 true 的权限失效问题 - auth.js: 登录接口返回用户角色信息,支持前端权限判断 - InventoryManagement.jsx: 修复 res.data 多取一层导致 TypeError 崩溃 --- backend/routes/auth.js | 12 +++++ frontend/src/context/AuthContext.jsx | 8 ++- frontend/src/pages/InventoryManagement.jsx | 14 +++--- frontend/src/utils/secureStorage.js | 58 ++++++++++++++++++---- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 2c314ce..d0e83d6 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -193,6 +193,13 @@ router.post('/login', async (req, res) => { const token = generateToken(user); + // 查询用户角色(通过 UserRole 中间表) + const userRoles = await UserRole.findAll({ + where: { UserId: user.userId }, + include: [{ model: Role }], + }); + const roles = userRoles.map(ur => ur.Role || ur); + user.lastLoginTime = new Date(); user.lastLoginIp = req.ip || req.connection.remoteAddress; user.loginCount = 0; @@ -208,6 +215,11 @@ router.post('/login', async (req, res) => { email: user.email, realName: user.realName, avatar: user.avatar, + roles: roles.map(r => ({ + roleId: r.roleId, + roleName: r.roleName, + roleCode: r.roleCode, + })), }, token, }, diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx index fa6de8b..b109c13 100644 --- a/frontend/src/context/AuthContext.jsx +++ b/frontend/src/context/AuthContext.jsx @@ -116,7 +116,13 @@ export const AuthProvider = ({ children }) => { const hasPermission = permission => { if (!user) return false; - return true; + const roles = user.roles || []; + // 管理员拥有所有权限 + if (roles.some(r => r.roleCode === 'admin')) return true; + // 检查具体权限 + if (permission === 'admin') return roles.some(r => r.roleCode === 'admin'); + // roleCode 格式的权限直接匹配 + return roles.some(r => r.roleCode === permission); }; const value = { diff --git a/frontend/src/pages/InventoryManagement.jsx b/frontend/src/pages/InventoryManagement.jsx index af6edf9..0cee316 100644 --- a/frontend/src/pages/InventoryManagement.jsx +++ b/frontend/src/pages/InventoryManagement.jsx @@ -79,10 +79,10 @@ const InventoryManagement = () => { ...searchParamsObj, }; const res = await api.get('/inventory/plans', { params }); - setPlans(res.data.plans || []); + setPlans(res.plans || []); setPagination(prev => ({ ...prev, - total: res.data.total || 0, + total: res.total || 0, })); } catch (error) { message.error('获取盘点计划失败'); @@ -95,9 +95,9 @@ const InventoryManagement = () => { try { const res = await api.get('/inventory/stats/dashboard'); setStats({ - totalPlans: res.data.totalPlans || 0, - completedPlans: res.data.completedPlans || 0, - inProgressPlans: res.data.inProgressPlans || 0, + totalPlans: res.totalPlans || 0, + completedPlans: res.completedPlans || 0, + inProgressPlans: res.inProgressPlans || 0, }); } catch (error) { console.error('获取统计失败', error); @@ -107,7 +107,7 @@ const InventoryManagement = () => { const fetchRooms = async () => { try { const res = await api.get('/rooms', { params: { pageSize: 1000 } }); - setRooms(Array.isArray(res.data) ? res.data : res.data.rooms || []); + setRooms(Array.isArray(res) ? res : res.rooms || []); } catch (error) { console.error('获取机房失败', error); } @@ -116,7 +116,7 @@ const InventoryManagement = () => { const fetchRacks = async () => { try { const res = await api.get('/racks', { params: { pageSize: 1000 } }); - const allRacks = Array.isArray(res.data) ? res.data : res.data.racks || []; + const allRacks = Array.isArray(res) ? res : res.racks || []; setRacks(allRacks); setFilteredRacks(allRacks); } catch (error) { diff --git a/frontend/src/utils/secureStorage.js b/frontend/src/utils/secureStorage.js index 9f2e0cc..6584ecb 100644 --- a/frontend/src/utils/secureStorage.js +++ b/frontend/src/utils/secureStorage.js @@ -1,10 +1,50 @@ const STORAGE_PREFIX = 'idc_'; const ENCRYPTION_ENABLED = import.meta.env.PROD; +/** + * 生成运行时密钥(基于浏览器指纹) + * 密钥在每次会话中动态生成,不会出现在源码或构建产物中 + * 同一浏览器同一域名下密钥保持一致,确保刷新页面后仍可解密 + */ +const getOrCreateRuntimeKey = () => { + const KEY_STORAGE_ID = '__idc_sk'; + let key = sessionStorage.getItem(KEY_STORAGE_ID); + if (key) return key; + + // 基于浏览器特征生成指纹作为密钥种子 + const fingerprint = [ + navigator.userAgent, + screen.width, + screen.height, + screen.colorDepth, + new Date().getTimezoneOffset(), + navigator.language, + ].join('|'); + + // 使用简单的哈希混合生成 32 字符密钥 + let hash = 0; + for (let i = 0; i < fingerprint.length; i++) { + const char = fingerprint.charCodeAt(i); + hash = ((hash << 5) - hash + char) | 0; + } + + // 生成 32 字符的随机密钥 + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + key = ''; + let seed = Math.abs(hash) + Date.now(); + for (let i = 0; i < 32; i++) { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + key += chars[seed % chars.length]; + } + + sessionStorage.setItem(KEY_STORAGE_ID, key); + return key; +}; + const simpleEncrypt = (text) => { if (!ENCRYPTION_ENABLED) return text; - - const key = import.meta.env.VITE_STORAGE_KEY || 'idc_default_secret_key_2024'; + + const key = getOrCreateRuntimeKey(); let result = ''; for (let i = 0; i < text.length; i++) { result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); @@ -14,9 +54,9 @@ const simpleEncrypt = (text) => { const simpleDecrypt = (encrypted) => { if (!ENCRYPTION_ENABLED) return encrypted; - + try { - const key = import.meta.env.VITE_STORAGE_KEY || 'idc_default_secret_key_2024'; + const key = getOrCreateRuntimeKey(); const decoded = decodeURIComponent(atob(encrypted)); let result = ''; for (let i = 0; i < decoded.length; i++) { @@ -51,22 +91,22 @@ export const secureStorage = { try { const storageKey = `${STORAGE_PREFIX}${key}`; const encrypted = localStorage.getItem(storageKey); - + if (!encrypted) return null; - + const decrypted = simpleDecrypt(encrypted); if (!decrypted) { localStorage.removeItem(storageKey); return null; } - + const data = JSON.parse(decrypted); - + if (data.expiry && Date.now() > data.expiry) { localStorage.removeItem(storageKey); return null; } - + return data.value; } catch (error) { console.error('[SecureStorage] Get error:', error);