feat(UI): protect API keys by not displaying them in the UI.

This commit is contained in:
Willie Zutz 2025-05-20 00:44:52 -06:00
parent cae11cc13c
commit 288120dc1d
2 changed files with 52 additions and 22 deletions

View file

@ -51,15 +51,23 @@ export const GET = async (req: Request) => {
}); });
} }
config['openaiApiKey'] = getOpenaiApiKey(); // Helper function to obfuscate API keys
const protectApiKey = (key: string | null | undefined) => {
return key ? "protected" : key;
};
// Obfuscate all API keys in the response
config['openaiApiKey'] = protectApiKey(getOpenaiApiKey());
config['groqApiKey'] = protectApiKey(getGroqApiKey());
config['anthropicApiKey'] = protectApiKey(getAnthropicApiKey());
config['geminiApiKey'] = protectApiKey(getGeminiApiKey());
config['deepseekApiKey'] = protectApiKey(getDeepseekApiKey());
config['customOpenaiApiKey'] = protectApiKey(getCustomOpenaiApiKey());
// Non-sensitive values remain unchanged
config['ollamaApiUrl'] = getOllamaApiEndpoint(); config['ollamaApiUrl'] = getOllamaApiEndpoint();
config['lmStudioApiUrl'] = getLMStudioApiEndpoint(); config['lmStudioApiUrl'] = getLMStudioApiEndpoint();
config['anthropicApiKey'] = getAnthropicApiKey();
config['groqApiKey'] = getGroqApiKey();
config['geminiApiKey'] = getGeminiApiKey();
config['deepseekApiKey'] = getDeepseekApiKey();
config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl(); config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl();
config['customOpenaiApiKey'] = getCustomOpenaiApiKey();
config['customOpenaiModelName'] = getCustomOpenaiModelName(); config['customOpenaiModelName'] = getCustomOpenaiModelName();
config['baseUrl'] = getBaseUrl(); config['baseUrl'] = getBaseUrl();
@ -77,32 +85,39 @@ export const POST = async (req: Request) => {
try { try {
const config = await req.json(); const config = await req.json();
const getUpdatedProtectedValue = (newValue: string, currentConfig: string) => {
if (newValue === 'protected') {
return currentConfig;
}
return newValue;
}
const updatedConfig = { const updatedConfig = {
MODELS: { MODELS: {
OPENAI: { OPENAI: {
API_KEY: config.openaiApiKey, API_KEY: getUpdatedProtectedValue(config.openaiApiKey, getOpenaiApiKey()),
}, },
GROQ: { GROQ: {
API_KEY: config.groqApiKey, API_KEY: getUpdatedProtectedValue(config.groqApiKey, getGroqApiKey()),
}, },
ANTHROPIC: { ANTHROPIC: {
API_KEY: config.anthropicApiKey, API_KEY: getUpdatedProtectedValue(config.anthropicApiKey, getAnthropicApiKey()),
}, },
GEMINI: { GEMINI: {
API_KEY: config.geminiApiKey, API_KEY: getUpdatedProtectedValue(config.geminiApiKey, getGeminiApiKey()),
}, },
OLLAMA: { OLLAMA: {
API_URL: config.ollamaApiUrl, API_URL: config.ollamaApiUrl,
}, },
DEEPSEEK: { DEEPSEEK: {
API_KEY: config.deepseekApiKey, API_KEY: getUpdatedProtectedValue(config.deepseekApiKey, getDeepseekApiKey()),
}, },
LM_STUDIO: { LM_STUDIO: {
API_URL: config.lmStudioApiUrl, API_URL: config.lmStudioApiUrl,
}, },
CUSTOM_OPENAI: { CUSTOM_OPENAI: {
API_URL: config.customOpenaiApiUrl, API_URL: config.customOpenaiApiUrl,
API_KEY: config.customOpenaiApiKey, API_KEY: getUpdatedProtectedValue(config.customOpenaiApiKey, getCustomOpenaiApiKey()),
MODEL_NAME: config.customOpenaiModelName, MODEL_NAME: config.customOpenaiModelName,
}, },
}, },

View file

@ -1,6 +1,6 @@
'use client'; 'use client';
import { Settings as SettingsIcon, ArrowLeft, Loader2 } from 'lucide-react'; import { Settings as SettingsIcon, ArrowLeft, Loader2, Info } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
@ -117,12 +117,24 @@ const Select = ({
const SettingsSection = ({ const SettingsSection = ({
title, title,
children, children,
tooltip,
}: { }: {
title: string; title: string;
children: React.ReactNode; children: React.ReactNode;
tooltip?: string;
}) => ( }) => (
<div className="flex flex-col space-y-4 p-4 bg-light-secondary/50 dark:bg-dark-secondary/50 rounded-xl border border-light-200 dark:border-dark-200"> <div className="flex flex-col space-y-4 p-4 bg-light-secondary/50 dark:bg-dark-secondary/50 rounded-xl border border-light-200 dark:border-dark-200">
<h2 className="text-black/90 dark:text-white/90 font-medium">{title}</h2> <div className="flex items-center gap-2">
<h2 className="text-black/90 dark:text-white/90 font-medium">{title}</h2>
{tooltip && (
<div className="relative group">
<Info size={16} className="text-black/70 dark:text-white/70 cursor-help" />
<div className="absolute left-1/2 -translate-x-1/2 bottom-full mb-2 px-3 py-2 bg-black/90 dark:bg-white/90 text-white dark:text-black text-xs rounded-lg opacity-0 group-hover:opacity-100 whitespace-nowrap transition-opacity">
{tooltip}
</div>
</div>
)}
</div>
{children} {children}
</div> </div>
); );
@ -226,7 +238,7 @@ const Page = () => {
fetchConfig(); fetchConfig();
}, []); }, []);
const saveConfig = async (key: string, value: any) => { const saveConfig = async (key: string, value: any) => {
setSavingStates((prev) => ({ ...prev, [key]: true })); setSavingStates((prev) => ({ ...prev, [key]: true }));
try { try {
@ -671,7 +683,7 @@ const Page = () => {
Custom OpenAI API Key Custom OpenAI API Key
</p> </p>
<Input <Input
type="text" type="password"
placeholder="Custom OpenAI API Key" placeholder="Custom OpenAI API Key"
value={config.customOpenaiApiKey} value={config.customOpenaiApiKey}
isSaving={savingStates['customOpenaiApiKey']} isSaving={savingStates['customOpenaiApiKey']}
@ -786,14 +798,17 @@ const Page = () => {
)} )}
</SettingsSection> </SettingsSection>
<SettingsSection title="API Keys"> <SettingsSection
title="API Keys"
tooltip="API Key values can be viewed in the config.toml file"
>
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
<div className="flex flex-col space-y-1"> <div className="flex flex-col space-y-1">
<p className="text-black/70 dark:text-white/70 text-sm"> <p className="text-black/70 dark:text-white/70 text-sm">
OpenAI API Key OpenAI API Key
</p> </p>
<Input <Input
type="text" type="password"
placeholder="OpenAI API Key" placeholder="OpenAI API Key"
value={config.openaiApiKey} value={config.openaiApiKey}
isSaving={savingStates['openaiApiKey']} isSaving={savingStates['openaiApiKey']}
@ -831,7 +846,7 @@ const Page = () => {
GROQ API Key GROQ API Key
</p> </p>
<Input <Input
type="text" type="password"
placeholder="GROQ API Key" placeholder="GROQ API Key"
value={config.groqApiKey} value={config.groqApiKey}
isSaving={savingStates['groqApiKey']} isSaving={savingStates['groqApiKey']}
@ -850,7 +865,7 @@ const Page = () => {
Anthropic API Key Anthropic API Key
</p> </p>
<Input <Input
type="text" type="password"
placeholder="Anthropic API key" placeholder="Anthropic API key"
value={config.anthropicApiKey} value={config.anthropicApiKey}
isSaving={savingStates['anthropicApiKey']} isSaving={savingStates['anthropicApiKey']}
@ -869,7 +884,7 @@ const Page = () => {
Gemini API Key Gemini API Key
</p> </p>
<Input <Input
type="text" type="password"
placeholder="Gemini API key" placeholder="Gemini API key"
value={config.geminiApiKey} value={config.geminiApiKey}
isSaving={savingStates['geminiApiKey']} isSaving={savingStates['geminiApiKey']}
@ -888,7 +903,7 @@ const Page = () => {
Deepseek API Key Deepseek API Key
</p> </p>
<Input <Input
type="text" type="password"
placeholder="Deepseek API Key" placeholder="Deepseek API Key"
value={config.deepseekApiKey} value={config.deepseekApiKey}
isSaving={savingStates['deepseekApiKey']} isSaving={savingStates['deepseekApiKey']}