89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
// Define Electron SafeStorage interface
|
|
interface ElectronSafeStorage {
|
|
isEncryptionAvailable(): boolean;
|
|
encryptString(plainText: string): Buffer;
|
|
decryptString(encrypted: Buffer): string;
|
|
}
|
|
|
|
// Safely import safeStorage - may not be available in all environments
|
|
let safeStorage: ElectronSafeStorage | null = null;
|
|
try {
|
|
// Access electron through the global window object in Obsidian's Electron environment
|
|
// This avoids require() while still getting synchronous access
|
|
const electronRemote = (window as Window & { require?: (module: string) => typeof import('electron') }).require;
|
|
if (electronRemote) {
|
|
const electron = electronRemote('electron');
|
|
safeStorage = electron.safeStorage || null;
|
|
}
|
|
} catch {
|
|
console.warn('Electron safeStorage not available, API keys will be stored in plaintext');
|
|
}
|
|
|
|
/**
|
|
* Checks if encryption is available on the current platform
|
|
* @returns true if safeStorage encryption is available
|
|
*/
|
|
export function isEncryptionAvailable(): boolean {
|
|
return safeStorage !== null &&
|
|
typeof safeStorage.isEncryptionAvailable === 'function' &&
|
|
safeStorage.isEncryptionAvailable();
|
|
}
|
|
|
|
/**
|
|
* Encrypts an API key using Electron's safeStorage API
|
|
* Falls back to plaintext if encryption is not available (e.g., Linux without keyring)
|
|
* @param apiKey The plaintext API key to encrypt
|
|
* @returns Encrypted API key with "encrypted:" prefix, or plaintext if encryption unavailable
|
|
*/
|
|
export function encryptApiKey(apiKey: string): string {
|
|
if (!apiKey) {
|
|
return '';
|
|
}
|
|
|
|
// Check if safeStorage is available and encryption is enabled
|
|
if (!isEncryptionAvailable()) {
|
|
console.warn('Encryption not available, storing API key in plaintext');
|
|
return apiKey;
|
|
}
|
|
|
|
try {
|
|
const encrypted = safeStorage!.encryptString(apiKey);
|
|
return `encrypted:${encrypted.toString('base64')}`;
|
|
} catch (error) {
|
|
console.error('Failed to encrypt API key, falling back to plaintext:', error);
|
|
return apiKey;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypts an API key encrypted with encryptApiKey
|
|
* @param stored The stored API key (encrypted or plaintext)
|
|
* @returns Decrypted API key
|
|
*/
|
|
export function decryptApiKey(stored: string): string {
|
|
if (!stored) {
|
|
return '';
|
|
}
|
|
|
|
// Check if this is an encrypted key
|
|
if (!stored.startsWith('encrypted:')) {
|
|
// Legacy plaintext key or fallback
|
|
return stored;
|
|
}
|
|
|
|
// If safeStorage is not available, we can't decrypt
|
|
if (!safeStorage) {
|
|
console.error('Cannot decrypt API key: safeStorage not available');
|
|
throw new Error('Failed to decrypt API key. You may need to regenerate it.');
|
|
}
|
|
|
|
try {
|
|
const encryptedData = stored.substring(10); // Remove "encrypted:" prefix
|
|
const buffer = Buffer.from(encryptedData, 'base64');
|
|
return safeStorage.decryptString(buffer);
|
|
} catch (error) {
|
|
console.error('Failed to decrypt API key:', error);
|
|
throw new Error('Failed to decrypt API key. You may need to regenerate it.');
|
|
}
|
|
}
|