Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate api key before saving #1057

Merged
merged 1 commit into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { Brain } from "@/lib/context/BrainProvider/types";
import { defineMaxTokens } from "@/lib/helpers/defineMaxTokens";
import { useToast } from "@/lib/hooks";

import { validateOpenAIKey } from "../utils/validateOpenAIKey";

type UseSettingsTabProps = {
brainId: UUID;
};
Expand Down Expand Up @@ -95,8 +97,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
if (brain.model !== undefined) {
setValue("model", brain.model);
}
},50);

}, 50);
};
useEffect(() => {
void fetchBrain();
Expand Down Expand Up @@ -142,7 +143,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
await setAsDefaultBrain(brainId);
publish({
variant: "success",
text: t("defaultBrainSet",{ns:"config"}),
text: t("defaultBrainSet", { ns: "config" }),
});
void fetchAllBrains();
void fetchDefaultBrain();
Expand Down Expand Up @@ -180,12 +181,12 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
void fetchBrain();
publish({
variant: "success",
text: t("promptRemoved",{ns:"config"}),
text: t("promptRemoved", { ns: "config" }),
});
} catch (err) {
publish({
variant: "danger",
text: t("errorRemovingPrompt",{ns:"config"}),
text: t("errorRemovingPrompt", { ns: "config" }),
});
} finally {
setIsUpdating(false);
Expand All @@ -203,39 +204,47 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
}
};

const handleSubmit = async (checkDirty:boolean) => {
const handleSubmit = async (checkDirty: boolean) => {
const hasChanges = Object.keys(dirtyFields).length > 0;
if (!hasChanges && checkDirty) {
return;
}
const { name: isNameDirty } = dirtyFields;
const { name } = getValues();
const { name, openAiKey: openai_api_key } = getValues();
if (isNameDirty !== undefined && isNameDirty && name.trim() === "") {
publish({
variant: "danger",
text: t("nameRequired",{ns:"config"}),
text: t("nameRequired", { ns: "config" }),
});

return;
}

if (
openai_api_key !== undefined &&
!(await validateOpenAIKey(
openai_api_key,
{
badApiKeyError: t("incorrectApiKey", { ns: "config" }),
invalidApiKeyError: t("invalidApiKeyError", { ns: "config" }),
},
publish
))
) {
return;
}

try {
setIsUpdating(true);

const {
maxTokens: max_tokens,
openAiKey: openai_api_key,
prompt,
...otherConfigs
} = getValues();
const { maxTokens: max_tokens, prompt, ...otherConfigs } = getValues();

if (
dirtyFields["prompt"] &&
(prompt.content === "" || prompt.title === "")
) {
publish({
variant: "warning",
text: t("promptFieldsRequired",{ns:"config"}),
text: t("promptFieldsRequired", { ns: "config" }),
});

return;
Expand Down Expand Up @@ -279,7 +288,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {

publish({
variant: "success",
text: t("brainUpdated",{ns:"config"}),
text: t("brainUpdated", { ns: "config" }),
});
void fetchAllBrains();
} catch (err) {
Expand Down Expand Up @@ -318,7 +327,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
setValue("prompt.content", content, {
shouldDirty: true,
});
};
};

return {
handleSubmit,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import axios from "axios";

import { ToastData } from "@/lib/components/ui/Toast/domain/types";
import { getAxiosErrorParams } from "@/lib/helpers/getAxiosErrorParams";

export const getOpenAIKeyValidationStatusCode = async (
key: string
): Promise<number> => {
const url = "https://api.openai.com/v1/chat/completions";
const headers = {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
};

const data = JSON.stringify({
model: "gpt-3.5-turbo",
messages: [
{
role: "user",
content: "Hello!",
},
],
});

try {
await axios.post(url, data, { headers });

return 200;
} catch (error) {
return getAxiosErrorParams(error)?.status ?? 400;
}
};

type ErrorMessages = {
badApiKeyError: string;
invalidApiKeyError: string;
};

export const validateOpenAIKey = async (
openai_api_key: string | undefined,
errorMessages: ErrorMessages,
publish: (toast: ToastData) => void
): Promise<boolean> => {
if (openai_api_key !== undefined) {
const keyValidationStatusCode = await getOpenAIKeyValidationStatusCode(
openai_api_key
);

if (keyValidationStatusCode !== 200) {
if (keyValidationStatusCode === 401) {
publish({
variant: "danger",
text: errorMessages.badApiKeyError,
});
}

if (keyValidationStatusCode === 429) {
publish({
variant: "danger",
text: errorMessages.invalidApiKeyError,
});
}

return false;
}

return true;
}

return false;
};
21 changes: 21 additions & 0 deletions frontend/app/user/components/ApiKeyConfig/hooks/useApiKeyConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable max-lines */
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

import { validateOpenAIKey } from "@/app/brains-management/[brainId]/components/BrainManagementTabs/components/SettingsTab/utils/validateOpenAIKey";
import { useAuthApi } from "@/lib/api/auth/useAuthApi";
import { useUserApi } from "@/lib/api/user/useUserApi";
import { UserIdentity } from "@/lib/api/user/user";
Expand All @@ -20,6 +22,7 @@ export const useApiKeyConfig = () => {
const { createApiKey } = useAuthApi();
const { publish } = useToast();
const [userIdentity, setUserIdentity] = useState<UserIdentity>();
const { t } = useTranslation(["config"]);

const fetchUserIdentity = async () => {
setUserIdentity(await getUserIdentity());
Expand Down Expand Up @@ -56,6 +59,24 @@ export const useApiKeyConfig = () => {
const changeOpenAiApiKey = async () => {
try {
setChangeOpenAiApiKeyRequestPending(true);

if (
openAiApiKey !== undefined &&
openAiApiKey !== null &&
!(await validateOpenAIKey(
openAiApiKey,
{
badApiKeyError: t("incorrectApiKey", { ns: "config" }),
invalidApiKeyError: t("invalidApiKeyError", { ns: "config" }),
},
publish
))
) {
setChangeOpenAiApiKeyRequestPending(false);

return;
}

await updateUserIdentity({
openai_api_key: openAiApiKey,
});
Expand Down
6 changes: 4 additions & 2 deletions frontend/public/locales/en/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"roleRequired": "You don't have the necessary role to access this tab 🧠💡🥲.",
"requireAccess": "Please require access from the owner.",
"ohno": "Oh no!",
"noUser": "No user"
}
"noUser": "No user",
"incorrectApiKey": "Incorrect API Key",
"invalidApiKeyError": "Invalid API Key"
}
4 changes: 3 additions & 1 deletion frontend/public/locales/es/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"supabaseURLPlaceHolder": "URL de Supabase",
"temperature": "Temperatura",
"title": "Configuración",
"updatingBrainSettings": "Actualizando configuración del cerebro..."
"updatingBrainSettings": "Actualizando configuración del cerebro...",
"incorrectApiKey": "Clave de API incorrecta",
"invalidApiKeyError": "Clave de API inválida"
}
4 changes: 3 additions & 1 deletion frontend/public/locales/fr/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"supabaseURLPlaceHolder": "URL Supabase",
"temperature": "Température",
"title": "Configuration",
"updatingBrainSettings": "Mise à jour des paramètres du cerveau..."
"updatingBrainSettings": "Mise à jour des paramètres du cerveau...",
"incorrectApiKey": "Clé API incorrecte",
"invalidApiKeyError": "Clé API invalide"
}
4 changes: 3 additions & 1 deletion frontend/public/locales/pt-br/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"roleRequired": "Você não possui a função necessária para acessar esta aba 🧠💡🥲.",
"requireAccess": "Por favor, solicite acesso ao proprietário.",
"ohno": "Oh, não!",
"noUser": "Nenhum usuário"
"noUser": "Nenhum usuário",
"incorrectApiKey": "Chave de API incorreta",
"invalidApiKeyError": "Chave de API inválida"
}
4 changes: 3 additions & 1 deletion frontend/public/locales/ru/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"roleRequired": "У вас нет необходимой роли для доступа к этой вкладке 🧠💡🥲.",
"requireAccess": "Пожалуйста, запросите доступ у владельца.",
"ohno": "О нет!",
"noUser": "Пользователь не найден"
"noUser": "Пользователь не найден",
"incorrectApiKey": "Неверный ключ API",
"invalidApiKeyError": "Недействительный ключ API"
}
4 changes: 3 additions & 1 deletion frontend/public/locales/zh-cn/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"roleRequired": "您没有访问此选项卡所需的权限 🧠💡🥲.",
"requireAccess": "请向所有者申请访问权限.",
"ohno": "哎呀!",
"noUser": "没有用户"
"noUser": "没有用户",
"incorrectApiKey": "无效的API密钥",
"invalidApiKeyError": "无效的API密钥"
}