Spaces:
Running
Running
/** | |
* Secure storage utilities for sensitive data like OAuth tokens | |
*/ | |
const ENCRYPTION_KEY_NAME = 'mcp-encryption-key'; | |
// Generate or retrieve encryption key | |
async function getEncryptionKey(): Promise<CryptoKey> { | |
const keyData = localStorage.getItem(ENCRYPTION_KEY_NAME); | |
if (keyData) { | |
try { | |
const keyBuffer = new Uint8Array(JSON.parse(keyData)); | |
return await crypto.subtle.importKey( | |
'raw', | |
keyBuffer, | |
{ name: 'AES-GCM' }, | |
false, | |
['encrypt', 'decrypt'] | |
); | |
} catch { | |
// Key corrupted, generate new one | |
} | |
} | |
// Generate new key | |
const key = await crypto.subtle.generateKey( | |
{ name: 'AES-GCM', length: 256 }, | |
true, | |
['encrypt', 'decrypt'] | |
); | |
// Store key for future use | |
const keyBuffer = await crypto.subtle.exportKey('raw', key); | |
localStorage.setItem(ENCRYPTION_KEY_NAME, JSON.stringify(Array.from(new Uint8Array(keyBuffer)))); | |
return key; | |
} | |
// Encrypt sensitive data | |
export async function encryptData(data: string): Promise<string> { | |
try { | |
const key = await getEncryptionKey(); | |
const encoder = new TextEncoder(); | |
const dataBuffer = encoder.encode(data); | |
const iv = crypto.getRandomValues(new Uint8Array(12)); | |
const encryptedBuffer = await crypto.subtle.encrypt( | |
{ name: 'AES-GCM', iv }, | |
key, | |
dataBuffer | |
); | |
// Combine IV and encrypted data | |
const result = new Uint8Array(iv.length + encryptedBuffer.byteLength); | |
result.set(iv); | |
result.set(new Uint8Array(encryptedBuffer), iv.length); | |
return btoa(String.fromCharCode(...result)); | |
} catch (error) { | |
console.warn('Encryption failed, storing data unencrypted:', error); | |
return data; | |
} | |
} | |
// Decrypt sensitive data | |
export async function decryptData(encryptedData: string): Promise<string> { | |
try { | |
const key = await getEncryptionKey(); | |
const dataBuffer = new Uint8Array( | |
atob(encryptedData).split('').map(char => char.charCodeAt(0)) | |
); | |
const iv = dataBuffer.slice(0, 12); | |
const encrypted = dataBuffer.slice(12); | |
const decryptedBuffer = await crypto.subtle.decrypt( | |
{ name: 'AES-GCM', iv }, | |
key, | |
encrypted | |
); | |
const decoder = new TextDecoder(); | |
return decoder.decode(decryptedBuffer); | |
} catch (error) { | |
console.warn('Decryption failed, returning data as-is:', error); | |
return encryptedData; | |
} | |
} | |
// Secure storage wrapper for sensitive data | |
export const secureStorage = { | |
async setItem(key: string, value: string): Promise<void> { | |
const encrypted = await encryptData(value); | |
localStorage.setItem(`secure_${key}`, encrypted); | |
}, | |
async getItem(key: string): Promise<string | null> { | |
const encrypted = localStorage.getItem(`secure_${key}`); | |
if (!encrypted) return null; | |
return await decryptData(encrypted); | |
}, | |
removeItem(key: string): void { | |
localStorage.removeItem(`secure_${key}`); | |
} | |
}; |