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