feat(UI): protect API keys by not displaying them in the UI.
This commit is contained in:
parent
cae11cc13c
commit
288120dc1d
2 changed files with 52 additions and 22 deletions
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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']}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue