diff --git a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx index 77fd1536b2..c2b717834c 100644 --- a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx +++ b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx @@ -1,5 +1,6 @@ import { render } from 'utils/testRenderer'; import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList'; describe('ProjectsList', () => { @@ -55,4 +56,58 @@ describe('ProjectsList', () => { expect(container.textContent).toEqual('*'); }); + + describe('orphaned tokens', () => { + it('should show warning icon if it is an orphaned token', async () => { + render( + , + ); + + const errorIcon = await screen.findByTestId('ErrorIcon'); + expect(errorIcon).toBeInTheDocument(); + }); + + it('should show tooltip with warning message if it is an orphaned token', async () => { + const user = userEvent.setup(); + render( + , + ); + + const errorIcon = await screen.findByTestId('ErrorIcon'); + user.hover(errorIcon); + + const tooltip = await screen.findByRole('tooltip'); + expect(tooltip).toHaveTextContent(/orphaned token/); + }); + + it('should not show warning icon if token is in v1 format', async () => { + render( + , + ); + + const errorIcon = await screen.queryByTestId('ErrorIcon'); + expect(errorIcon).toBeNull(); + }); + + it('should not show warning for wildcard tokens', async () => { + render( + , + ); + + const errorIcon = await screen.queryByTestId('ErrorIcon'); + expect(errorIcon).toBeNull(); + }); + }); }); diff --git a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx index 905fc2b9f0..4be5daae7c 100644 --- a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx +++ b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx @@ -1,11 +1,13 @@ +import { Fragment, type FC } from 'react'; import { styled } from '@mui/material'; import { Highlighter } from 'component/common/Highlighter/Highlighter'; import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; -import { Fragment, type FC } from 'react'; +import ErrorIcon from '@mui/icons-material/Error'; import { Link } from 'react-router-dom'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; const StyledLink = styled(Link)(({ theme }) => ({ textDecoration: 'none', @@ -15,12 +17,28 @@ const StyledLink = styled(Link)(({ theme }) => ({ }, })); +const StyledErrorIcon = styled(ErrorIcon)(({ theme }) => ({ + color: theme.palette.error.main, + marginBottom: theme.spacing(0.5), + marginLeft: theme.spacing(0.5), +})); + +const StyledContainer = styled('div')({ + display: 'flex', + alignItems: 'center', +}); + interface IProjectsListProps { project?: string; projects?: string | string[]; + secret?: string; } -export const ProjectsList: FC = ({ projects, project }) => { +export const ProjectsList: FC = ({ + projects, + project, + secret, +}) => { const { searchQuery } = useSearchHighlightContext(); const projectsList = @@ -67,16 +85,36 @@ export const ProjectsList: FC = ({ projects, project }) => { return ; } + const tokenFormat = secret?.includes(':') ? 'v2' : 'v1'; // see https://docs.getunleash.io/reference/api-tokens-and-client-keys#format + const isWildcardToken = secret?.startsWith('*:'); + const isOrphanedToken = tokenFormat === 'v2' && !isWildcardToken; + return ( + This is an orphaned token. All of its original + projects have been deleted and it now has access to + all current and future projects. You should stop + using this token and delete it. It will lose access + to all projects at a later date. + + ) : ( + 'ALL current and future projects.' + ) + } placement='bottom' arrow > - + * - + } + /> + ); diff --git a/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx b/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx index 54c09eac94..4012414daf 100644 --- a/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx +++ b/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx @@ -61,6 +61,7 @@ export const useApiTokenTable = ( ), width: 160,