Incorporate Cookies based Auth / Clean up (#410)

This commit is contained in:
Tan Nhu 2023-09-07 19:45:24 +00:00 committed by Harness
parent bcf47c6c60
commit fefb053d14
15 changed files with 79 additions and 105 deletions

View File

@ -9,7 +9,6 @@ import { AppContextProvider, defaultCurrentUser } from 'AppContext'
import type { AppProps } from 'AppProps'
import { buildResfulReactRequestOptions, handle401 } from 'AppUtils'
import { RouteDestinations } from 'RouteDestinations'
import { useAPIToken } from 'hooks/useAPIToken'
import { routes as _routes } from 'RouteDefinitions'
import { getConfig } from 'services/config'
import { ModalProvider } from 'hooks/useModalHook'
@ -33,10 +32,9 @@ const App: React.FC<AppProps> = React.memo(function App({
currentUserProfileURL = ''
}: AppProps) {
const [strings, setStrings] = useState<LanguageRecord>()
const [token] = useAPIToken()
const getRequestOptions = useCallback(
(): Partial<RequestInit> => buildResfulReactRequestOptions(hooks?.useGetToken?.() || token),
[token, hooks]
(): Partial<RequestInit> => buildResfulReactRequestOptions(hooks?.useGetToken?.() || ''),
[hooks]
)
const routingId = useMemo(() => (standalone ? '' : space.split('/').shift() || ''), [standalone, space])
const queryParams = useMemo(() => (!standalone ? { routingId } : {}), [standalone, routingId])

View File

@ -1,10 +1,10 @@
import React, { useState, useContext, useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { noop } from 'lodash-es'
import { useGet } from 'restful-react'
import type { AppProps } from 'AppProps'
import { routes } from 'RouteDefinitions'
import type { TypesUser } from 'services/code'
import { useAPIToken } from 'hooks/useAPIToken'
interface AppContextProps extends AppProps {
setAppContext: (value: Partial<AppProps>) => void
@ -34,10 +34,9 @@ export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(func
value: initialValue,
children
}) {
const [token, setToken] = useAPIToken()
const history = useHistory()
const { data: currentUser = defaultCurrentUser, error } = useGet({
path: '/api/v1/user',
lazy: initialValue.standalone && !token
path: '/api/v1/user'
})
const [appStates, setAppStates] = useState<AppProps>(initialValue)
@ -49,9 +48,9 @@ export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(func
useEffect(() => {
if (initialValue.standalone && error) {
setToken('')
history.push(routes.toSignIn())
}
}, [initialValue.standalone, error, setToken])
}, [initialValue.standalone, error, history])
return (
<AppContext.Provider

View File

@ -109,7 +109,7 @@ export const Changes: React.FC<ChangesProps> = ({
})
)
}
}, [commitRange])
}, [commitRange, history, routes, repoMetadata.path, pullRequestMetadata?.number])
const {
data: rawDiff,

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react'
import React, { useEffect, useMemo } from 'react'
import { Divider, PopoverInteractionKind, Position } from '@blueprintjs/core'
import { Checkbox, Container, FlexExpander, Layout, Popover, Text } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
@ -50,13 +50,13 @@ const CommitRangeDropdown: React.FC<CommitRangeDropdownProps> = ({
setSelectedCommits
}) => {
const { getString } = useStrings()
const allCommitsSHA = allCommits.map(commit => commit.sha as string)
const allCommitsSHA = useMemo(() => allCommits.map(commit => commit.sha as string), [allCommits])
useEffect(() => {
if (selectedCommits.length && allCommitsSHA.length) {
setSelectedCommits(prevVal => getCommitRange(prevVal, allCommitsSHA))
}
}, [JSON.stringify(allCommitsSHA)])
}, [allCommitsSHA, setSelectedCommits, selectedCommits.length])
const handleCheckboxClick = (
event: React.MouseEvent<HTMLInputElement | HTMLDivElement, MouseEvent>,

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { Button, ButtonVariation, Container, Dialog, FlexExpander, Layout, Text, useToaster } from '@harnessio/uicore'
import { FontVariation } from '@harnessio/design-system'
import { useMutate } from 'restful-react'
@ -24,16 +24,20 @@ const CloneCredentialDialog = (props: CloneCredentialDialogProps) => {
const { showError } = useToaster()
const hash = generateAlphaNumericHash(6)
const { mutate } = useMutate({ path: '/api/v1/user/tokens', verb: 'POST' })
const genToken = async (_props: { uid: string }) => {
const res = await mutate({ uid: _props.uid })
try {
setToken(res?.access_token)
} catch {
showError(res?.data?.message || res?.message)
}
return res
}
const genToken = useCallback(
async (_props: { uid: string }) => {
const res = await mutate({ uid: _props.uid })
try {
setToken(res?.access_token)
} catch {
showError(res?.data?.message || res?.message)
}
return res
},
[mutate, showError]
)
const tokenData = standalone ? false : hooks?.useGenerateToken?.(hash, currentUser.uid, flag)
useEffect(() => {
if (tokenData) {
if (tokenData && tokenData?.status !== 400) {
@ -44,7 +48,7 @@ const CloneCredentialDialog = (props: CloneCredentialDialogProps) => {
} else if (!tokenData && standalone && flag) {
genToken({ uid: `code_token_${hash}` })
}
}, [flag, tokenData, showError])
}, [flag, tokenData, showError]) // eslint-disable-line react-hooks/exhaustive-deps
return (
<Dialog
isOpen={flag}

View File

@ -61,7 +61,7 @@ export function MarkdownViewer({ source, className, maxHeight, darkMode }: Markd
setIsOpen(true)
}
},
[history, source]
[history]
)
return (

View File

@ -69,7 +69,7 @@ export const SpaceSelector: React.FC<SpaceSelectorProps> = ({ onSelect }) => {
selectSpace(data, false)
refetch()
}
}, [data])
}, [data, refetch, selectSpace, space])
useEffect(() => {
if (space && !selectedSpace && data) {
@ -81,7 +81,7 @@ export const SpaceSelector: React.FC<SpaceSelectorProps> = ({ onSelect }) => {
if (response?.status === 403) {
history.push(routes.toSignIn())
}
}, [response, history])
}, [response, history, routes])
useShowRequestError(error)
const NewSpaceButton = (

View File

@ -1,17 +0,0 @@
import { useLocalStorage } from 'hooks/useLocalStorage'
const API_TOKEN_KEY = 'HARNESS_CODE_MODULE_STANDALONE_APP_API_TOKEN'
/**
* Get and Set API token to use in Restful React calls.
*
* This function is called to inject token to Restful React calls only when
* application is run under standalone mode. In embedded mode, token is passed
* from parent app.
*
* @param initialToken initial API token.
* @returns [token, setToken].
*/
export function useAPIToken(initialToken = ''): [string, React.Dispatch<React.SetStateAction<string>>] {
return useLocalStorage(API_TOKEN_KEY, initialToken)
}

View File

@ -32,7 +32,7 @@ const ModalRoot: Unknown = memo(({ modals, container, component: RootComponent =
useEffect(() => {
setMountNode(container || document.body)
}, [])
}, [container])
return mountNode
? ReactDOM.createPortal(

View File

@ -130,7 +130,7 @@ const ExecutionList = () => {
disableSortBy: true
}
],
[getString]
[getString, repoMetadata?.path, routes]
)
return (

View File

@ -168,7 +168,7 @@ const PipelineList = () => {
disableSortBy: true
}
],
[getString, searchTerm]
[getString, repoMetadata?.path, routes, searchTerm]
)
return (

View File

@ -7,42 +7,44 @@ import { useStrings } from 'framework/strings'
import { useOnLogin } from 'services/code'
import AuthLayout from 'components/AuthLayout/AuthLayout'
import { useAppContext } from 'AppContext'
import { useAPIToken } from 'hooks/useAPIToken'
import { getErrorMessage, type LoginForm } from 'utils/Utils'
import css from './SignIn.module.scss'
export const SignIn: React.FC = () => {
const { routes } = useAppContext()
const { getString } = useStrings()
const [, setToken] = useAPIToken()
const { mutate } = useOnLogin({})
const { mutate } = useOnLogin({
queryParams: {
include_cookie: true
}
})
const { showError } = useToaster()
const onLogin = useCallback(
(data: LoginForm) => {
({ username, password }: LoginForm) => {
mutate(
{ login_identifier: data.username, password: data.password },
{ login_identifier: username, password },
{
headers: { Authorization: '' }
}
)
.then(result => {
setToken(result.access_token as string)
.then(() => {
window.location.replace(window.location.origin + routes.toCODEHome())
})
.catch(error => {
showError(getErrorMessage(error))
})
},
[mutate, setToken, showError]
[mutate, showError, routes]
)
const onSubmit = useCallback(
(data: LoginForm): void => {
if (data.username && data.password) {
onLogin(data)
}
},
[onLogin]
)
const handleSubmit = (data: LoginForm): void => {
if (data.username && data.password) {
onLogin(data)
}
}
return (
<AuthLayout>
<Container className={css.signInContainer}>
@ -54,10 +56,9 @@ export const SignIn: React.FC = () => {
<Formik<LoginForm>
initialValues={{ username: '', password: '' }}
formName="loginPageForm"
onSubmit={handleSubmit}
onSubmit={onSubmit}
validationSchema={Yup.object().shape({
username: Yup.string().required(getString('userNameRequired')),
password: Yup.string().required(getString('passwordRequired'))
})}>
<FormikForm>

View File

@ -18,15 +18,12 @@ import AuthLayout from 'components/AuthLayout/AuthLayout'
import { useAppContext } from 'AppContext'
import { getErrorMessage, type RegisterForm } from 'utils/Utils'
import { useOnRegister } from 'services/code'
import { useAPIToken } from 'hooks/useAPIToken'
import css from './SignUp.module.scss'
// Renders the Register page.
export const SignUp: React.FC = () => {
const { routes } = useAppContext()
const { getString } = useStrings()
const { showError, showSuccess } = useToaster()
const [, setToken] = useAPIToken()
const { mutate } = useOnRegister({})
const onRegister = useCallback(
@ -42,8 +39,7 @@ export const SignUp: React.FC = () => {
headers: { Authorization: '' }
}
)
.then(result => {
setToken(result.access_token as string)
.then(() => {
showSuccess(getString('userCreated'))
window.location.replace(window.location.origin + routes.toCODEHome())
})
@ -51,7 +47,7 @@ export const SignUp: React.FC = () => {
showError(getErrorMessage(error))
})
},
[mutate, setToken, showSuccess, showError, getString]
[mutate, showSuccess, showError, getString, routes]
)
const handleSubmit = (data: RegisterForm): void => {

View File

@ -18,19 +18,15 @@ import { useGet, useMutate } from 'restful-react'
import type { CellProps, Column } from 'react-table'
import ReactTimeago from 'react-timeago'
import moment from 'moment'
import { useStrings } from 'framework/strings'
import { useAPIToken } from 'hooks/useAPIToken'
import { TypesToken, TypesUser, useGetUser, useOpLogout, useUpdateUser } from 'services/code'
import { ButtonRoleProps, getErrorMessage } from 'utils/Utils'
import { useConfirmAct } from 'hooks/useConfirmAction'
import { useAppContext } from 'AppContext'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
import useNewToken from './NewToken/NewToken'
import EditableTextField from './EditableTextField'
import css from './UserProfile.module.scss'
const USER_TOKENS_API_PATH = '/api/v1/user/tokens'
@ -48,11 +44,8 @@ const UserProfile = () => {
const { data: userTokens, loading: tokensLoading, refetch: refetchTokens } = useGet({ path: USER_TOKENS_API_PATH })
const { mutate: deleteToken } = useMutate({ path: USER_TOKENS_API_PATH, verb: 'DELETE' })
const [, setToken] = useAPIToken()
const onLogout = async () => {
await logoutUser()
setToken('')
history.push(routes.toSignIn())
}

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useCallback, useMemo } from 'react'
import {
Avatar,
Button,
@ -40,34 +40,34 @@ const UsersListing = () => {
}
})
const { mutate: deleteUser } = useAdminDeleteUser({})
const { openModal } = useAddUserModal({ onClose: refetch })
const { openModal: openResetPasswordModal } = useResetPasswordModal()
const onConfirmAct = useConfirmAct()
const handleDeleteUser = async (userId: string, displayName?: string) =>
await onConfirmAct({
action: async () => {
try {
await deleteUser(userId)
showSuccess(getString('newUserModal.userDeleted', { name: displayName }))
refetch()
} catch (error) {
showError(getErrorMessage(error))
}
},
message: (
<Text font={{ variation: FontVariation.BODY2 }}>
<StringSubstitute
str={getString('userManagement.deleteUserMsg', { displayName, userId })}
vars={{ avatar: <Avatar name={displayName} /> }}
/>
</Text>
),
intent: 'danger',
title: getString('userManagement.deleteUser')
})
const handleDeleteUser = useCallback(
async (userId: string, displayName?: string) =>
await onConfirmAct({
action: async () => {
try {
await deleteUser(userId)
showSuccess(getString('newUserModal.userDeleted', { name: displayName }))
refetch()
} catch (error) {
showError(getErrorMessage(error))
}
},
message: (
<Text font={{ variation: FontVariation.BODY2 }}>
<StringSubstitute
str={getString('userManagement.deleteUserMsg', { displayName, userId })}
vars={{ avatar: <Avatar name={displayName} /> }}
/>
</Text>
),
intent: 'danger',
title: getString('userManagement.deleteUser')
}),
[deleteUser, getString, onConfirmAct, refetch, showError, showSuccess]
)
const columns: Column<TypesUser>[] = useMemo(
() => [
@ -178,7 +178,7 @@ const UsersListing = () => {
}
}
],
[]
[getString, handleDeleteUser, onConfirmAct, openModal, openResetPasswordModal, refetch, showError, showSuccess]
)
return (