fix: 修复加密失效、权限失效、盘点页面崩溃等问题

- secureStorage.js: 移除硬编码密钥,改为运行时动态生成密钥
- AuthContext.jsx: 修复 hasPermission() 始终返回 true 的权限失效问题
- auth.js: 登录接口返回用户角色信息,支持前端权限判断
- InventoryManagement.jsx: 修复 res.data 多取一层导致 TypeError 崩溃
This commit is contained in:
zhang96110
2026-04-01 06:26:03 +00:00
parent 6eda3ae1f2
commit 3ce7ea40de
4 changed files with 75 additions and 17 deletions
+12
View File
@@ -193,6 +193,13 @@ router.post('/login', async (req, res) => {
const token = generateToken(user); 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.lastLoginTime = new Date();
user.lastLoginIp = req.ip || req.connection.remoteAddress; user.lastLoginIp = req.ip || req.connection.remoteAddress;
user.loginCount = 0; user.loginCount = 0;
@@ -208,6 +215,11 @@ router.post('/login', async (req, res) => {
email: user.email, email: user.email,
realName: user.realName, realName: user.realName,
avatar: user.avatar, avatar: user.avatar,
roles: roles.map(r => ({
roleId: r.roleId,
roleName: r.roleName,
roleCode: r.roleCode,
})),
}, },
token, token,
}, },
+7 -1
View File
@@ -116,7 +116,13 @@ export const AuthProvider = ({ children }) => {
const hasPermission = permission => { const hasPermission = permission => {
if (!user) return false; 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 = { const value = {
+7 -7
View File
@@ -79,10 +79,10 @@ const InventoryManagement = () => {
...searchParamsObj, ...searchParamsObj,
}; };
const res = await api.get('/inventory/plans', { params }); const res = await api.get('/inventory/plans', { params });
setPlans(res.data.plans || []); setPlans(res.plans || []);
setPagination(prev => ({ setPagination(prev => ({
...prev, ...prev,
total: res.data.total || 0, total: res.total || 0,
})); }));
} catch (error) { } catch (error) {
message.error('获取盘点计划失败'); message.error('获取盘点计划失败');
@@ -95,9 +95,9 @@ const InventoryManagement = () => {
try { try {
const res = await api.get('/inventory/stats/dashboard'); const res = await api.get('/inventory/stats/dashboard');
setStats({ setStats({
totalPlans: res.data.totalPlans || 0, totalPlans: res.totalPlans || 0,
completedPlans: res.data.completedPlans || 0, completedPlans: res.completedPlans || 0,
inProgressPlans: res.data.inProgressPlans || 0, inProgressPlans: res.inProgressPlans || 0,
}); });
} catch (error) { } catch (error) {
console.error('获取统计失败', error); console.error('获取统计失败', error);
@@ -107,7 +107,7 @@ const InventoryManagement = () => {
const fetchRooms = async () => { const fetchRooms = async () => {
try { try {
const res = await api.get('/rooms', { params: { pageSize: 1000 } }); 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) { } catch (error) {
console.error('获取机房失败', error); console.error('获取机房失败', error);
} }
@@ -116,7 +116,7 @@ const InventoryManagement = () => {
const fetchRacks = async () => { const fetchRacks = async () => {
try { try {
const res = await api.get('/racks', { params: { pageSize: 1000 } }); 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); setRacks(allRacks);
setFilteredRacks(allRacks); setFilteredRacks(allRacks);
} catch (error) { } catch (error) {
+49 -9
View File
@@ -1,10 +1,50 @@
const STORAGE_PREFIX = 'idc_'; const STORAGE_PREFIX = 'idc_';
const ENCRYPTION_ENABLED = import.meta.env.PROD; 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) => { const simpleEncrypt = (text) => {
if (!ENCRYPTION_ENABLED) return text; if (!ENCRYPTION_ENABLED) return text;
const key = import.meta.env.VITE_STORAGE_KEY || 'idc_default_secret_key_2024'; const key = getOrCreateRuntimeKey();
let result = ''; let result = '';
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
@@ -14,9 +54,9 @@ const simpleEncrypt = (text) => {
const simpleDecrypt = (encrypted) => { const simpleDecrypt = (encrypted) => {
if (!ENCRYPTION_ENABLED) return encrypted; if (!ENCRYPTION_ENABLED) return encrypted;
try { try {
const key = import.meta.env.VITE_STORAGE_KEY || 'idc_default_secret_key_2024'; const key = getOrCreateRuntimeKey();
const decoded = decodeURIComponent(atob(encrypted)); const decoded = decodeURIComponent(atob(encrypted));
let result = ''; let result = '';
for (let i = 0; i < decoded.length; i++) { for (let i = 0; i < decoded.length; i++) {
@@ -51,22 +91,22 @@ export const secureStorage = {
try { try {
const storageKey = `${STORAGE_PREFIX}${key}`; const storageKey = `${STORAGE_PREFIX}${key}`;
const encrypted = localStorage.getItem(storageKey); const encrypted = localStorage.getItem(storageKey);
if (!encrypted) return null; if (!encrypted) return null;
const decrypted = simpleDecrypt(encrypted); const decrypted = simpleDecrypt(encrypted);
if (!decrypted) { if (!decrypted) {
localStorage.removeItem(storageKey); localStorage.removeItem(storageKey);
return null; return null;
} }
const data = JSON.parse(decrypted); const data = JSON.parse(decrypted);
if (data.expiry && Date.now() > data.expiry) { if (data.expiry && Date.now() > data.expiry) {
localStorage.removeItem(storageKey); localStorage.removeItem(storageKey);
return null; return null;
} }
return data.value; return data.value;
} catch (error) { } catch (error) {
console.error('[SecureStorage] Get error:', error); console.error('[SecureStorage] Get error:', error);