From fa23a7b8e2d6c70c4afe32aca7208b84c5d2a806 Mon Sep 17 00:00:00 2001 From: Kacper Date: Mon, 29 Dec 2025 21:39:59 +0100 Subject: [PATCH] fix: Allow testing API keys without saving first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add optional apiKey parameter to verifyClaudeAuth endpoint - Backend uses provided key when available, falls back to stored key - Frontend passes current input value to test unsaved keys - Add input validation before testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../routes/setup/routes/verify-claude-auth.ts | 28 ++++++++++++------- .../api-keys/hooks/use-api-key-management.ts | 12 +++++++- apps/ui/src/lib/electron.ts | 18 +++++++++--- apps/ui/src/lib/http-api-client.ts | 5 ++-- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/apps/server/src/routes/setup/routes/verify-claude-auth.ts b/apps/server/src/routes/setup/routes/verify-claude-auth.ts index 5debc5c7..c202ff96 100644 --- a/apps/server/src/routes/setup/routes/verify-claude-auth.ts +++ b/apps/server/src/routes/setup/routes/verify-claude-auth.ts @@ -71,10 +71,15 @@ function containsAuthError(text: string): boolean { export function createVerifyClaudeAuthHandler() { return async (req: Request, res: Response): Promise => { try { - // Get the auth method from the request body - const { authMethod } = req.body as { authMethod?: 'cli' | 'api_key' }; + // Get the auth method and optional API key from the request body + const { authMethod, apiKey } = req.body as { + authMethod?: 'cli' | 'api_key'; + apiKey?: string; + }; - logger.info(`[Setup] Verifying Claude authentication using method: ${authMethod || 'auto'}`); + logger.info( + `[Setup] Verifying Claude authentication using method: ${authMethod || 'auto'}${apiKey ? ' (with provided key)' : ''}` + ); // Create an AbortController with a 30-second timeout const abortController = new AbortController(); @@ -94,14 +99,17 @@ export function createVerifyClaudeAuthHandler() { delete process.env.ANTHROPIC_API_KEY; logger.info('[Setup] Cleared API key environment for CLI verification'); } else if (authMethod === 'api_key') { - // For API key verification, ensure we're using the stored API key - const storedApiKey = getApiKey('anthropic'); - if (storedApiKey) { - process.env.ANTHROPIC_API_KEY = storedApiKey; - logger.info('[Setup] Using stored API key for verification'); + // For API key verification, use provided key, stored key, or env var (in order of priority) + if (apiKey) { + // Use the provided API key (allows testing unsaved keys) + process.env.ANTHROPIC_API_KEY = apiKey; + logger.info('[Setup] Using provided API key for verification'); } else { - // Check env var - if (!process.env.ANTHROPIC_API_KEY) { + const storedApiKey = getApiKey('anthropic'); + if (storedApiKey) { + process.env.ANTHROPIC_API_KEY = storedApiKey; + logger.info('[Setup] Using stored API key for verification'); + } else if (!process.env.ANTHROPIC_API_KEY) { res.json({ success: true, authenticated: false, diff --git a/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts b/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts index cd0a4c9c..97eb157a 100644 --- a/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts +++ b/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts @@ -69,12 +69,22 @@ export function useApiKeyManagement() { // Test Anthropic/Claude connection const handleTestAnthropicConnection = async () => { + // Validate input first + if (!anthropicKey || anthropicKey.trim().length === 0) { + setTestResult({ + success: false, + message: 'Please enter an API key to test.', + }); + return; + } + setTestingConnection(true); setTestResult(null); try { const api = getElectronAPI(); - const data = await api.setup.verifyClaudeAuth('api_key'); + // Pass the current input value to test unsaved keys + const data = await api.setup.verifyClaudeAuth('api_key', anthropicKey); if (data.success && data.authenticated) { setTestResult({ diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index be3cd7bf..65d50024 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -537,7 +537,10 @@ export interface ElectronAPI { isMac: boolean; isLinux: boolean; }>; - verifyClaudeAuth: (authMethod?: 'cli' | 'api_key') => Promise<{ + verifyClaudeAuth: ( + authMethod?: 'cli' | 'api_key', + apiKey?: string + ) => Promise<{ success: boolean; authenticated: boolean; error?: string; @@ -1118,7 +1121,10 @@ interface SetupAPI { isMac: boolean; isLinux: boolean; }>; - verifyClaudeAuth: (authMethod?: 'cli' | 'api_key') => Promise<{ + verifyClaudeAuth: ( + authMethod?: 'cli' | 'api_key', + apiKey?: string + ) => Promise<{ success: boolean; authenticated: boolean; error?: string; @@ -1208,8 +1214,12 @@ function createMockSetupAPI(): SetupAPI { }; }, - verifyClaudeAuth: async (authMethod?: 'cli' | 'api_key') => { - console.log('[Mock] Verifying Claude auth with method:', authMethod); + verifyClaudeAuth: async (authMethod?: 'cli' | 'api_key', apiKey?: string) => { + console.log( + '[Mock] Verifying Claude auth with method:', + authMethod, + apiKey ? '(with key)' : '' + ); // Mock always returns not authenticated return { success: true, diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index d5019aef..dddf9eae 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -491,12 +491,13 @@ export class HttpApiClient implements ElectronAPI { }> => this.get('/api/setup/platform'), verifyClaudeAuth: ( - authMethod?: 'cli' | 'api_key' + authMethod?: 'cli' | 'api_key', + apiKey?: string ): Promise<{ success: boolean; authenticated: boolean; error?: string; - }> => this.post('/api/setup/verify-claude-auth', { authMethod }), + }> => this.post('/api/setup/verify-claude-auth', { authMethod, apiKey }), getGhStatus: (): Promise<{ success: boolean;