fix: 修复加密失效、权限失效、盘点页面崩溃等问题
- secureStorage.js: 移除硬编码密钥,改为运行时动态生成密钥 - AuthContext.jsx: 修复 hasPermission() 始终返回 true 的权限失效问题 - auth.js: 登录接口返回用户角色信息,支持前端权限判断 - InventoryManagement.jsx: 修复 res.data 多取一层导致 TypeError 崩溃
This commit is contained in:
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
@@ -16,7 +56,7 @@ 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++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user