feat: 拉取最新仓库代码

Co-authored-by: traeagent <traeagent@users.noreply.github.com>
This commit is contained in:
gituib
2026-05-06 02:58:20 +00:00
co-authored by traeagent
parent d107c4c092
commit a6af71ebe7
2 changed files with 146 additions and 130 deletions
+27 -21
View File
@@ -20,35 +20,41 @@ export const useAuth = () => {
}; };
export const AuthProvider = ({ children }) => { export const AuthProvider = ({ children }) => {
const savedToken = secureStorage.get(TOKEN_KEY); const [user, setUser] = useState(null);
const savedUser = secureStorage.get(USER_KEY); const [token, setToken] = useState(null);
const [user, setUser] = useState(savedUser);
const [token, setToken] = useState(savedToken);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [initialized, setInitialized] = useState(false); const [initialized, setInitialized] = useState(false);
useEffect(() => { useEffect(() => {
const currentToken = secureStorage.get(TOKEN_KEY);
if (!currentToken) {
setToken(null);
setUser(null);
setLoading(false);
setInitialized(true);
return;
}
const initializeAuth = async () => { const initializeAuth = async () => {
try { try {
const response = await authAPI.getProfile(); const storedToken = await secureStorage.loadFromStorage(TOKEN_KEY);
if (response.success) { const storedUser = await secureStorage.loadFromStorage(USER_KEY);
setUser(response.data.user);
secureStorage.set(USER_KEY, response.data.user); if (!storedToken) {
setToken(null);
setUser(null);
setLoading(false);
setInitialized(true);
return;
}
setToken(storedToken);
setUser(storedUser);
try {
const response = await authAPI.getProfile();
if (response.success) {
setUser(response.data.user);
secureStorage.set(USER_KEY, response.data.user);
}
} catch {
secureStorage.remove(TOKEN_KEY);
secureStorage.remove(USER_KEY);
setToken(null);
setUser(null);
} }
} catch { } catch {
secureStorage.remove(TOKEN_KEY);
secureStorage.remove(USER_KEY);
setToken(null); setToken(null);
setUser(null); setUser(null);
} finally { } finally {
+118 -108
View File
@@ -1,14 +1,41 @@
const STORAGE_PREFIX = 'idc_'; const STORAGE_PREFIX = 'idc_';
const KEY_STORAGE_ID = '__idc_sk';
const SALT = new TextEncoder().encode('idc-secure-storage-salt-v1');
const PBKDF2_ITERATIONS = 100000;
const AES_KEY_LENGTH = 256;
/** const encoder = new TextEncoder();
* 生成运行时密钥(基于浏览器指纹) const decoder = new TextDecoder();
* 密钥在每次会话中动态生成,不会出现在源码或构建产物中
* 同一浏览器同一域名下密钥保持一致,确保刷新页面后仍可解密 const memoryCache = new Map();
*/
const getOrCreateRuntimeKey = () => { let _cachedCryptoKey = null;
const KEY_STORAGE_ID = '__idc_sk';
let key = sessionStorage.getItem(KEY_STORAGE_ID); async function deriveKey(material) {
if (key) return key; if (_cachedCryptoKey) return _cachedCryptoKey;
const keyMaterial = await crypto.subtle.importKey('raw', material, 'PBKDF2', false, [
'deriveKey',
]);
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: SALT, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: AES_KEY_LENGTH },
false,
['encrypt', 'decrypt']
);
_cachedCryptoKey = key;
return key;
}
async function getOrCreateKeyMaterial() {
let stored = sessionStorage.getItem(KEY_STORAGE_ID);
if (stored) {
const raw = Uint8Array.from(atob(stored), c => c.charCodeAt(0));
return raw;
}
const fingerprint = [ const fingerprint = [
navigator.userAgent, navigator.userAgent,
@@ -19,128 +46,115 @@ const getOrCreateRuntimeKey = () => {
navigator.language, navigator.language,
].join('|'); ].join('|');
let hash = 0; const fingerprintBytes = encoder.encode(fingerprint);
for (let i = 0; i < fingerprint.length; i++) { const hashBuffer = await crypto.subtle.digest('SHA-256', fingerprintBytes);
const char = fingerprint.charCodeAt(i); const material = new Uint8Array(hashBuffer);
hash = ((hash << 5) - hash + char) | 0;
}
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; sessionStorage.setItem(KEY_STORAGE_ID, btoa(String.fromCharCode(...material)));
key = ''; return material;
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); async function encrypt(plaintext) {
console.log('[SecureStorage] New runtime key generated:', key.substring(0, 8) + '...'); const material = await getOrCreateKeyMaterial();
return key; const key = await deriveKey(material);
}; const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = encoder.encode(plaintext);
const simpleEncrypt = (text) => { const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);
const key = getOrCreateRuntimeKey();
let result = '';
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return btoa(encodeURIComponent(result));
};
const simpleDecrypt = (encrypted) => { const payload = new Uint8Array(iv.length + ciphertext.byteLength);
payload.set(iv, 0);
payload.set(new Uint8Array(ciphertext), iv.length);
return btoa(String.fromCharCode(...payload));
}
async function decrypt(encrypted) {
try { try {
const key = getOrCreateRuntimeKey(); const material = await getOrCreateKeyMaterial();
const decoded = decodeURIComponent(atob(encrypted)); const key = await deriveKey(material);
let result = '';
for (let i = 0; i < decoded.length; i++) { const raw = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));
result += String.fromCharCode(decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)); const iv = raw.slice(0, 12);
} const ciphertext = raw.slice(12);
return result;
} catch (e) { const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
console.error('[SecureStorage] Decrypt error:', e.message); return decoder.decode(decrypted);
} catch {
return null; return null;
} }
}; }
async function decryptAndParse(encrypted) {
const decrypted = await decrypt(encrypted);
if (!decrypted) return null;
try {
const data = JSON.parse(decrypted);
if (data.expiry && Date.now() > data.expiry) return null;
return data.value;
} catch {
return null;
}
}
export const secureStorage = { export const secureStorage = {
set: (key, value, options = {}) => { set: (key, value, options = {}) => {
try { memoryCache.set(key, value);
const storageKey = `${STORAGE_PREFIX}${key}`;
const data = { const storageKey = `${STORAGE_PREFIX}${key}`;
value, const data = {
timestamp: Date.now(), value,
expiry: options.expiry || null, timestamp: Date.now(),
}; expiry: options.expiry || null,
const serialized = JSON.stringify(data); };
const encrypted = simpleEncrypt(serialized); const serialized = JSON.stringify(data);
localStorage.setItem(storageKey, encrypted);
console.log('[SecureStorage] Set:', key, '=', value ? value.substring(0, 30) + '...' : 'null'); encrypt(serialized)
console.log('[SecureStorage] Stored encrypted value length:', encrypted.length); .then(encrypted => {
return true; localStorage.setItem(storageKey, encrypted);
} catch (error) { })
console.error('[SecureStorage] Set error:', error); .catch(() => {});
return false;
} return true;
}, },
get: (key) => { get: (key) => {
if (memoryCache.has(key)) {
return memoryCache.get(key);
}
return null;
},
loadFromStorage: async (key) => {
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) { const value = await decryptAndParse(encrypted);
console.log('[SecureStorage] Get:', key, '- not found in localStorage'); if (value !== null) {
return null; memoryCache.set(key, value);
} } else {
console.log('[SecureStorage] Get:', key, '- found encrypted data, length:', encrypted.length);
let decrypted = simpleDecrypt(encrypted);
if (!decrypted) {
console.log('[SecureStorage] Get:', key, '- decryption failed, trying raw JSON parse');
try {
const parsed = JSON.parse(encrypted);
if (parsed && parsed.value !== undefined) {
console.log('[SecureStorage] Get:', key, '- recovered from raw JSON');
return parsed.value;
}
} catch {
console.log('[SecureStorage] Get:', key, '- raw JSON parse also failed');
}
localStorage.removeItem(storageKey); localStorage.removeItem(storageKey);
return null;
} }
return value;
const data = JSON.parse(decrypted); } catch {
if (data.expiry && Date.now() > data.expiry) {
console.log('[SecureStorage] Get:', key, '- expired');
localStorage.removeItem(storageKey);
return null;
}
const valuePreview = typeof data.value === 'string' ? data.value.substring(0, 30) + '...' : typeof data.value;
console.log('[SecureStorage] Get:', key, '- success:', valuePreview);
return data.value;
} catch (error) {
console.error('[SecureStorage] Get error:', error);
return null; return null;
} }
}, },
remove: (key) => { remove: (key) => {
memoryCache.delete(key);
try { try {
const storageKey = `${STORAGE_PREFIX}${key}`; const storageKey = `${STORAGE_PREFIX}${key}`;
localStorage.removeItem(storageKey); localStorage.removeItem(storageKey);
console.log('[SecureStorage] Remove:', key); } catch {}
return true; return true;
} catch (error) {
console.error('[SecureStorage] Remove error:', error);
return false;
}
}, },
clear: () => { clear: () => {
memoryCache.clear();
try { try {
const keys = []; const keys = [];
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
@@ -149,13 +163,9 @@ export const secureStorage = {
keys.push(key); keys.push(key);
} }
} }
keys.forEach((key) => localStorage.removeItem(key)); keys.forEach(key => localStorage.removeItem(key));
console.log('[SecureStorage] Clear: removed', keys.length, 'keys'); } catch {}
return true; return true;
} catch (error) {
console.error('[SecureStorage] Clear error:', error);
return false;
}
}, },
}; };