feat(tool): add support for API key authentication via query parameter (#21656)
This commit is contained in:
parent
f929bfb94c
commit
e576b989b8
9 changed files with 126 additions and 20 deletions
|
|
@ -39,19 +39,22 @@ class ApiToolProviderController(ToolProviderController):
|
||||||
type=ProviderConfig.Type.SELECT,
|
type=ProviderConfig.Type.SELECT,
|
||||||
options=[
|
options=[
|
||||||
ProviderConfig.Option(value="none", label=I18nObject(en_US="None", zh_Hans="无")),
|
ProviderConfig.Option(value="none", label=I18nObject(en_US="None", zh_Hans="无")),
|
||||||
ProviderConfig.Option(value="api_key", label=I18nObject(en_US="api_key", zh_Hans="api_key")),
|
ProviderConfig.Option(value="api_key_header", label=I18nObject(en_US="Header", zh_Hans="请求头")),
|
||||||
|
ProviderConfig.Option(
|
||||||
|
value="api_key_query", label=I18nObject(en_US="Query Param", zh_Hans="查询参数")
|
||||||
|
),
|
||||||
],
|
],
|
||||||
default="none",
|
default="none",
|
||||||
help=I18nObject(en_US="The auth type of the api provider", zh_Hans="api provider 的认证类型"),
|
help=I18nObject(en_US="The auth type of the api provider", zh_Hans="api provider 的认证类型"),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if auth_type == ApiProviderAuthType.API_KEY:
|
if auth_type == ApiProviderAuthType.API_KEY_HEADER:
|
||||||
credentials_schema = [
|
credentials_schema = [
|
||||||
*credentials_schema,
|
*credentials_schema,
|
||||||
ProviderConfig(
|
ProviderConfig(
|
||||||
name="api_key_header",
|
name="api_key_header",
|
||||||
required=False,
|
required=False,
|
||||||
default="api_key",
|
default="Authorization",
|
||||||
type=ProviderConfig.Type.TEXT_INPUT,
|
type=ProviderConfig.Type.TEXT_INPUT,
|
||||||
help=I18nObject(en_US="The header name of the api key", zh_Hans="携带 api key 的 header 名称"),
|
help=I18nObject(en_US="The header name of the api key", zh_Hans="携带 api key 的 header 名称"),
|
||||||
),
|
),
|
||||||
|
|
@ -74,6 +77,25 @@ class ApiToolProviderController(ToolProviderController):
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
elif auth_type == ApiProviderAuthType.API_KEY_QUERY:
|
||||||
|
credentials_schema = [
|
||||||
|
*credentials_schema,
|
||||||
|
ProviderConfig(
|
||||||
|
name="api_key_query_param",
|
||||||
|
required=False,
|
||||||
|
default="key",
|
||||||
|
type=ProviderConfig.Type.TEXT_INPUT,
|
||||||
|
help=I18nObject(
|
||||||
|
en_US="The query parameter name of the api key", zh_Hans="携带 api key 的查询参数名称"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ProviderConfig(
|
||||||
|
name="api_key_value",
|
||||||
|
required=True,
|
||||||
|
type=ProviderConfig.Type.SECRET_INPUT,
|
||||||
|
help=I18nObject(en_US="The api key", zh_Hans="api key 的值"),
|
||||||
|
),
|
||||||
|
]
|
||||||
elif auth_type == ApiProviderAuthType.NONE:
|
elif auth_type == ApiProviderAuthType.NONE:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ class ApiTool(Tool):
|
||||||
if "auth_type" not in credentials:
|
if "auth_type" not in credentials:
|
||||||
raise ToolProviderCredentialValidationError("Missing auth_type")
|
raise ToolProviderCredentialValidationError("Missing auth_type")
|
||||||
|
|
||||||
if credentials["auth_type"] == "api_key":
|
if credentials["auth_type"] in ("api_key_header", "api_key"): # backward compatibility:
|
||||||
api_key_header = "api_key"
|
api_key_header = "Authorization"
|
||||||
|
|
||||||
if "api_key_header" in credentials:
|
if "api_key_header" in credentials:
|
||||||
api_key_header = credentials["api_key_header"]
|
api_key_header = credentials["api_key_header"]
|
||||||
|
|
@ -100,6 +100,11 @@ class ApiTool(Tool):
|
||||||
|
|
||||||
headers[api_key_header] = credentials["api_key_value"]
|
headers[api_key_header] = credentials["api_key_value"]
|
||||||
|
|
||||||
|
elif credentials["auth_type"] == "api_key_query":
|
||||||
|
# For query parameter authentication, we don't add anything to headers
|
||||||
|
# The query parameter will be added in do_http_request method
|
||||||
|
pass
|
||||||
|
|
||||||
needed_parameters = [parameter for parameter in (self.api_bundle.parameters or []) if parameter.required]
|
needed_parameters = [parameter for parameter in (self.api_bundle.parameters or []) if parameter.required]
|
||||||
for parameter in needed_parameters:
|
for parameter in needed_parameters:
|
||||||
if parameter.required and parameter.name not in parameters:
|
if parameter.required and parameter.name not in parameters:
|
||||||
|
|
@ -154,6 +159,15 @@ class ApiTool(Tool):
|
||||||
cookies = {}
|
cookies = {}
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
|
# Add API key to query parameters if auth_type is api_key_query
|
||||||
|
if self.runtime and self.runtime.credentials:
|
||||||
|
credentials = self.runtime.credentials
|
||||||
|
if credentials.get("auth_type") == "api_key_query":
|
||||||
|
api_key_query_param = credentials.get("api_key_query_param", "key")
|
||||||
|
api_key_value = credentials.get("api_key_value")
|
||||||
|
if api_key_value:
|
||||||
|
params[api_key_query_param] = api_key_value
|
||||||
|
|
||||||
# check parameters
|
# check parameters
|
||||||
for parameter in self.api_bundle.openapi.get("parameters", []):
|
for parameter in self.api_bundle.openapi.get("parameters", []):
|
||||||
value = self.get_parameter_value(parameter, parameters)
|
value = self.get_parameter_value(parameter, parameters)
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@ class ApiProviderAuthType(Enum):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NONE = "none"
|
NONE = "none"
|
||||||
API_KEY = "api_key"
|
API_KEY_HEADER = "api_key_header"
|
||||||
|
API_KEY_QUERY = "api_key_query"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def value_of(cls, value: str) -> "ApiProviderAuthType":
|
def value_of(cls, value: str) -> "ApiProviderAuthType":
|
||||||
|
|
|
||||||
|
|
@ -684,9 +684,16 @@ class ToolManager:
|
||||||
if provider is None:
|
if provider is None:
|
||||||
raise ToolProviderNotFoundError(f"api provider {provider_id} not found")
|
raise ToolProviderNotFoundError(f"api provider {provider_id} not found")
|
||||||
|
|
||||||
|
auth_type = ApiProviderAuthType.NONE
|
||||||
|
provider_auth_type = provider.credentials.get("auth_type")
|
||||||
|
if provider_auth_type in ("api_key_header", "api_key"): # backward compatibility
|
||||||
|
auth_type = ApiProviderAuthType.API_KEY_HEADER
|
||||||
|
elif provider_auth_type == "api_key_query":
|
||||||
|
auth_type = ApiProviderAuthType.API_KEY_QUERY
|
||||||
|
|
||||||
controller = ApiToolProviderController.from_db(
|
controller = ApiToolProviderController.from_db(
|
||||||
provider,
|
provider,
|
||||||
ApiProviderAuthType.API_KEY if provider.credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE,
|
auth_type,
|
||||||
)
|
)
|
||||||
controller.load_bundled_tools(provider.tools)
|
controller.load_bundled_tools(provider.tools)
|
||||||
|
|
||||||
|
|
@ -745,9 +752,16 @@ class ToolManager:
|
||||||
credentials = {}
|
credentials = {}
|
||||||
|
|
||||||
# package tool provider controller
|
# package tool provider controller
|
||||||
|
auth_type = ApiProviderAuthType.NONE
|
||||||
|
credentials_auth_type = credentials.get("auth_type")
|
||||||
|
if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility
|
||||||
|
auth_type = ApiProviderAuthType.API_KEY_HEADER
|
||||||
|
elif credentials_auth_type == "api_key_query":
|
||||||
|
auth_type = ApiProviderAuthType.API_KEY_QUERY
|
||||||
|
|
||||||
controller = ApiToolProviderController.from_db(
|
controller = ApiToolProviderController.from_db(
|
||||||
provider_obj,
|
provider_obj,
|
||||||
ApiProviderAuthType.API_KEY if credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE,
|
auth_type,
|
||||||
)
|
)
|
||||||
# init tool configuration
|
# init tool configuration
|
||||||
tool_configuration = ProviderConfigEncrypter(
|
tool_configuration = ProviderConfigEncrypter(
|
||||||
|
|
|
||||||
|
|
@ -159,11 +159,16 @@ class ToolTransformService:
|
||||||
convert provider controller to user provider
|
convert provider controller to user provider
|
||||||
"""
|
"""
|
||||||
# package tool provider controller
|
# package tool provider controller
|
||||||
|
auth_type = ApiProviderAuthType.NONE
|
||||||
|
credentials_auth_type = db_provider.credentials.get("auth_type")
|
||||||
|
if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility
|
||||||
|
auth_type = ApiProviderAuthType.API_KEY_HEADER
|
||||||
|
elif credentials_auth_type == "api_key_query":
|
||||||
|
auth_type = ApiProviderAuthType.API_KEY_QUERY
|
||||||
|
|
||||||
controller = ApiToolProviderController.from_db(
|
controller = ApiToolProviderController.from_db(
|
||||||
db_provider=db_provider,
|
db_provider=db_provider,
|
||||||
auth_type=ApiProviderAuthType.API_KEY
|
auth_type=auth_type,
|
||||||
if db_provider.credentials["auth_type"] == "api_key"
|
|
||||||
else ApiProviderAuthType.NONE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
|
|
|
||||||
|
|
@ -68,23 +68,34 @@ const ConfigCredential: FC<Props> = ({
|
||||||
text={t('tools.createTool.authMethod.types.none')}
|
text={t('tools.createTool.authMethod.types.none')}
|
||||||
value={AuthType.none}
|
value={AuthType.none}
|
||||||
isChecked={tempCredential.auth_type === AuthType.none}
|
isChecked={tempCredential.auth_type === AuthType.none}
|
||||||
onClick={value => setTempCredential({ ...tempCredential, auth_type: value as AuthType })}
|
onClick={value => setTempCredential({
|
||||||
|
auth_type: value as AuthType,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<SelectItem
|
<SelectItem
|
||||||
text={t('tools.createTool.authMethod.types.api_key')}
|
text={t('tools.createTool.authMethod.types.api_key_header')}
|
||||||
value={AuthType.apiKey}
|
value={AuthType.apiKeyHeader}
|
||||||
isChecked={tempCredential.auth_type === AuthType.apiKey}
|
isChecked={tempCredential.auth_type === AuthType.apiKeyHeader}
|
||||||
onClick={value => setTempCredential({
|
onClick={value => setTempCredential({
|
||||||
...tempCredential,
|
|
||||||
auth_type: value as AuthType,
|
auth_type: value as AuthType,
|
||||||
api_key_header: tempCredential.api_key_header || 'Authorization',
|
api_key_header: tempCredential.api_key_header || 'Authorization',
|
||||||
api_key_value: tempCredential.api_key_value || '',
|
api_key_value: tempCredential.api_key_value || '',
|
||||||
api_key_header_prefix: tempCredential.api_key_header_prefix || AuthHeaderPrefix.custom,
|
api_key_header_prefix: tempCredential.api_key_header_prefix || AuthHeaderPrefix.custom,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
<SelectItem
|
||||||
|
text={t('tools.createTool.authMethod.types.api_key_query')}
|
||||||
|
value={AuthType.apiKeyQuery}
|
||||||
|
isChecked={tempCredential.auth_type === AuthType.apiKeyQuery}
|
||||||
|
onClick={value => setTempCredential({
|
||||||
|
auth_type: value as AuthType,
|
||||||
|
api_key_query_param: tempCredential.api_key_query_param || 'key',
|
||||||
|
api_key_value: tempCredential.api_key_value || '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{tempCredential.auth_type === AuthType.apiKey && (
|
{tempCredential.auth_type === AuthType.apiKeyHeader && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.authHeaderPrefix.title')}</div>
|
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.authHeaderPrefix.title')}</div>
|
||||||
|
|
@ -136,6 +147,35 @@ const ConfigCredential: FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>)}
|
</>)}
|
||||||
|
{tempCredential.auth_type === AuthType.apiKeyQuery && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<div className='system-sm-medium flex items-center py-2 text-text-primary'>
|
||||||
|
{t('tools.createTool.authMethod.queryParam')}
|
||||||
|
<Tooltip
|
||||||
|
popupContent={
|
||||||
|
<div className='w-[261px] text-text-tertiary'>
|
||||||
|
{t('tools.createTool.authMethod.queryParamTooltip')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
triggerClassName='ml-0.5 w-4 h-4'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
value={tempCredential.api_key_query_param}
|
||||||
|
onChange={e => setTempCredential({ ...tempCredential, api_key_query_param: e.target.value })}
|
||||||
|
placeholder={t('tools.createTool.authMethod.types.queryParamPlaceholder')!}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.authMethod.value')}</div>
|
||||||
|
<Input
|
||||||
|
value={tempCredential.api_key_value}
|
||||||
|
onChange={e => setTempCredential({ ...tempCredential, api_key_value: e.target.value })}
|
||||||
|
placeholder={t('tools.createTool.authMethod.types.apiValuePlaceholder')!}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ export enum LOC {
|
||||||
|
|
||||||
export enum AuthType {
|
export enum AuthType {
|
||||||
none = 'none',
|
none = 'none',
|
||||||
apiKey = 'api_key',
|
apiKeyHeader = 'api_key_header',
|
||||||
|
apiKeyQuery = 'api_key_query',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthHeaderPrefix {
|
export enum AuthHeaderPrefix {
|
||||||
|
|
@ -21,6 +22,7 @@ export type Credential = {
|
||||||
api_key_header?: string
|
api_key_header?: string
|
||||||
api_key_value?: string
|
api_key_value?: string
|
||||||
api_key_header_prefix?: AuthHeaderPrefix
|
api_key_header_prefix?: AuthHeaderPrefix
|
||||||
|
api_key_query_param?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CollectionType {
|
export enum CollectionType {
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,15 @@ const translation = {
|
||||||
title: 'Authorization method',
|
title: 'Authorization method',
|
||||||
type: 'Authorization type',
|
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',
|
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',
|
||||||
|
queryParam: 'Query Parameter',
|
||||||
|
queryParamTooltip: 'The name of the API key query parameter to pass, e.g. "key" in "https://example.com/test?key=API_KEY".',
|
||||||
types: {
|
types: {
|
||||||
none: 'None',
|
none: 'None',
|
||||||
api_key: 'API Key',
|
api_key_header: 'Header',
|
||||||
|
api_key_query: 'Query Param',
|
||||||
apiKeyPlaceholder: 'HTTP header name for API Key',
|
apiKeyPlaceholder: 'HTTP header name for API Key',
|
||||||
apiValuePlaceholder: 'Enter API Key',
|
apiValuePlaceholder: 'Enter API Key',
|
||||||
|
queryParamPlaceholder: 'Query parameter name for API Key',
|
||||||
},
|
},
|
||||||
key: 'Key',
|
key: 'Key',
|
||||||
value: 'Value',
|
value: 'Value',
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,15 @@ const translation = {
|
||||||
title: '鉴权方法',
|
title: '鉴权方法',
|
||||||
type: '鉴权类型',
|
type: '鉴权类型',
|
||||||
keyTooltip: 'HTTP 头部名称,如果你不知道是什么,可以将其保留为 Authorization 或设置为自定义值',
|
keyTooltip: 'HTTP 头部名称,如果你不知道是什么,可以将其保留为 Authorization 或设置为自定义值',
|
||||||
|
queryParam: '查询参数',
|
||||||
|
queryParamTooltip: '用于传递 API 密钥查询参数的名称, 如 "https://example.com/test?key=API_KEY" 中的 "key"参数',
|
||||||
types: {
|
types: {
|
||||||
none: '无',
|
none: '无',
|
||||||
api_key: 'API Key',
|
api_key_header: '请求头',
|
||||||
|
api_key_query: '查询参数',
|
||||||
apiKeyPlaceholder: 'HTTP 头部名称,用于传递 API Key',
|
apiKeyPlaceholder: 'HTTP 头部名称,用于传递 API Key',
|
||||||
apiValuePlaceholder: '输入 API Key',
|
apiValuePlaceholder: '输入 API Key',
|
||||||
|
queryParamPlaceholder: '查询参数名称,用于传递 API Key',
|
||||||
},
|
},
|
||||||
key: '键',
|
key: '键',
|
||||||
value: '值',
|
value: '值',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue