From d44b05a9e502b5578bebc8b6d139805b4a030803 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 28 Feb 2024 23:19:08 +0800 Subject: [PATCH] feat: support auth type like basic bearer and custom (#2613) --- api/core/tools/provider/api_tool_provider.py | 15 +++++ api/core/tools/tool/api_tool.py | 11 ++++ .../config-credentials.tsx | 56 ++++++++++++++++--- .../edit-custom-collection-modal/index.tsx | 5 +- web/app/components/tools/tool-list/index.tsx | 6 +- web/app/components/tools/types.ts | 7 +++ web/i18n/en-US/tools.ts | 9 +++ web/i18n/pt-BR/tools.ts | 7 +++ web/i18n/uk-UA/tools.ts | 7 +++ web/i18n/zh-Hans/tools.ts | 9 +++ 10 files changed, 122 insertions(+), 10 deletions(-) diff --git a/api/core/tools/provider/api_tool_provider.py b/api/core/tools/provider/api_tool_provider.py index 13f4bc2c3d..eb839e9341 100644 --- a/api/core/tools/provider/api_tool_provider.py +++ b/api/core/tools/provider/api_tool_provider.py @@ -55,6 +55,21 @@ class ApiBasedToolProviderController(ToolProviderController): en_US='The api key', zh_Hans='api key的值' ) + ), + 'api_key_header_prefix': ToolProviderCredentials( + name='api_key_header_prefix', + required=False, + default='basic', + type=ToolProviderCredentials.CredentialsType.SELECT, + help=I18nObject( + en_US='The prefix of the api key header', + zh_Hans='api key header 的前缀' + ), + options=[ + ToolCredentialsOption(value='basic', label=I18nObject(en_US='Basic', zh_Hans='Basic')), + ToolCredentialsOption(value='bearer', label=I18nObject(en_US='Bearer', zh_Hans='Bearer')), + ToolCredentialsOption(value='custom', label=I18nObject(en_US='Custom', zh_Hans='Custom')) + ] ) } elif auth_type == ApiProviderAuthType.NONE: diff --git a/api/core/tools/tool/api_tool.py b/api/core/tools/tool/api_tool.py index 2a1ee92e78..781eff13b4 100644 --- a/api/core/tools/tool/api_tool.py +++ b/api/core/tools/tool/api_tool.py @@ -62,6 +62,17 @@ class ApiTool(Tool): if 'api_key_value' not in credentials: raise ToolProviderCredentialValidationError('Missing api_key_value') + elif not isinstance(credentials['api_key_value'], str): + raise ToolProviderCredentialValidationError('api_key_value must be a string') + + if 'api_key_header_prefix' in credentials: + api_key_header_prefix = credentials['api_key_header_prefix'] + if api_key_header_prefix == 'basic': + credentials['api_key_value'] = f'Basic {credentials["api_key_value"]}' + elif api_key_header_prefix == 'bearer': + credentials['api_key_value'] = f'Bearer {credentials["api_key_value"]}' + elif api_key_header_prefix == 'custom': + pass headers[api_key_header] = credentials['api_key_value'] diff --git a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx index 1deef1b531..9da0ff7dcc 100644 --- a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx @@ -3,11 +3,13 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' +import Tooltip from '../../base/tooltip' +import { HelpCircle } from '../../base/icons/src/vender/line/general' import type { Credential } from '@/app/components/tools/types' import Drawer from '@/app/components/base/drawer-plus' import Button from '@/app/components/base/button' import Radio from '@/app/components/base/radio/ui' -import { AuthType } from '@/app/components/tools/types' +import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types' type Props = { credential: Credential @@ -18,9 +20,9 @@ const keyClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900' type ItemProps = { text: string - value: AuthType + value: AuthType | AuthHeaderPrefix isChecked: boolean - onClick: (value: AuthType) => void + onClick: (value: AuthType | AuthHeaderPrefix) => void } const SelectItem: FC = ({ text, value, isChecked, onClick }) => { @@ -31,7 +33,6 @@ const SelectItem: FC = ({ text, value, isChecked, onClick }) => { >
{text}
- ) } @@ -43,6 +44,7 @@ const ConfigCredential: FC = ({ }) => { const { t } = useTranslation() const [tempCredential, setTempCredential] = React.useState(credential) + return ( = ({ text={t('tools.createTool.authMethod.types.none')} value={AuthType.none} isChecked={tempCredential.auth_type === AuthType.none} - onClick={value => setTempCredential({ ...tempCredential, auth_type: value })} + onClick={value => setTempCredential({ ...tempCredential, auth_type: value as AuthType })} /> setTempCredential({ ...tempCredential, auth_type: value })} + onClick={value => setTempCredential({ + ...tempCredential, + auth_type: value as AuthType, + api_key_header: tempCredential.api_key_header || 'Authorization', + api_key_value: tempCredential.api_key_value || '', + api_key_header_prefix: tempCredential.api_key_header_prefix || AuthHeaderPrefix.custom, + })} /> {tempCredential.auth_type === AuthType.apiKey && ( <> +
{t('tools.createTool.authHeaderPrefix.title')}
+
+ setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })} + /> + setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })} + /> + setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })} + /> +
-
{t('tools.createTool.authMethod.key')}
+
+ {t('tools.createTool.authMethod.key')} + + {t('tools.createTool.authMethod.keyTooltip')} +
+ } + > + + +
setTempCredential({ ...tempCredential, api_key_header: e.target.value })} @@ -83,7 +124,6 @@ const ConfigCredential: FC = ({ placeholder={t('tools.createTool.authMethod.types.apiKeyPlaceholder')!} /> -
{t('tools.createTool.authMethod.value')}
= ({ const { t } = useTranslation() const isAdd = !payload const isEdit = !!payload + const [editFirst, setEditFirst] = useState(!isAdd) const [paramsSchemas, setParamsSchemas] = useState(payload?.tools || []) const [customCollection, setCustomCollection, getCustomCollection] = useGetState(isAdd @@ -44,6 +45,8 @@ const EditCustomCollectionModal: FC = ({ provider: '', credentials: { auth_type: AuthType.none, + api_key_header: 'Authorization', + api_key_header_prefix: AuthHeaderPrefix.basic, }, icon: { content: '🕵️', diff --git a/web/app/components/tools/tool-list/index.tsx b/web/app/components/tools/tool-list/index.tsx index 58fcf5613b..3bee3292e6 100644 --- a/web/app/components/tools/tool-list/index.tsx +++ b/web/app/components/tools/tool-list/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' -import { CollectionType, LOC } from '../types' +import { AuthHeaderPrefix, AuthType, CollectionType, LOC } from '../types' import type { Collection, CustomCollectionBackend, Tool } from '../types' import Loading from '../../base/loading' import { ArrowNarrowRight } from '../../base/icons/src/vender/line/arrows' @@ -53,6 +53,10 @@ const ToolList: FC = ({ (async () => { if (collection.type === CollectionType.custom) { const res = await fetchCustomCollection(collection.name) + if (res.credentials.auth_type === AuthType.apiKey && !res.credentials.api_key_header_prefix) { + if (res.credentials.api_key_value) + res.credentials.api_key_header_prefix = AuthHeaderPrefix.custom + } setCustomCollection({ ...res, provider: collection.name, diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index e06e011767..389276e81c 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -9,10 +9,17 @@ export enum AuthType { apiKey = 'api_key', } +export enum AuthHeaderPrefix { + basic = 'basic', + bearer = 'bearer', + custom = 'custom', +} + export type Credential = { 'auth_type': AuthType 'api_key_header'?: string 'api_key_value'?: string + 'api_key_header_prefix'?: AuthHeaderPrefix } export enum CollectionType { diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 9746fab8bc..30e075210c 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -51,6 +51,7 @@ const translation = { authMethod: { title: 'Authorization method', type: 'Authorization type', + keyTooltip: 'Http Header Key, You can leave it with "Authorization" if you have no idea what it is or set it to a custom value', types: { none: 'None', api_key: 'API Key', @@ -60,6 +61,14 @@ const translation = { key: 'Key', value: 'Value', }, + authHeaderPrefix: { + title: 'Auth Type', + types: { + basic: 'Basic', + bearer: 'Bearer', + custom: 'Custom', + }, + }, privacyPolicy: 'Privacy policy', privacyPolicyPlaceholder: 'Please enter privacy policy', }, diff --git a/web/i18n/pt-BR/tools.ts b/web/i18n/pt-BR/tools.ts index 9e2da08a1a..3434bd15ee 100644 --- a/web/i18n/pt-BR/tools.ts +++ b/web/i18n/pt-BR/tools.ts @@ -58,6 +58,13 @@ const translation = { key: 'Chave', value: 'Valor', }, + authHeaderPrefix: { + types: { + basic: 'Basic', + bearer: 'Bearer', + custom: 'Custom', + }, + }, privacyPolicy: 'Política de Privacidade', privacyPolicyPlaceholder: 'Digite a política de privacidade', }, diff --git a/web/i18n/uk-UA/tools.ts b/web/i18n/uk-UA/tools.ts index 56b4371cfb..307149c386 100644 --- a/web/i18n/uk-UA/tools.ts +++ b/web/i18n/uk-UA/tools.ts @@ -58,6 +58,13 @@ const translation = { key: 'Ключ', value: 'Значення', }, + authHeaderPrefix: { + types: { + basic: 'Basic', + bearer: 'Bearer', + custom: 'Custom', + }, + }, privacyPolicy: 'Політика конфіденційності', privacyPolicyPlaceholder: 'Введіть політику конфіденційності', }, diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index ff3b5c0fb8..c709d62547 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -51,6 +51,7 @@ const translation = { authMethod: { title: '鉴权方法', type: '鉴权类型', + keyTooltip: 'HTTP 头部名称,如果你不知道是什么,可以将其保留为 Authorization 或设置为自定义值', types: { none: '无', api_key: 'API Key', @@ -60,6 +61,14 @@ const translation = { key: '键', value: '值', }, + authHeaderPrefix: { + title: '鉴权头部前缀', + types: { + basic: 'Basic', + bearer: 'Bearer', + custom: 'Custom', + }, + }, privacyPolicy: '隐私协议', privacyPolicyPlaceholder: '请输入隐私协议', },