File size: 4,191 Bytes
064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | import { useCallback, useState } from 'react'
import { getIsNonInteractiveSession } from '../bootstrap/state.js'
import { verifyApiKey } from '../services/api/claude.js'
import { getGlobalConfig } from '../utils/config.js'
import {
getAnthropicApiKeyWithSource,
getApiKeyFromApiKeyHelper,
isAnthropicAuthEnabled,
isClaudeAISubscriber,
getConfiguredAuthProvider,
getOpenAIAuthTokens,
} from '../utils/auth.js'
export type VerificationStatus =
| 'loading'
| 'valid'
| 'invalid'
| 'missing'
| 'error'
export type ApiKeyVerificationResult = {
status: VerificationStatus
reverify: () => Promise<void>
error: Error | null
}
export function useApiKeyVerification(): ApiKeyVerificationResult {
const [status, setStatus] = useState<VerificationStatus>(() => {
const authProvider = getConfiguredAuthProvider()
// Check OpenAI authentication
if (authProvider === 'openai') {
const config = getGlobalConfig()
const hasApiKey = !!config.openAiApiKey
const hasAccessToken = !!config.openAiAccessToken
if (hasApiKey || hasAccessToken) {
return 'valid'
}
return 'missing'
}
// Check OpenRouter authentication
if (authProvider === 'openrouter') {
const config = getGlobalConfig()
const hasApiKey = !!config.openRouterApiKey
if (hasApiKey) {
return 'valid'
}
return 'missing'
}
// Anthropic authentication
if (!isAnthropicAuthEnabled() || isClaudeAISubscriber()) {
return 'valid'
}
// Use skipRetrievingKeyFromApiKeyHelper to avoid executing apiKeyHelper
// before trust dialog is shown (security: prevents RCE via settings.json)
const { key, source } = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true,
})
// If apiKeyHelper is configured, we have a key source even though we
// haven't executed it yet - return 'loading' to indicate we'll verify later
if (key || source === 'apiKeyHelper') {
return 'loading'
}
return 'missing'
})
const [error, setError] = useState<Error | null>(null)
const verify = useCallback(async (): Promise<void> => {
const authProvider = getConfiguredAuthProvider()
// Check OpenAI authentication
if (authProvider === 'openai') {
const config = getGlobalConfig()
const hasApiKey = !!config.openAiApiKey
const hasAccessToken = !!config.openAiAccessToken
if (hasApiKey || hasAccessToken) {
setStatus('valid')
} else {
setStatus('missing')
}
return
}
// Check OpenRouter authentication
if (authProvider === 'openrouter') {
const config = getGlobalConfig()
const hasApiKey = !!config.openRouterApiKey
if (hasApiKey) {
setStatus('valid')
} else {
setStatus('missing')
}
return
}
// Anthropic authentication
if (!isAnthropicAuthEnabled() || isClaudeAISubscriber()) {
setStatus('valid')
return
}
// Warm the apiKeyHelper cache (no-op if not configured), then read from
// all sources. getAnthropicApiKeyWithSource() reads the now-warm cache.
await getApiKeyFromApiKeyHelper(getIsNonInteractiveSession())
const { key: apiKey, source } = getAnthropicApiKeyWithSource()
if (!apiKey) {
if (source === 'apiKeyHelper') {
setStatus('error')
setError(new Error('API key helper did not return a valid key'))
return
}
const newStatus = 'missing'
setStatus(newStatus)
return
}
try {
const isValid = await verifyApiKey(apiKey, false)
const newStatus = isValid ? 'valid' : 'invalid'
setStatus(newStatus)
return
} catch (error) {
// This happens when there an error response from the API but it's not an invalid API key error
// In this case, we still mark the API key as invalid - but we also log the error so we can
// display it to the user to be more helpful
setError(error as Error)
const newStatus = 'error'
setStatus(newStatus)
return
}
}, [])
return {
status,
reverify: verify,
error,
}
}
|