diff --git a/src/main.ts b/src/main.ts index 7451535..8d9bc10 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,8 @@ import { MCPPluginSettings, DEFAULT_SETTINGS } from './types/settings-types'; import { MCPServerSettingTab } from './settings'; import { NotificationManager } from './ui/notifications'; import { NotificationHistoryModal } from './ui/notification-history'; +import { generateApiKey } from './utils/auth-utils'; +import { encryptApiKey, decryptApiKey } from './utils/encryption-utils'; export default class MCPServerPlugin extends Plugin { settings!: MCPPluginSettings; @@ -14,6 +16,22 @@ export default class MCPServerPlugin extends Plugin { async onload() { await this.loadSettings(); + // Auto-generate API key if not set + if (!this.settings.apiKey || this.settings.apiKey.trim() === '') { + console.log('Generating new API key...'); + this.settings.apiKey = generateApiKey(); + await this.saveSettings(); + } + + // Migrate legacy settings (remove enableCORS and allowedOrigins) + const legacySettings = this.settings as any; + if ('enableCORS' in legacySettings || 'allowedOrigins' in legacySettings) { + console.log('Migrating legacy CORS settings...'); + delete legacySettings.enableCORS; + delete legacySettings.allowedOrigins; + await this.saveSettings(); + } + // Initialize notification manager this.updateNotificationManager(); @@ -135,11 +153,33 @@ export default class MCPServerPlugin extends Plugin { } async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + const data = await this.loadData(); + this.settings = Object.assign({}, DEFAULT_SETTINGS, data); + + // Decrypt API key if encrypted + if (this.settings.apiKey) { + try { + this.settings.apiKey = decryptApiKey(this.settings.apiKey); + } catch (error) { + console.error('Failed to decrypt API key:', error); + new Notice('⚠️ Failed to decrypt API key. Please regenerate in settings.'); + this.settings.apiKey = ''; + } + } } async saveSettings() { - await this.saveData(this.settings); + // Create a copy of settings for saving + const settingsToSave = { ...this.settings }; + + // Encrypt API key before saving + if (settingsToSave.apiKey) { + settingsToSave.apiKey = encryptApiKey(settingsToSave.apiKey); + } + + await this.saveData(settingsToSave); + + // Update server settings if running if (this.mcpServer) { this.mcpServer.updateSettings(this.settings); } diff --git a/tests/main-migration.test.ts b/tests/main-migration.test.ts new file mode 100644 index 0000000..4f1ca32 --- /dev/null +++ b/tests/main-migration.test.ts @@ -0,0 +1,80 @@ +import { generateApiKey } from '../src/utils/auth-utils'; +import { encryptApiKey, decryptApiKey } from '../src/utils/encryption-utils'; +import { DEFAULT_SETTINGS } from '../src/types/settings-types'; + +// Mock electron +jest.mock('electron', () => ({ + safeStorage: { + isEncryptionAvailable: jest.fn(() => true), + encryptString: jest.fn((data: string) => Buffer.from(`encrypted:${data}`)), + decryptString: jest.fn((buffer: Buffer) => { + const str = buffer.toString(); + return str.replace('encrypted:', ''); + }) + } +})); + +describe('Settings Migration', () => { + describe('API key initialization', () => { + it('should generate API key if empty', () => { + const settings = { ...DEFAULT_SETTINGS, apiKey: '' }; + + // Simulate what plugin should do + if (!settings.apiKey) { + settings.apiKey = generateApiKey(); + } + + expect(settings.apiKey).toBeTruthy(); + expect(settings.apiKey.length).toBeGreaterThanOrEqual(32); + }); + + it('should encrypt API key on save', () => { + const plainKey = generateApiKey(); + const encrypted = encryptApiKey(plainKey); + + expect(encrypted).toMatch(/^encrypted:/); + expect(encrypted).not.toBe(plainKey); + }); + + it('should decrypt API key on load', () => { + const plainKey = generateApiKey(); + const encrypted = encryptApiKey(plainKey); + const decrypted = decryptApiKey(encrypted); + + expect(decrypted).toBe(plainKey); + }); + }); + + describe('Legacy settings migration', () => { + it('should remove enableCORS from legacy settings', () => { + const legacySettings: any = { + ...DEFAULT_SETTINGS, + enableCORS: true, + allowedOrigins: ['*'] + }; + + // Simulate migration + delete legacySettings.enableCORS; + delete legacySettings.allowedOrigins; + + expect(legacySettings.enableCORS).toBeUndefined(); + expect(legacySettings.allowedOrigins).toBeUndefined(); + }); + + it('should preserve other settings during migration', () => { + const legacySettings: any = { + ...DEFAULT_SETTINGS, + port: 4000, + enableCORS: false, + allowedOrigins: ['http://localhost:8080'], + notificationsEnabled: true + }; + + // Simulate migration + const { enableCORS, allowedOrigins, ...migrated } = legacySettings; + + expect(migrated.port).toBe(4000); + expect(migrated.notificationsEnabled).toBe(true); + }); + }); +});