feat(auth): migrate auth plugin from Material-UI to Backstage UI (#33282)
Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth': patch
|
||||
---
|
||||
|
||||
Migrated the ConsentPage UI from Material-UI and `@backstage/core-components` to `@backstage/ui`.
|
||||
@@ -47,13 +47,10 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@remixicon/react": "^4.6.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
.card {
|
||||
max-width: 600px;
|
||||
margin: var(--bui-space-8) auto 0;
|
||||
}
|
||||
|
||||
.appHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-4);
|
||||
}
|
||||
|
||||
.appIcon {
|
||||
color: var(--bui-fg-secondary);
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--bui-border-1);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.callbackUrl {
|
||||
font-family: var(--bui-font-monospace);
|
||||
background: var(--bui-bg-neutral-2);
|
||||
padding: var(--bui-space-2);
|
||||
border-radius: var(--bui-radius-2);
|
||||
word-break: break-all;
|
||||
font-size: var(--bui-font-size-3);
|
||||
margin-top: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.completedIcon {
|
||||
margin-bottom: var(--bui-space-4);
|
||||
}
|
||||
|
||||
.completedIconSuccess {
|
||||
composes: completedIcon;
|
||||
color: var(--bui-fg-success);
|
||||
}
|
||||
|
||||
.completedIconDanger {
|
||||
composes: completedIcon;
|
||||
color: var(--bui-fg-danger);
|
||||
}
|
||||
@@ -16,89 +16,37 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
Divider,
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||
import CancelIcon from '@material-ui/icons/Cancel';
|
||||
import AppsIcon from '@material-ui/icons/Apps';
|
||||
import WarningIcon from '@material-ui/icons/Warning';
|
||||
CardBody,
|
||||
CardFooter,
|
||||
Container,
|
||||
Flex,
|
||||
FullPage,
|
||||
Text,
|
||||
VisuallyHidden,
|
||||
} from '@backstage/ui';
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
Header,
|
||||
Page,
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
RiAppsLine,
|
||||
RiCheckboxCircleLine,
|
||||
RiCloseCircleLine,
|
||||
} from '@remixicon/react';
|
||||
import { useConsentSession } from './useConsentSession';
|
||||
import { configApiRef, useApi } from '@backstage/frontend-plugin-api';
|
||||
import styles from './ConsentPage.module.css';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
authCard: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
marginTop: theme.spacing(4),
|
||||
},
|
||||
appHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
appIcon: {
|
||||
marginRight: theme.spacing(2),
|
||||
fontSize: 40,
|
||||
},
|
||||
appName: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
securityWarning: {
|
||||
margin: theme.spacing(2, 0),
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: theme.spacing(2),
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
callbackUrl: {
|
||||
fontFamily: 'monospace',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing(1),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
wordBreak: 'break-all',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
scopeList: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
const ConsentPageLayout = ({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<Page themeId="tool">
|
||||
<Header title={title} />
|
||||
<Content>{children}</Content>
|
||||
</Page>
|
||||
const ConsentPageLayout = ({ children }: { children: React.ReactNode }) => (
|
||||
<FullPage>
|
||||
<VisuallyHidden>
|
||||
<h1>Authorization</h1>
|
||||
</VisuallyHidden>
|
||||
<Container py="8">{children}</Container>
|
||||
</FullPage>
|
||||
);
|
||||
|
||||
export const ConsentPage = () => {
|
||||
const classes = useStyles();
|
||||
const { sessionId } = useParams<{ sessionId: string }>();
|
||||
const { state, handleAction } = useConsentSession({ sessionId });
|
||||
const configApi = useApi(configApiRef);
|
||||
@@ -106,9 +54,10 @@ export const ConsentPage = () => {
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<ConsentPageLayout title="Authorization Error">
|
||||
<EmptyState
|
||||
missing="data"
|
||||
<ConsentPageLayout>
|
||||
<Alert
|
||||
status="info"
|
||||
icon
|
||||
title="Invalid Request"
|
||||
description="The consent request ID is missing or invalid."
|
||||
/>
|
||||
@@ -118,57 +67,62 @@ export const ConsentPage = () => {
|
||||
|
||||
if (state.status === 'loading') {
|
||||
return (
|
||||
<ConsentPageLayout title="Authorization Request">
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight={300}
|
||||
>
|
||||
<Progress />
|
||||
</Box>
|
||||
<ConsentPageLayout>
|
||||
<Alert loading title="Loading authorization request..." />
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.status === 'error') {
|
||||
return (
|
||||
<ConsentPageLayout title="Authorization Error">
|
||||
<ResponseErrorPanel error={new Error(state.error)} />
|
||||
<ConsentPageLayout>
|
||||
<Alert
|
||||
status="danger"
|
||||
icon
|
||||
title="Authorization Error"
|
||||
description={state.error}
|
||||
/>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.status === 'completed') {
|
||||
return (
|
||||
<ConsentPageLayout title="Authorization Complete">
|
||||
<Card className={classes.authCard}>
|
||||
<CardContent>
|
||||
<Box textAlign="center">
|
||||
<ConsentPageLayout>
|
||||
<Card className={styles.card}>
|
||||
<CardBody>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
gap="2"
|
||||
style={{ textAlign: 'center' }}
|
||||
>
|
||||
{state.action === 'approve' ? (
|
||||
<CheckCircleIcon
|
||||
style={{ fontSize: 64, color: 'green', marginBottom: 16 }}
|
||||
<RiCheckboxCircleLine
|
||||
size={64}
|
||||
className={styles.completedIconSuccess}
|
||||
/>
|
||||
) : (
|
||||
<CancelIcon
|
||||
style={{ fontSize: 64, color: 'red', marginBottom: 16 }}
|
||||
<RiCloseCircleLine
|
||||
size={64}
|
||||
className={styles.completedIconDanger}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="h5" gutterBottom>
|
||||
<Text as="h2" variant="title-small">
|
||||
{state.action === 'approve'
|
||||
? 'Authorization Approved'
|
||||
: 'Authorization Denied'}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" gutterBottom>
|
||||
</Text>
|
||||
<Text variant="body-medium" color="secondary">
|
||||
{state.action === 'approve'
|
||||
? `You have successfully authorized the application to access your ${appTitle} account.`
|
||||
: `You have denied the application access to your ${appTitle} account.`}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
</Text>
|
||||
<Text variant="body-small" color="secondary">
|
||||
Redirecting to the application...
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Text>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
@@ -179,70 +133,67 @@ export const ConsentPage = () => {
|
||||
const appName = session.clientName ?? session.clientId;
|
||||
|
||||
return (
|
||||
<ConsentPageLayout title="Authorization Request">
|
||||
<Card className={classes.authCard}>
|
||||
<CardContent>
|
||||
<Box className={classes.appHeader}>
|
||||
<AppsIcon className={classes.appIcon} />
|
||||
<Box>
|
||||
<Typography className={classes.appName}>{appName}</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
wants to access your {appTitle} account
|
||||
</Typography>
|
||||
<ConsentPageLayout>
|
||||
<Card className={styles.card}>
|
||||
<CardBody>
|
||||
<Flex direction="column" gap="4">
|
||||
<Box className={styles.appHeader}>
|
||||
<RiAppsLine size={40} className={styles.appIcon} />
|
||||
<Flex direction="column" gap="0.5">
|
||||
<Text as="span" variant="title-small" weight="bold">
|
||||
{appName}
|
||||
</Text>
|
||||
<Text variant="body-small" color="secondary">
|
||||
wants to access your {appTitle} account
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
<hr className={styles.divider} />
|
||||
|
||||
<Alert
|
||||
severity="warning"
|
||||
icon={<WarningIcon />}
|
||||
className={classes.securityWarning}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
<strong>Security Notice:</strong> By authorizing this application,
|
||||
you are granting it access to your {appTitle} account. The
|
||||
application will receive an access token that allows it to act on
|
||||
your behalf.
|
||||
</Typography>
|
||||
<Box mt={1}>
|
||||
<Typography variant="body2">
|
||||
<strong>Callback URL:</strong>
|
||||
</Typography>
|
||||
<Box className={classes.callbackUrl}>{session.redirectUri}</Box>
|
||||
</Box>
|
||||
</Alert>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon
|
||||
title="Security Notice"
|
||||
description={
|
||||
<>
|
||||
By authorizing this application, you are granting it access to
|
||||
your {appTitle} account. The application will receive an
|
||||
access token that allows it to act on your behalf.
|
||||
<div className={styles.callbackUrl}>
|
||||
{session.redirectUri}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
<Text variant="body-small" color="secondary">
|
||||
Make sure you trust this application and recognize the callback
|
||||
URL above. Only authorize applications you trust.
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Text>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
|
||||
<CardActions className={classes.buttonContainer}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => handleAction('reject')}
|
||||
startIcon={<CancelIcon />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => handleAction('approve')}
|
||||
startIcon={<CheckCircleIcon />}
|
||||
>
|
||||
{isSubmitting ? 'Authorizing...' : 'Authorize'}
|
||||
</Button>
|
||||
</CardActions>
|
||||
<CardFooter>
|
||||
<Flex justify="between" gap="4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
isDisabled={isSubmitting}
|
||||
onPress={() => handleAction('reject')}
|
||||
iconStart={<RiCloseCircleLine />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
isDisabled={isSubmitting}
|
||||
onPress={() => handleAction('approve')}
|
||||
iconStart={<RiCheckboxCircleLine />}
|
||||
>
|
||||
{isSubmitting ? 'Authorizing...' : 'Authorize'}
|
||||
</Button>
|
||||
</Flex>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
|
||||
@@ -4394,16 +4394,13 @@ __metadata:
|
||||
resolution: "@backstage/plugin-auth@workspace:plugins/auth"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/frontend-defaults": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:4.0.0-alpha.61"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@remixicon/react": "npm:^4.6.0"
|
||||
"@testing-library/jest-dom": "npm:^6.0.0"
|
||||
"@testing-library/react": "npm:^16.0.0"
|
||||
"@testing-library/user-event": "npm:^14.0.0"
|
||||
|
||||
Reference in New Issue
Block a user