diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts index 95376128a63..6a86f0d81b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts @@ -15,7 +15,10 @@ import { AxiosResponse } from 'axios'; import { Operation } from 'fast-json-patch'; import { isNil, isUndefined } from 'lodash'; import { SearchIndex } from '../enums/search.enum'; -import { CreateUser } from '../generated/api/teams/createUser'; +import { + AuthenticationMechanism, + CreateUser, +} from '../generated/api/teams/createUser'; import { JwtAuth } from '../generated/entity/teams/authN/jwtAuth'; import { User } from '../generated/entity/teams/user'; import { EntityReference } from '../generated/type/entityReference'; @@ -185,3 +188,11 @@ export const getGroupTypeTeams = async () => { return response.data; }; + +export const getAuthMechanismForBotUser = async (botId: string) => { + const response = await APIClient.get( + `/users/auth-mechanism/${botId}` + ); + + return response.data; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanism.less b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanism.less new file mode 100644 index 00000000000..af889756a73 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanism.less @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.ant-space-authMechanism { + .ant-space-item:first-child { + width: 100%; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanism.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanism.tsx new file mode 100644 index 00000000000..9fad381bfcc --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanism.tsx @@ -0,0 +1,293 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, Divider, Input, Space, Typography } from 'antd'; +import { capitalize } from 'lodash'; +import React, { FC } from 'react'; +import { AuthType } from '../../generated/api/teams/createUser'; +import { AuthenticationMechanism } from '../../generated/entity/teams/user'; +import { getTokenExpiry } from '../../utils/BotsUtils'; +import SVGIcons from '../../utils/SvgUtils'; +import CopyToClipboardButton from '../buttons/CopyToClipboardButton/CopyToClipboardButton'; +import './AuthMechanism.less'; + +interface Props { + authenticationMechanism: AuthenticationMechanism; + hasPermission: boolean; + onEdit: () => void; + onTokenRevoke: () => void; +} + +const AuthMechanism: FC = ({ + authenticationMechanism, + hasPermission, + onEdit, + onTokenRevoke, +}: Props) => { + if (authenticationMechanism.authType === AuthType.Jwt) { + const JWTToken = authenticationMechanism.config?.JWTToken; + const JWTTokenExpiresAt = + authenticationMechanism.config?.JWTTokenExpiresAt ?? 0; + + // get the token expiry date + const { tokenExpiryDate, isTokenExpired } = + getTokenExpiry(JWTTokenExpiresAt); + + return ( + <> + + + OpenMetadata JWT Token + + + {JWTToken ? ( + + ) : ( + + )} + + + + + Token you have generated that can be used to access the OpenMetadata + API. + + + {JWTToken ? ( + <> + + + + +

+ {JWTTokenExpiresAt ? ( + isTokenExpired ? ( + `Expired on ${tokenExpiryDate}.` + ) : ( + `Expires on ${tokenExpiryDate}.` + ) + ) : ( + <> + + + This token has no expiration date. + + + )} +

+ + ) : ( +
+ No token available +
+ )} + + ); + } + + if (authenticationMechanism.authType === AuthType.Sso) { + const authConfig = authenticationMechanism.config?.authConfig; + const ssoType = authenticationMechanism.config?.ssoServiceType; + + return ( + <> + + {`${capitalize(ssoType)} SSO`} + + + + + + {authConfig?.secretKey && ( + <> + SecretKey + + + + + + )} + {authConfig?.privateKey && ( + <> + PrivateKey + + + + + + )} + {authConfig?.clientSecret && ( + <> + ClientSecret + + + + + + )} + {authConfig?.audience && ( + <> + Audience + + + + + + )} + {authConfig?.clientId && ( + <> + ClientId + + + + + + )} + {authConfig?.email && ( + <> + Email + + + + + + )} + {authConfig?.orgURL && ( + <> + OrgURL + + + + + + )} + {authConfig?.scopes && ( + <> + Scopes + + + + + + )} + {authConfig?.domain && ( + <> + Domain + + + + + + )} + {authConfig?.authority && ( + <> + Authority + + + + + + )} + {authConfig?.tokenEndpoint && ( + <> + TokenEndpoint + + + + + + )} + + + ); + } + + return null; +}; + +export default AuthMechanism; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanismForm.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanismForm.tsx new file mode 100644 index 00000000000..eccff2adc72 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/AuthMechanismForm.tsx @@ -0,0 +1,542 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Button, Form, Input, Select, Space } from 'antd'; +import React, { FC, useEffect, useState } from 'react'; +import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; +import { SsoServiceType } from '../../generated/entity/teams/authN/ssoAuth'; +import { + AuthenticationMechanism, + AuthType, + JWTTokenExpiry, +} from '../../generated/entity/teams/user'; +import { Auth0SSOClientConfig } from '../../generated/security/client/auth0SSOClientConfig'; +import { AzureSSOClientConfig } from '../../generated/security/client/azureSSOClientConfig'; +import { CustomOidcSSOClientConfig } from '../../generated/security/client/customOidcSSOClientConfig'; +import { GoogleSSOClientConfig } from '../../generated/security/client/googleSSOClientConfig'; +import { OktaSSOClientConfig } from '../../generated/security/client/oktaSSOClientConfig'; +import { + getAuthMechanismTypeOptions, + getJWTTokenExpiryOptions, +} from '../../utils/BotsUtils'; +import { SSOClientConfig } from '../CreateUser/CreateUser.interface'; +import Loader from '../Loader/Loader'; + +const { Option } = Select; + +interface Props { + isUpdating: boolean; + authenticationMechanism: AuthenticationMechanism; + onSave: (updatedAuthMechanism: AuthenticationMechanism) => void; + onCancel: () => void; +} + +const AuthMechanismForm: FC = ({ + isUpdating, + onSave, + onCancel, + authenticationMechanism, +}) => { + const { authConfig } = useAuthContext(); + + const [authMechanism, setAuthMechanism] = useState( + authenticationMechanism.authType ?? AuthType.Jwt + ); + const [tokenExpiry, setTokenExpiry] = useState( + authenticationMechanism.config?.JWTTokenExpiry ?? JWTTokenExpiry.OneHour + ); + + const [ssoClientConfig, setSSOClientConfig] = useState( + (authenticationMechanism.config?.authConfig as SSOClientConfig) ?? + ({} as SSOClientConfig) + ); + + useEffect(() => { + const authType = authenticationMechanism.authType; + const authConfig = authenticationMechanism.config?.authConfig; + const JWTTokenExpiryValue = authenticationMechanism.config?.JWTTokenExpiry; + setAuthMechanism(authType ?? AuthType.Jwt); + setSSOClientConfig( + (authConfig as SSOClientConfig) ?? ({} as SSOClientConfig) + ); + setTokenExpiry(JWTTokenExpiryValue ?? JWTTokenExpiry.OneHour); + }, [authenticationMechanism]); + + /** + * Handle on change event + * @param event + */ + const handleOnChange = ( + event: React.ChangeEvent + ) => { + const value = event.target.value; + const eleName = event.target.name; + + switch (eleName) { + case 'secretKey': + case 'audience': + case 'clientId': + case 'domain': + case 'clientSecret': + case 'authority': + case 'privateKey': + case 'orgURL': + case 'tokenEndpoint': + setSSOClientConfig((previous) => ({ + ...previous, + [eleName]: value, + })); + + break; + + case 'scopes': + setSSOClientConfig((previous) => ({ + ...previous, + scopes: value ? value.split(',') : [], + })); + + break; + + case 'oktaEmail': + setSSOClientConfig((previous) => ({ + ...previous, + email: value, + })); + + break; + + default: + break; + } + }; + + const handleSave = () => { + const updatedAuthMechanism: AuthenticationMechanism = { + authType: authMechanism, + config: + authMechanism === AuthType.Jwt + ? { + JWTTokenExpiry: tokenExpiry, + } + : { + ssoServiceType: authConfig?.provider as SsoServiceType, + authConfig: { + ...ssoClientConfig, + }, + }, + }; + + onSave(updatedAuthMechanism); + }; + + const getSSOConfig = () => { + switch (authConfig?.provider) { + case SsoServiceType.Google: { + const googleConfig = ssoClientConfig as GoogleSSOClientConfig; + + return ( + <> + + + + + + + + ); + } + + case SsoServiceType.Auth0: { + const auth0Config = ssoClientConfig as Auth0SSOClientConfig; + + return ( + <> + + + + + + + + + + + ); + } + case SsoServiceType.Azure: { + const azureConfig = ssoClientConfig as AzureSSOClientConfig; + + return ( + <> + + + + + + + + + + + + + + ); + } + case SsoServiceType.Okta: { + const oktaConfig = ssoClientConfig as OktaSSOClientConfig; + + return ( + <> + + + + + + + + + + + + + + + + + ); + } + case SsoServiceType.CustomOidc: { + const customOidcConfig = ssoClientConfig as CustomOidcSSOClientConfig; + + return ( + <> + + + + + + + + + + + ); + } + + default: + return null; + } + }; + + return ( +
+ { + if (!authMechanism) { + return Promise.reject('Auth Mechanism is required'); + } + + return Promise.resolve(); + }, + }, + ]}> + + + + {authMechanism === AuthType.Jwt && ( + { + if (!tokenExpiry) { + return Promise.reject('Token Expiration is required'); + } + + return Promise.resolve(); + }, + }, + ]}> + + + )} + {authMechanism === AuthType.Sso && <>{getSSOConfig()}} + + + + +
+ ); +}; + +export default AuthMechanismForm; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.component.tsx index 68d42cde6a6..b99e83b6578 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.component.tsx @@ -12,9 +12,8 @@ */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Card, Input, Select } from 'antd'; +import { Card } from 'antd'; import { AxiosError } from 'axios'; -import classNames from 'classnames'; import React, { FC, Fragment, @@ -23,40 +22,34 @@ import React, { useMemo, useState, } from 'react'; -import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; -import { getUserToken, updateUser } from '../../axiosAPIs/userAPI'; +import { + getAuthMechanismForBotUser, + updateUser, +} from '../../axiosAPIs/userAPI'; import { GlobalSettingOptions, GlobalSettingsMenuCategory, } from '../../constants/globalSettings.constants'; import { + AuthenticationMechanism, AuthType, - JWTTokenExpiry, - SsoServiceType, User, } from '../../generated/entity/teams/user'; import { EntityReference } from '../../generated/type/entityReference'; -import { - getAuthMechanismTypeOptions, - getJWTTokenExpiryOptions, - getTokenExpiryDate, - getTokenExpiryText, -} from '../../utils/BotsUtils'; -import { getEntityName, requiredField } from '../../utils/CommonUtils'; +import { getEntityName } from '../../utils/CommonUtils'; import { getSettingPath } from '../../utils/RouterUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import { Button } from '../buttons/Button/Button'; -import CopyToClipboardButton from '../buttons/CopyToClipboardButton/CopyToClipboardButton'; import Description from '../common/description/Description'; import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.component'; import PageLayout, { leftPanelAntCardStyle } from '../containers/PageLayout'; -import { Field } from '../Field/Field'; -import Loader from '../Loader/Loader'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface'; import { UserDetails } from '../Users/Users.interface'; -const { Option } = Select; +import AuthMechanism from './AuthMechanism'; +import AuthMechanismForm from './AuthMechanismForm'; + interface BotsDetailProp extends HTMLAttributes { botsData: User; botPermission: OperationPermission; @@ -64,32 +57,25 @@ interface BotsDetailProp extends HTMLAttributes { revokeTokenHandler: () => void; } -interface Option { - value: string; - label: string; -} - const BotDetails: FC = ({ botsData, updateBotsDetails, revokeTokenHandler, botPermission, }) => { - const { authConfig } = useAuthContext(); const [displayName, setDisplayName] = useState(botsData.displayName); const [isDisplayNameEdit, setIsDisplayNameEdit] = useState(false); const [isDescriptionEdit, setIsDescriptionEdit] = useState(false); - const [botsToken, setBotsToken] = useState(''); - const [botsTokenExpiry, setBotsTokenExpiry] = useState(); const [isRevokingToken, setIsRevokingToken] = useState(false); - const [generateToken, setGenerateToken] = useState(false); - const [authMechanism, setAuthMechanism] = useState(AuthType.Jwt); - const [tokenExpiry, setTokenExpiry] = useState( - JWTTokenExpiry.OneHour - ); const [isUpdating, setIsUpdating] = useState(false); + const [authenticationMechanism, setAuthenticationMechanism] = + useState(); + + const [isAuthMechanismEdit, setIsAuthMechanismEdit] = + useState(false); + const editAllPermission = useMemo( () => botPermission.EditAll, [botPermission] @@ -104,20 +90,18 @@ const BotDetails: FC = ({ [botPermission] ); - const fetchBotsToken = () => { - getUserToken(botsData.id) - .then((res) => { - const { JWTToken, JWTTokenExpiresAt } = res; - setBotsToken(JWTToken); - setBotsTokenExpiry(JWTTokenExpiresAt); - setTokenExpiry(res.JWTTokenExpiry ?? JWTTokenExpiry.OneHour); - }) - .catch((err: AxiosError) => { - showErrorToast(err); - }); + const fetchAuthMechanismForBot = async () => { + try { + const response = await getAuthMechanismForBotUser(botsData.id); + setAuthenticationMechanism(response); + } catch (error) { + showErrorToast(error as AxiosError); + } }; - const handleBotTokenDetailUpdate = async () => { + const handleAuthMechanismUpdate = async ( + updatedAuthMechanism: AuthenticationMechanism + ) => { setIsUpdating(true); try { const { @@ -145,30 +129,30 @@ const BotDetails: FC = ({ roles, authenticationMechanism: { ...botsData.authenticationMechanism, - authType: authMechanism, + authType: updatedAuthMechanism.authType, config: - authMechanism === AuthType.Jwt + updatedAuthMechanism.authType === AuthType.Jwt ? { - JWTTokenExpiry: tokenExpiry, + JWTTokenExpiry: updatedAuthMechanism.config?.JWTTokenExpiry, } : { - ssoServiceType: SsoServiceType.Google, - authConfig: {}, + ssoServiceType: updatedAuthMechanism.config?.ssoServiceType, + authConfig: updatedAuthMechanism.config?.authConfig, }, }, } as User); if (response.data) { - fetchBotsToken(); + fetchAuthMechanismForBot(); } } catch (error) { showErrorToast(error as AxiosError); } finally { setIsUpdating(false); - setGenerateToken(false); + setIsAuthMechanismEdit(false); } }; - const handleTokenGeneration = () => setGenerateToken(true); + const handleAuthMechanismEdit = () => setIsAuthMechanismEdit(true); const onDisplayNameChange = (e: React.ChangeEvent) => { setDisplayName(e.target.value); @@ -297,207 +281,29 @@ const BotDetails: FC = ({ ); }; - const fetchRightPanel = () => { - return ( - -
-
-
Token Security
-

- Anyone who has your JWT Token will be able to send REST API - requests to the OpenMetadata Server. Do not expose the JWT Token - in your application code. Do not share it on GitHub or anywhere - else online. -

-
+ const rightPanel = ( + +
+
+
Token Security
+

+ Anyone who has your JWT Token will be able to send REST API requests + to the OpenMetadata Server. Do not expose the JWT Token in your + application code. Do not share it on GitHub or anywhere else online. +

- - ); - }; - - const getCopyComponent = () => { - if (botsToken) { - return ; - } else { - return null; - } - }; - - const getBotsTokenExpiryDate = () => { - if (botsTokenExpiry) { - // get the current date timestamp - const currentTimeStamp = Date.now(); - - const isTokenExpired = currentTimeStamp >= botsTokenExpiry; - - // get the token expiry date - const tokenExpiryDate = getTokenExpiryDate(botsTokenExpiry); - - return ( -

- {isTokenExpired - ? `Expired on ${tokenExpiryDate}` - : `Expires on ${tokenExpiryDate}`} - . -

- ); - } else { - return ( -

- - - This token has no expiration date. - -

- ); - } - }; - - const centerLayout = () => { - if (generateToken) { - return ( -
- - - - - - - -

{getTokenExpiryText(tokenExpiry)}

-
- -
- - -
-
- ); - } else { - if (botsToken) { - return ( - -
- - {getCopyComponent()} -
- {getBotsTokenExpiryDate()} -
- ); - } else { - return ( -
- No token available -
- ); - } - } - }; - - const getCenterLayout = () => { - return ( -
-
-
- {generateToken ? 'Generate JWT token' : 'JWT Token'} -
- {!generateToken && editAllPermission ? ( - <> - {botsToken ? ( - - ) : ( - - )} - - ) : null} -
-
-

- Token you have generated that can be used to access the OpenMetadata - API. -

- {centerLayout()}
- ); - }; + + ); useEffect(() => { if (botsData.id) { - fetchBotsToken(); + fetchAuthMechanismForBot(); } }, [botsData]); @@ -520,8 +326,32 @@ const BotDetails: FC = ({ /> } leftPanel={fetchLeftPanel()} - rightPanel={fetchRightPanel()}> - {getCenterLayout()} + rightPanel={rightPanel}> + {authenticationMechanism && ( + + {isAuthMechanismEdit ? ( + setIsAuthMechanismEdit(false)} + onSave={handleAuthMechanismUpdate} + /> + ) : ( + setIsRevokingToken(true)} + /> + )} + + )} + {isRevokingToken ? ( = ({ onConfirm={() => { revokeTokenHandler(); setIsRevokingToken(false); - handleTokenGeneration(); + handleAuthMechanismEdit(); }} /> ) : null} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.test.tsx index e7ca71d70f5..af1589a2199 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BotDetails/BotDetails.test.tsx @@ -11,29 +11,15 @@ * limitations under the License. */ -import { - findByTestId, - fireEvent, - queryByTestId, - render, -} from '@testing-library/react'; +import { findByTestId, fireEvent, render } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { getUserToken } from '../../axiosAPIs/userAPI'; import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface'; import BotDetails from './BotDetails.component'; const revokeTokenHandler = jest.fn(); const updateBotsDetails = jest.fn(); -const mockToken = { - JWTToken: - // eslint-disable-next-line max-len - 'eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzYWNoaW5jaGF1cmFzaXlhY2hvdGV5ODciLCJpc0JvdCI6dHJ1ZSwiaXNzIjoib3Blbi1tZXRhZGF0YS5vcmciLCJleHAiOjE2NTMzMDM5ODcsImlhdCI6MTY1MjY5OTE4NywiZW1haWwiOiJzYWNoaW5jaGF1cmFzaXlhY2hvdGV5ODdAZ21haWwuY29tIn0.qwcyGU_geL9GsZ58lw5H46eP7OY9GNq3gBS5l3DhvOGTjtqWzFBUdtYwg3KdP0ejXHSMW5DD2I-1jbCZI8tuSRZ0kdN7gt0xEhU3o7pweAcDb38mbPB3sgvNTGqrdX9Ya6ICVVDH3v7jVxJuJcykDxfVYFy6fyrwbrW3RxuyacV9xMUIyrD8EyDuAhth4wpwGnj5NqikQFRdqQYEWZlyafskMad4ghMy2eoFjrSc5vv7KN0bkp1SHGjxr_TAd3Oc9lIMWKquUZthGXQnnj5XKxGl1PJnXqK7l3U25DcCobbc5KxOI2_TUxfFNIfxduoHiWsAUBSqshvh7O7nCqiZqw', - JWTTokenExpiry: '7', - JWTTokenExpiresAt: 1653303987652, -}; - const botsData = { id: 'ea09aed1-0251-4a75-b92a-b65641610c53', name: 'sachinchaurasiyachotey87', @@ -46,23 +32,20 @@ const botsData = { href: 'http://localhost:8585/api/v1/users/ea09aed1-0251-4a75-b92a-b65641610c53', isBot: true, isAdmin: false, - changeDescription: { - fieldsAdded: [ - { - name: 'authenticationMechanism', - newValue: { - config: mockToken, - authType: 'JWT', - }, - }, - ], - fieldsUpdated: [], - fieldsDeleted: [], - previousVersion: 0.1, - }, deleted: false, }; +const mockAuthMechanism = { + config: { + JWTToken: + // eslint-disable-next-line max-len + 'eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzYWNoaW5jaGF1cmFzaXlhY2hvdGV5ODciLCJpc0JvdCI6dHJ1ZSwiaXNzIjoib3Blbi1tZXRhZGF0YS5vcmciLCJleHAiOjE2NjY3OTE5NjAsImlhdCI6MTY2NDE5OTk2MCwiZW1haWwiOiJzYWNoaW5jaGF1cmFzaXlhY2hvdGV5ODdAZ21haWwuY29tIn0.e5y5hh61EksbcWlLet_GpE84raDYvMho6OXAOLe5MCKrimHYj1roqoY54PFlJDSdrPWJOOeAFsTOxlqnMB_FGhOIufNW9yJwlkIOspWCusNJisLpv8_oYw9ZbrB5ATKyDz9MLTaZRZptx3JirA7s6tV-DJZId-mNzQejW2kiecYZeLZ-ipHqQeVxfzryfxUqcBUGTv-_de0uxlPdklqBuwt24bCy29qVIGxUweFDhrstmdRx_ZyQdrRvmeMHifUB6FCB1OBbII8mKYvF2P0CWF_SsxVLlRHUeOsxKeAeUk1MAA1mHm4UYdMD9OAuFMTZ10gpiELebVWiKrFYYjdICA', + JWTTokenExpiry: '30', + JWTTokenExpiresAt: 1666791960664, + }, + authType: 'JWT', +}; + const mockProp = { botsData, botPermission: { @@ -85,9 +68,9 @@ jest.mock('../../utils/PermissionsUtils', () => ({ jest.mock('../../axiosAPIs/userAPI', () => { return { updateUser: jest.fn().mockImplementation(() => Promise.resolve(botsData)), - getUserToken: jest + getAuthMechanismForBotUser: jest .fn() - .mockImplementation(() => Promise.resolve(mockToken)), + .mockImplementation(() => Promise.resolve(mockAuthMechanism)), }; }); @@ -125,54 +108,6 @@ describe('Test BotsDetail Component', () => { expect(tokenExpiry).toBeInTheDocument(); }); - it('Should render no token placeholder if token is not present', async () => { - (getUserToken as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ - ...mockToken, - JWTToken: '', - JWTTokenExpiresAt: '', - }) - ); - const { container } = render(, { - wrapper: MemoryRouter, - }); - - const tokenElement = queryByTestId(container, 'token'); - const tokenExpiry = queryByTestId(container, 'token-expiry'); - - expect(tokenElement).not.toBeInTheDocument(); - expect(tokenExpiry).not.toBeInTheDocument(); - - const noToken = await findByTestId(container, 'no-token'); - - expect(noToken).toBeInTheDocument(); - }); - - it('Should render generate token form if generate token button is clicked', async () => { - (getUserToken as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ ...mockToken, JWTToken: '', JWTTokenExpiresAt: '' }) - ); - const { container } = render(, { - wrapper: MemoryRouter, - }); - - const generateToken = await findByTestId(container, 'generate-token'); - - expect(generateToken).toHaveTextContent('Generate new token'); - - fireEvent.click(generateToken); - - const tokenForm = await findByTestId(container, 'generate-token-form'); - - expect(tokenForm).toBeInTheDocument(); - - const generateButton = await findByTestId(tokenForm, 'generate-button'); - const discardButton = await findByTestId(tokenForm, 'discard-button'); - - expect(generateButton).toBeInTheDocument(); - expect(discardButton).toBeInTheDocument(); - }); - it('Test Revoke token flow', async () => { const { container } = render(, { wrapper: MemoryRouter, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/BotsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/BotsUtils.ts index dc4c6e2936a..f89cb617555 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/BotsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/BotsUtils.ts @@ -100,10 +100,18 @@ export const getTokenExpiryText = (expiry: string) => { /** * * @param expiry expiry timestamp - * @returns date like "Fri 23rd September, 2022,02:26 PM." + * @returns TokenExpiry */ -export const getTokenExpiryDate = (expiry: number) => { - return moment(expiry).format('ddd Do MMMM, YYYY,hh:mm A'); +export const getTokenExpiry = (expiry: number) => { + // get the current date timestamp + const currentTimeStamp = Date.now(); + + const isTokenExpired = currentTimeStamp >= expiry; + + return { + tokenExpiryDate: moment(expiry).format('ddd Do MMMM, YYYY,hh:mm A'), + isTokenExpired, + }; }; export const DEFAULT_GOOGLE_SSO_CLIENT_CONFIG = {