diff --git a/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/[id]/page.tsx b/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/[id]/page.tsx index 52a4bdc00..253e502d9 100644 --- a/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/[id]/page.tsx +++ b/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/[id]/page.tsx @@ -21,7 +21,7 @@ export const dynamic = 'force-dynamic'; export default async function Page(params: { params: Promise<{ id: string }>; - searchParams: Promise<{ path: string; code?: string }>; + searchParams: Promise<{ path: string; code?: string; isUser?: string }>; }) { const isEnableAuth = getIsEnableAuthToggle(); const token = await getUserToken(isEnableAuth, headers(), cookies()); @@ -32,6 +32,7 @@ export default async function Page(params: { } let oAuthCode = null; + let isUser = false; let etag = DEFAULT_ETAG; let toolsets: AssetToolset[] = []; @@ -40,6 +41,7 @@ export default async function Page(params: { try { const searchParams = await params.searchParams; oAuthCode = searchParams.code; + isUser = searchParams.isUser === 'true'; const path = decodeURIComponent(searchParams.path); const name = decodeURIComponent((await params.params).id); @@ -65,7 +67,13 @@ export default async function Page(params: { return ( - + ); diff --git a/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/actions.ts b/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/actions.ts index b1782ba68..e943a3460 100644 --- a/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/actions.ts +++ b/apps/ai-dial-admin/src/app/[lang]/assets-toolsets/actions.ts @@ -58,9 +58,14 @@ export async function getAssetTools(name: string) { return assetsApi.getTools(name, token); } -export async function signInToolset(toolset: AssetToolset, type: ToolsetAuthCredentialLevel, authCode?: string) { +export async function signInToolset( + toolset: AssetToolset, + type: ToolsetAuthCredentialLevel, + apiKey?: string, + authCode?: string, +) { const token = await getUserToken(getIsEnableAuthToggle(), headers(), cookies()); - return assetsApi.signInToolset(toolset, type, token, authCode); + return assetsApi.signInToolset(toolset, type, token, apiKey, authCode); } export async function signOutToolset(toolset: AssetToolset, type: ToolsetAuthCredentialLevel) { diff --git a/apps/ai-dial-admin/src/components/Assets/Deployments/Properties.tsx b/apps/ai-dial-admin/src/components/Assets/Deployments/Properties.tsx index d32c1bbb3..0bd9a1be9 100644 --- a/apps/ai-dial-admin/src/components/Assets/Deployments/Properties.tsx +++ b/apps/ai-dial-admin/src/components/Assets/Deployments/Properties.tsx @@ -29,6 +29,7 @@ import { Toolset } from '@/src/models/dial/toolset'; import { ApplicationRoute } from '@/src/types/routes'; import { getErrorNotification } from '@/src/utils/notification'; import { modifyNameVersionInPrompt } from '@/src/utils/prompts/versions'; +import Authentication from '@/src/components/Toolsets/View/Authentication'; interface Props { etag: string; @@ -36,10 +37,21 @@ interface Props { asset: DeploymentAsset; assets: DeploymentAsset[]; runners: DialApplicationScheme[]; + apiKeyValue?: string; + onChangeKeyValue?: (apiKeyValue: string) => void; onChange: (asset: DeploymentAsset) => void; } -const DeploymentProperties: FC = ({ etag, asset, view, assets, runners, onChange }) => { +const DeploymentProperties: FC = ({ + apiKeyValue, + onChangeKeyValue, + etag, + asset, + view, + assets, + runners, + onChange, +}) => { const t = useI18n() as (t: string) => string; const router = useRouter(); const { showNotification } = useNotification(); @@ -123,8 +135,12 @@ const DeploymentProperties: FC = ({ etag, asset, view, assets, runners, o {view === ApplicationRoute.AssetsToolsets && ( <> void} /> - {/* // TODO: waiting BE */} - {/* void} /> */} + void} + /> )} {view === ApplicationRoute.AssetsApplications && ( diff --git a/apps/ai-dial-admin/src/components/Assets/Toolsets/View.tsx b/apps/ai-dial-admin/src/components/Assets/Toolsets/View.tsx index 975932ad7..5b8cd77db 100644 --- a/apps/ai-dial-admin/src/components/Assets/Toolsets/View.tsx +++ b/apps/ai-dial-admin/src/components/Assets/Toolsets/View.tsx @@ -3,8 +3,9 @@ import { useRouter } from 'next/navigation'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { DialTabs } from '@epam/ai-dial-ui-kit'; +import { ButtonVariant, DialButton, DialTabs } from '@epam/ai-dial-ui-kit'; import classNames from 'classnames'; +import { IconLogin, IconLogout } from '@tabler/icons-react'; import { cloneDeep } from 'lodash'; import { @@ -35,18 +36,25 @@ import { addTrailingSlash, changePath, getListOfPathsToMove, removeTrailingSlash import { isEqualSkippingUndefined } from '@/src/utils/is-equals-entity'; import { getErrorNotification, getSuccessNotification } from '@/src/utils/notification'; import { getUrnForEntity } from '@/src/utils/open-in-new-tab'; -import { encodeToolsetRedirectState, isLoggedInToToolset } from '@/src/utils/toolset/toolset-auth'; +import { + encodeToolsetRedirectState, + isLoggedInToToolset, + isUserLoggedInToToolset, +} from '@/src/utils/toolset/toolset-auth'; import LoginPopup from './LoginPopup'; import { getUpdateNotificationDescription, getUpdateNotificationTitle } from '@/src/utils/entities/update-entity'; - +import { ToolsetI18nKey } from '@/src/constants/i18n'; +import { BASE_ICON_PROPS } from '@/src/constants/main-layout'; +let isSignInProcessed = false; interface Props { etag: string; oAuthCode?: string | null; + isUserLevel?: boolean; originalToolset: AssetToolset; toolsets: AssetToolset[]; } -const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) => { +const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets, isUserLevel }) => { const t = useI18n() as (stringToTranslate: string) => string; const tabs = [propertiesTabs(t), toolsTabs(t)]; const router = useRouter(); @@ -59,8 +67,8 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) const [isChanged, setIsChanged] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [jsonEditorEnabled, setJsonEditorEnabled] = useState(false); + const [apiKeyValue, setApiKeyValue] = useState(''); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const isToolsetSignedIn = useMemo(() => { return isLoggedInToToolset(selectedToolset); }, [selectedToolset]); @@ -165,15 +173,16 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) const signIn = useCallback( (type: ToolsetAuthCredentialLevel, code?: string) => { - signInToolset(selectedToolset, type, code).then((res) => { - if (res.success) { - router.push(getUrnForEntity(ApplicationRoute.AssetsToolsets, selectedToolset)); - } else { + isSignInProcessed = true; + signInToolset(selectedToolset, type, apiKeyValue, code).then((res) => { + isSignInProcessed = false; + if (!res.success) { showNotification(getErrorNotification(res.errorHeader, res.errorMessage)); } + router.push(getUrnForEntity(ApplicationRoute.AssetsToolsets, selectedToolset)); }); }, - [router, selectedToolset, showNotification], + [router, selectedToolset, showNotification, apiKeyValue], ); const onLogin = useCallback( @@ -193,9 +202,11 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) url.searchParams.set( 'redirect_uri', - `${window.location.origin}${getUrnForEntity(ApplicationRoute.AssetsToolsets, selectedToolset)}`, + `${window.location.origin}${getUrnForEntity(ApplicationRoute.AssetsToolsets, selectedToolset)}&isUser=${type === ToolsetAuthCredentialLevel.USER}`, ); - + if (authSettings.codeChallenge) { + url.searchParams.set('code_challenge', authSettings.codeChallenge); + } if (authSettings.codeChallengeMethod) { url.searchParams.set('code_challenge_method', authSettings.codeChallengeMethod); } @@ -213,9 +224,11 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) [selectedToolset, signIn], ); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const onLogout = useCallback(() => { - signOutToolset(selectedToolset, ToolsetAuthCredentialLevel.GLOBAL).then((res) => { + const level = isUserLoggedInToToolset(selectedToolset) + ? ToolsetAuthCredentialLevel.USER + : ToolsetAuthCredentialLevel.GLOBAL; + signOutToolset(selectedToolset, level).then((res) => { if (res.success) { router.push(getUrnForEntity(ApplicationRoute.AssetsToolsets, selectedToolset)); } else { @@ -225,10 +238,11 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) }, [router, selectedToolset, showNotification]); useEffect(() => { - if (oAuthCode) { - signIn(ToolsetAuthCredentialLevel.USER, oAuthCode); + if (oAuthCode && !isSignInProcessed) { + signIn(isUserLevel ? ToolsetAuthCredentialLevel.USER : ToolsetAuthCredentialLevel.GLOBAL, oAuthCode); } - }, [signIn, oAuthCode]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( <> @@ -252,8 +266,7 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) context={useToolsetFolder as () => AssetsFolderContext} childrenContainerClass="flex-row-reverse" > - {/* TODO: waiting for BE */} - {/* {isToolsetSignedIn ? ( + {isToolsetSignedIn ? ( = ({ oAuthCode, etag, originalToolset, toolsets }) iconBefore={} onClick={() => setIsModalOpen(true)} /> - )} */} + )}
@@ -290,6 +303,8 @@ const ToolsetView: FC = ({ oAuthCode, etag, originalToolset, toolsets }) jsonEditorEnabled={jsonEditorEnabled} isSkipRefresh={false} onChangeEntity={onChangeEntity} + apiKeyValue={apiKeyValue} + onChangeKeyValue={setApiKeyValue} /> )} diff --git a/apps/ai-dial-admin/src/components/EntityView/View/Content/PropertiesContent.tsx b/apps/ai-dial-admin/src/components/EntityView/View/Content/PropertiesContent.tsx index b70c32510..a103126ab 100644 --- a/apps/ai-dial-admin/src/components/EntityView/View/Content/PropertiesContent.tsx +++ b/apps/ai-dial-admin/src/components/EntityView/View/Content/PropertiesContent.tsx @@ -22,6 +22,10 @@ interface Props { assets?: DeploymentAsset[] | null; selectedEntity: BaseEntity; onChangeEntity: (entity: BaseEntity) => void; + + // asset toolset specific props + apiKeyValue?: string; + onChangeKeyValue?: (apiKeyValue: string) => void; } const PropertiesContent: FC = ({ @@ -31,6 +35,8 @@ const PropertiesContent: FC = ({ etag, assets, selectedEntity, + onChangeKeyValue, + apiKeyValue, onChangeEntity, }) => { const getPropertiesView = useCallback(() => { @@ -51,6 +57,8 @@ const PropertiesContent: FC = ({ assets={assets || []} runners={applicationSchemes || []} onChange={onChangeEntity} + apiKeyValue={apiKeyValue} + onChangeKeyValue={onChangeKeyValue} /> ); } @@ -64,7 +72,7 @@ const PropertiesContent: FC = ({ updateEntity={onChangeEntity} /> ); - }, [view, selectedEntity, applicationSchemes, names, onChangeEntity, etag, assets]); + }, [view, selectedEntity, applicationSchemes, names, onChangeEntity, etag, assets, apiKeyValue, onChangeKeyValue]); return (
diff --git a/apps/ai-dial-admin/src/components/EntityView/View/Content/ViewContent.tsx b/apps/ai-dial-admin/src/components/EntityView/View/Content/ViewContent.tsx index 4166e8f3c..8f4734d05 100644 --- a/apps/ai-dial-admin/src/components/EntityView/View/Content/ViewContent.tsx +++ b/apps/ai-dial-admin/src/components/EntityView/View/Content/ViewContent.tsx @@ -38,6 +38,10 @@ interface Props { key?: number; setIsChanged?: Dispatch>; setSelectedEntity?: Dispatch>; + + // asset toolset specific props + apiKeyValue?: string; + onChangeKeyValue?: (apiKeyValue: string) => void; } const ViewContent: FC = ({ diff --git a/apps/ai-dial-admin/src/components/Publications/Assets/Toolset/Info.tsx b/apps/ai-dial-admin/src/components/Publications/Assets/Toolset/Info.tsx index c753dda04..205a41122 100644 --- a/apps/ai-dial-admin/src/components/Publications/Assets/Toolset/Info.tsx +++ b/apps/ai-dial-admin/src/components/Publications/Assets/Toolset/Info.tsx @@ -6,6 +6,7 @@ import IconControl from '@/src/components/EntityMainProperties/BaseProperties/Ic import { DialToolsetResource } from '@/src/models/dial/application-resource'; import TopicsControl from '@/src/components/EntityMainProperties/BaseProperties/Topics'; import ToolsetEndpoint from '@/src/components/SourceField/Endpoints/ToolsetEndpoint'; +import Authentication from '@/src/components/Toolsets/View/Authentication'; interface Props { toolset: DialToolsetResource; @@ -20,6 +21,7 @@ const ToolsetInfo: FC = ({ toolset }) => { +
) : null; }; diff --git a/apps/ai-dial-admin/src/components/Toolsets/View/Auth/ApiKeySection.tsx b/apps/ai-dial-admin/src/components/Toolsets/View/Auth/ApiKeySection.tsx index 79e15adbc..585ed98c9 100644 --- a/apps/ai-dial-admin/src/components/Toolsets/View/Auth/ApiKeySection.tsx +++ b/apps/ai-dial-admin/src/components/Toolsets/View/Auth/ApiKeySection.tsx @@ -1,4 +1,4 @@ -import { DialPasswordInputField } from '@epam/ai-dial-ui-kit'; +import { DialPasswordInputField, DialTextInputField } from '@epam/ai-dial-ui-kit'; import { FC } from 'react'; import { EntityFieldsI18nKey, EntityPlaceholdersI18nKey } from '@/src/constants/i18n'; @@ -6,21 +6,33 @@ import { useI18n } from '@/src/locales/client'; import { ToolsetAuthSettings } from '@/src/models/dial/toolset'; interface Props { + apiKeyValue?: string; authSettings?: ToolsetAuthSettings; - onChange: (authSettings: ToolsetAuthSettings) => void; + disabled?: boolean; + onChange?: (authSettings: ToolsetAuthSettings) => void; + onChangeKeyValue?: (apiKeyValue: string) => void; } -const ApiKeySection: FC = ({ authSettings, onChange }) => { +const ApiKeySection: FC = ({ disabled, authSettings, onChange, onChangeKeyValue, apiKeyValue }) => { const t = useI18n(); return (
+ onChange?.({ ...(authSettings || {}), apiKeyHeader } as ToolsetAuthSettings)} + /> onChange({ ...(authSettings || {}), apiKeyHeader } as ToolsetAuthSettings)} + value={apiKeyValue} + disabled={disabled} + onChange={(v) => onChangeKeyValue?.(v || '')} />
); diff --git a/apps/ai-dial-admin/src/components/Toolsets/View/Auth/AuthTypeSection.tsx b/apps/ai-dial-admin/src/components/Toolsets/View/Auth/AuthTypeSection.tsx index cb1a56e16..7d33abf42 100644 --- a/apps/ai-dial-admin/src/components/Toolsets/View/Auth/AuthTypeSection.tsx +++ b/apps/ai-dial-admin/src/components/Toolsets/View/Auth/AuthTypeSection.tsx @@ -11,19 +11,21 @@ import OAuthSection from './OAuthSection'; enum AuthType { With_login = 'With_login', - Without_login = 'Without_login', With_config_and_login = 'With_config_and_login', } interface Props { config: AuthConfig; isSelected: boolean; - onClick: (type: ToolsetAuthType) => void; + disabled?: boolean; + onClick?: (type: ToolsetAuthType) => void; + apiKeyValue?: string; authSettings?: ToolsetAuthSettings; - onChange: (authSettings: ToolsetAuthSettings) => void; + onChange?: (authSettings: ToolsetAuthSettings) => void; + onChangeKeyValue?: (apiKeyValue: string) => void; } -const AuthTypeSection: FC = ({ config, isSelected, onClick, authSettings, onChange }) => { +const AuthTypeSection: FC = ({ disabled, config, isSelected, onClick, authSettings, onChange, ...props }) => { const t = useI18n(); const [selectedAuthType, setSelectedAuthType] = useState(AuthType.With_login); @@ -36,12 +38,7 @@ const AuthTypeSection: FC = ({ config, isSelected, onClick, authSettings, }, ]; - if (config.id === ToolsetAuthType.API_KEY) { - buttons.push({ - id: AuthType.Without_login, - name: t(ToolsetI18nKey.WithoutLogin), - }); - } else if (config.id === ToolsetAuthType.OAUTH) { + if (config.id === ToolsetAuthType.OAUTH) { buttons.push({ id: AuthType.With_config_and_login, name: t(ToolsetI18nKey.WithLoginAndConfig), @@ -52,17 +49,13 @@ const AuthTypeSection: FC = ({ config, isSelected, onClick, authSettings, }, [config.id, t]); useEffect(() => { - const value = authSettings?.clientId - ? AuthType.With_config_and_login - : config.id === ToolsetAuthType.API_KEY - ? AuthType.Without_login - : AuthType.With_login; + const value = authSettings?.clientId ? AuthType.With_config_and_login : AuthType.With_login; setSelectedAuthType(value); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleOnClick = useCallback(() => { - onClick(config.id); + onClick?.(config.id); }, [config.id, onClick]); const onChangeAuth = useCallback((value: string) => { @@ -84,19 +77,22 @@ const AuthTypeSection: FC = ({ config, isSelected, onClick, authSettings,
{isSelected && config.id !== ToolsetAuthType.NONE && (
- + {config.id === ToolsetAuthType.OAUTH && ( + + )} {selectedAuthType === AuthType.With_login && config.id === ToolsetAuthType.API_KEY && ( - + )} {selectedAuthType === AuthType.With_config_and_login && config.id === ToolsetAuthType.OAUTH && ( - + )}
)} diff --git a/apps/ai-dial-admin/src/components/Toolsets/View/Auth/OAuthSection.tsx b/apps/ai-dial-admin/src/components/Toolsets/View/Auth/OAuthSection.tsx index 9c6ce8776..561ec398a 100644 --- a/apps/ai-dial-admin/src/components/Toolsets/View/Auth/OAuthSection.tsx +++ b/apps/ai-dial-admin/src/components/Toolsets/View/Auth/OAuthSection.tsx @@ -15,16 +15,14 @@ enum AuthType { interface Props { authSettings?: ToolsetAuthSettings; - onChange: (entity: ToolsetAuthSettings) => void; + disabled?: boolean; + onChange?: (entity: ToolsetAuthSettings) => void; } -const OAuthSection: FC = ({ authSettings, onChange }) => { +const OAuthSection: FC = ({ disabled, authSettings, onChange }) => { const t = useI18n(); - const types: SelectOption[] = [ - // { value: AuthType.DYNAMIC, label: t(ToolsetI18nKey.DynamicRegistration) }, - { value: AuthType.EXISTING, label: t(ToolsetI18nKey.ExistingClient) }, - ]; + const types: SelectOption[] = [{ value: AuthType.EXISTING, label: t(ToolsetI18nKey.ExistingClient) }]; const methods: SelectOption[] = [ { value: BasicI18nKey.None, label: t(BasicI18nKey.None) }, @@ -38,77 +36,86 @@ const OAuthSection: FC = ({ authSettings, onChange }) => { { - onChange({ + onChange?.({ ...(authSettings || {}), clientId: type === AuthType.EXISTING ? '' : undefined, } as ToolsetAuthSettings); }} /> - onChange({ ...(authSettings || {}), redirectUri } as ToolsetAuthSettings)} - /> +
+ onChange?.({ ...(authSettings || {}), redirectUri } as ToolsetAuthSettings)} + /> +
{authSettings?.clientId != null && (
onChange({ ...(authSettings || {}), clientId })} + onChange={(clientId) => onChange?.({ ...(authSettings || {}), clientId })} /> onChange({ ...(authSettings || {}), clientSecret })} + onChange={(clientSecret) => onChange?.({ ...(authSettings || {}), clientSecret })} /> { - onChange({ ...(authSettings || {}), scopesSupported }); + onChange?.({ ...(authSettings || {}), scopesSupported }); }} heading={t(EntityFieldsI18nKey.scopes)} title={t(EntityFieldsI18nKey.scopes)} addTitle={t(BasicI18nKey.AddField)} /> onChange({ ...(authSettings || {}), authorizationEndpoint })} + onChange={(authorizationEndpoint) => onChange?.({ ...(authSettings || {}), authorizationEndpoint })} /> onChange({ ...(authSettings || {}), tokenEndpoint })} + onChange={(tokenEndpoint) => onChange?.({ ...(authSettings || {}), tokenEndpoint })} /> { - onChange({ + onChange?.({ ...(authSettings || {}), codeChallengeMethod: codeChallengeMethod === BasicI18nKey.None ? '' : codeChallengeMethod, } as ToolsetAuthSettings); diff --git a/apps/ai-dial-admin/src/components/Toolsets/View/Authentication.tsx b/apps/ai-dial-admin/src/components/Toolsets/View/Authentication.tsx index 3025c5d23..423abbd34 100644 --- a/apps/ai-dial-admin/src/components/Toolsets/View/Authentication.tsx +++ b/apps/ai-dial-admin/src/components/Toolsets/View/Authentication.tsx @@ -12,7 +12,10 @@ import { getUrnForEntity } from '@/src/utils/open-in-new-tab'; interface Props { toolset: Toolset; - onChange: (entity: Toolset) => void; + disabled?: boolean; + onChange?: (entity: Toolset) => void; + apiKeyValue?: string; + onChangeKeyValue?: (apiKeyValue: string) => void; } export interface AuthConfig { @@ -20,7 +23,7 @@ export interface AuthConfig { title: string; icon?: ReactNode; } -const Authentication: FC = ({ toolset, onChange }) => { +const Authentication: FC = ({ disabled, toolset, onChange, ...props }) => { const t = useI18n(); const selectedAuthType = useMemo(() => toolset.authSettings?.authenticationType || ToolsetAuthType.NONE, [toolset]); @@ -32,15 +35,14 @@ const Authentication: FC = ({ toolset, onChange }) => { const onChangeAuthType = useCallback( (authenticationType: ToolsetAuthType) => { - onChange({ + onChange?.({ ...toolset, authSettings: { - ...toolset.authSettings, authenticationType, redirectUri: authenticationType === ToolsetAuthType.OAUTH ? `${window.location.origin}${getUrnForEntity(ApplicationRoute.AssetsToolsets, toolset)}` - : '', + : void 0, }, }); }, @@ -48,18 +50,29 @@ const Authentication: FC = ({ toolset, onChange }) => { ); return ( -
+
- {authOptions.map((option) => ( + {disabled ? ( option.id === selectedAuthType)!} + isSelected={true} + disabled={true} authSettings={toolset.authSettings} - onChange={(authSettings) => onChange({ ...toolset, authSettings })} + {...props} /> - ))} + ) : ( + authOptions.map((option) => ( + onChange?.({ ...toolset, authSettings })} + {...props} + /> + )) + )}
); }; diff --git a/apps/ai-dial-admin/src/constants/i18n.ts b/apps/ai-dial-admin/src/constants/i18n.ts index 1d6e485f0..d597b7d3a 100644 --- a/apps/ai-dial-admin/src/constants/i18n.ts +++ b/apps/ai-dial-admin/src/constants/i18n.ts @@ -789,7 +789,6 @@ export enum EntityPlaceholdersI18nKey { Maintainer = 'EntityPlaceholders.Maintainer', Type = 'EntityPlaceholders.Type', Title = 'EntityPlaceholders.Title', - Header = 'EntityPlaceholders.Header', Tier = 'EntityPlaceholders.Tier', Weight = 'EntityPlaceholders.Weight', UpstreamEndpoint = 'EntityPlaceholders.UpstreamEndpoint', @@ -818,12 +817,14 @@ export enum EntityPlaceholdersI18nKey { ToolName = 'EntityPlaceholders.ToolName', Hour = 'EntityPlaceholders.Hour', Object = 'EntityPlaceholders.Object', + Header = 'EntityPlaceholders.Header', } export enum EntityFieldsI18nKey { roleLimits = 'EntityFields.roleLimits', isPublic = 'EntityFields.isPublic', apiKeyHeader = 'EntityFields.apiKeyHeader', + apiKeyValue = 'EntityFields.apiKeyValue', costLimit = 'EntityFields.costLimit', defaultRoleLimit = 'EntityFields.defaultRoleLimit', fieldsHashingOrder = 'EntityFields.fieldsHashingOrder', diff --git a/apps/ai-dial-admin/src/locales/en.ts b/apps/ai-dial-admin/src/locales/en.ts index ae3d5eef1..0da55217b 100644 --- a/apps/ai-dial-admin/src/locales/en.ts +++ b/apps/ai-dial-admin/src/locales/en.ts @@ -810,7 +810,8 @@ export default { title: 'Title', applicationTypeRoutes: 'App Routes', routes: 'App Routes', - apiKeyHeader: 'API key value', + apiKeyHeader: 'API key Header', + apiKeyValue: 'API key value', costLimit: 'Cost limits', applicationTypeCompletionEndpoint: 'Completion endpoint', completionEndpoint: 'Completion endpoint', diff --git a/apps/ai-dial-admin/src/models/dial/toolset.ts b/apps/ai-dial-admin/src/models/dial/toolset.ts index 66f95536e..ef61238c6 100644 --- a/apps/ai-dial-admin/src/models/dial/toolset.ts +++ b/apps/ai-dial-admin/src/models/dial/toolset.ts @@ -22,6 +22,7 @@ export interface ToolsetAuthSettings { clientSecret?: string; redirectUri?: string; authorizationEndpoint?: string; + codeChallenge?: string; codeChallengeMethod?: ToolsetCodeChallengeMethod; tokenEndpoint?: string; scopesSupported?: string[]; diff --git a/apps/ai-dial-admin/src/server/entities/assets-api.ts b/apps/ai-dial-admin/src/server/entities/assets-api.ts index 8fc2b68c2..bbff61f0c 100644 --- a/apps/ai-dial-admin/src/server/entities/assets-api.ts +++ b/apps/ai-dial-admin/src/server/entities/assets-api.ts @@ -221,14 +221,20 @@ export class AssetsApi extends BaseApi { return this.post(url, { path: name }, token).then((res) => (res as { tools: Tool[] })?.tools || []); } - signInToolset(toolset: AssetToolset, type: ToolsetAuthCredentialLevel, token: JWT | null, authCode?: string) { - const url = `${ResourceBasePaths[ResourceType.TOOLSET]}/signin`; + signInToolset( + toolset: AssetToolset, + type: ToolsetAuthCredentialLevel, + token: JWT | null, + apiKey?: string, + authCode?: string, + ) { + const url = `${ResourceBasePaths[ResourceType.TOOLSET]}/sign-in`; - return this.postAction(url, getToolsetSignInBody(toolset, type, authCode), token); + return this.postAction(url, getToolsetSignInBody(toolset, type, apiKey, authCode), token); } signOutToolset(toolset: AssetToolset, type: ToolsetAuthCredentialLevel, token: JWT | null) { - const url = `${ResourceBasePaths[ResourceType.TOOLSET]}/signout`; + const url = `${ResourceBasePaths[ResourceType.TOOLSET]}/sign-out`; return this.postAction(url, getToolsetBasicBody(toolset, type), token); } diff --git a/apps/ai-dial-admin/src/utils/toolset/tests/toolset-auth.spec.ts b/apps/ai-dial-admin/src/utils/toolset/tests/toolset-auth.spec.ts index f04b30fa0..e0caebda0 100644 --- a/apps/ai-dial-admin/src/utils/toolset/tests/toolset-auth.spec.ts +++ b/apps/ai-dial-admin/src/utils/toolset/tests/toolset-auth.spec.ts @@ -1,4 +1,4 @@ -import { ToolsetAuthStatus, ToolsetAuthType } from '@/src/models/dial/toolset'; +import { ToolsetAuthCredentialLevel, ToolsetAuthStatus, ToolsetAuthType } from '@/src/models/dial/toolset'; import { describe, expect, test } from 'vitest'; import { encodeToolsetRedirectState, @@ -10,28 +10,36 @@ import { describe('toolset-auth utils', () => { test('getToolsetSignInBody returns OAUTH body with code', () => { const toolset = { authSettings: { authenticationType: ToolsetAuthType.OAUTH } } as any; - const result = getToolsetSignInBody(toolset, 'admin', 'authcode'); + const result = getToolsetSignInBody(toolset, ToolsetAuthCredentialLevel.APP, void 0, 'authcode'); expect(result).toMatchObject({ - credentials_level: 'admin', + credentialsLevel: ToolsetAuthCredentialLevel.APP, authenticationType: ToolsetAuthType.OAUTH, code: 'authcode', }); }); test('getToolsetSignInBody returns APIKEY body with apiKeyHeader', () => { - const toolset = { authSettings: { authenticationType: ToolsetAuthType.API_KEY, apiKeyHeader: 'X-API-KEY' } } as any; - const result = getToolsetSignInBody(toolset, 'user'); + const toolset = { + path: 'public/toolset1', + authSettings: { authenticationType: ToolsetAuthType.API_KEY, apiKeyHeader: 'X-API-KEY' }, + } as any; + const result = getToolsetSignInBody(toolset, ToolsetAuthCredentialLevel.USER, 'value'); expect(result).toMatchObject({ - credentials_level: 'user', + url: 'toolsets/public/toolset1', + credentialsLevel: ToolsetAuthCredentialLevel.USER, authenticationType: ToolsetAuthType.API_KEY, - apiKeyHeader: 'X-API-KEY', + apiKey: 'value', }); }); test('getToolsetBasicBody returns basic body', () => { - const toolset = { authSettings: { authenticationType: ToolsetAuthType.API_KEY } } as any; - const result = getToolsetBasicBody(toolset, 'user'); - expect(result).toMatchObject({ credentials_level: 'user', authenticationType: ToolsetAuthType.API_KEY }); + const toolset = { path: 'public/toolset1', authSettings: { authenticationType: ToolsetAuthType.API_KEY } } as any; + const result = getToolsetBasicBody(toolset, ToolsetAuthCredentialLevel.USER); + expect(result).toMatchObject({ + url: 'toolsets/public/toolset1', + credentialsLevel: ToolsetAuthCredentialLevel.USER, + authenticationType: ToolsetAuthType.API_KEY, + }); }); test('encodeToolsetRedirectState encodes state to base64url', () => { diff --git a/apps/ai-dial-admin/src/utils/toolset/toolset-auth.ts b/apps/ai-dial-admin/src/utils/toolset/toolset-auth.ts index 32eac6e36..ab4343947 100644 --- a/apps/ai-dial-admin/src/utils/toolset/toolset-auth.ts +++ b/apps/ai-dial-admin/src/utils/toolset/toolset-auth.ts @@ -1,26 +1,25 @@ import { AssetToolset } from '@/src/models/dial/deployment-asset'; import { ToolsetAuthCredentialLevel, ToolsetAuthStatus, ToolsetAuthType } from '@/src/models/dial/toolset'; -export const getToolsetSignInBody = (toolset: AssetToolset, level: ToolsetAuthCredentialLevel, authCode?: string) => { +export const getToolsetSignInBody = ( + toolset: AssetToolset, + level: ToolsetAuthCredentialLevel, + apiKey?: string, + authCode?: string, +) => { const body = { ...getToolsetBasicBody(toolset, level) }; if (toolset.authSettings?.authenticationType === ToolsetAuthType.OAUTH) { - return { - ...body, - code: authCode, - }; + return { ...body, code: authCode }; } - return { - ...body, - apiKeyHeader: toolset.authSettings?.apiKeyHeader, - }; + return { ...body, apiKey }; }; export const getToolsetBasicBody = (toolset: AssetToolset, level: ToolsetAuthCredentialLevel) => { return { - // url: 'toolsets/{bucket}/{path}', // TODO: ask BE - credentials_level: level, + url: `toolsets/${toolset.path}`, + credentialsLevel: level, authenticationType: toolset.authSettings?.authenticationType, }; };