Merge pull request #4293 from backstage/rugvip/autoauth

core: add single provider mode for SignInPage, with automatic sign-in
This commit is contained in:
Patrik Oldsberg
2021-01-29 17:41:32 +01:00
committed by GitHub
7 changed files with 127 additions and 26 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core': patch
---
Add a `prop` union for `SignInPage` that allows it to be used for just a single provider, with inline errors, and optionally with automatic sign-in.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core': patch
---
Fix check that determines whether popup was closed or the messaging was misconfigured.
+1
View File
@@ -130,6 +130,7 @@ middleware
minikube
Minikube
misconfiguration
misconfigured
misgendering
mkdocs
Mkdocs
+2 -1
View File
@@ -86,7 +86,8 @@ to a `guest` identity for all users, without any ID token. To enable sign-in, a
`SignInPage` needs to be configured, which in turn has to supply a user to the
app. The `@backstage/core` package provides a basic sign-in page that allows
both the user and the app developer to choose between a couple of different
sign-in methods.
sign-in methods, or to designate a single provider that may also be logged in to
automatically.
## Further Reading
+12 -16
View File
@@ -349,22 +349,18 @@ const app = createApp({
apis,
plugins: Object.values(plugins),
components: {
SignInPage: props => {
return (
<SignInPage
{...props}
providers={[
{
id: 'github-auth-provider',
title: 'GitHub',
message: 'Simple Backstage Application Login',
apiRef: githubAuthApiRef,
},
]}
align="center"
/>
);
},
SignInPage: props => (
<SignInPage
{...props}
auto
provider={{
id: 'github-auth-provider',
title: 'GitHub',
message: 'Simple Backstage Application Login',
apiRef: githubAuthApiRef,
}}
/>
),
},
});
```
+4 -2
View File
@@ -82,7 +82,9 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
let targetOrigin = '';
if (!popup || typeof popup.closed === 'undefined' || popup.closed) {
reject(new Error('Failed to open auth popup.'));
const error = new Error('Failed to open auth popup.');
error.name = 'PopupRejectedError';
reject(error);
return;
}
@@ -120,7 +122,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
const intervalId = setInterval(() => {
if (popup.closed) {
const errMessage = `Login failed, ${
targetOrigin !== window.location.origin
targetOrigin && targetOrigin !== window.location.origin
? `Incorrect app origin, expected ${targetOrigin}`
: 'popup was closed'
}`;
@@ -14,30 +14,38 @@
* limitations under the License.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Page } from '../Page';
import { Header } from '../Header';
import { Content } from '../Content/Content';
import { ContentHeader } from '../ContentHeader/ContentHeader';
import { Grid } from '@material-ui/core';
import { Grid, Button, Typography } from '@material-ui/core';
import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api';
import { useSignInProviders, getSignInProviders } from './providers';
import { IdentityProviders } from './types';
import { IdentityProviders, SignInConfig } from './types';
import { Progress } from '../../components/Progress';
import { useStyles } from './styles';
import { GridItem, useStyles } from './styles';
import { InfoCard } from '../InfoCard';
export type Props = SignInPageProps & {
type MultiSignInPageProps = SignInPageProps & {
providers: IdentityProviders;
title?: string;
align?: 'center' | 'left';
};
export const SignInPage = ({
type SingleSignInPageProps = SignInPageProps & {
provider: SignInConfig;
auto?: boolean;
};
export type Props = MultiSignInPageProps | SingleSignInPageProps;
export const MultiSignInPage = ({
onResult,
providers = [],
title,
align = 'left',
}: Props) => {
}: MultiSignInPageProps) => {
const configApi = useApi(configApiRef);
const classes = useStyles();
@@ -69,3 +77,86 @@ export const SignInPage = ({
</Page>
);
};
export const SingleSignInPage = ({
onResult,
provider,
auto,
}: SingleSignInPageProps) => {
const classes = useStyles();
const authApi = useApi(provider.apiRef);
const configApi = useApi(configApiRef);
const [retry, setRetry] = useState<{} | boolean | undefined>(auto);
const [error, setError] = useState<Error>();
useEffect(() => {
const login = async () => {
const identity = await authApi.getBackstageIdentity({
instantPopup: true,
});
const profile = await authApi.getProfile();
onResult({
userId: identity!.id,
profile: profile!,
getIdToken: () => {
return authApi.getBackstageIdentity().then(i => i!.idToken);
},
signOut: async () => {
await authApi.signOut();
},
});
};
if (retry) {
login().catch(setError);
}
}, [onResult, authApi, retry]);
return (
<Page themeId="home">
<Header title={configApi.getString('app.title')} />
<Content>
<Grid
container
justify="center"
spacing={2}
component="ul"
classes={classes}
>
<GridItem>
<InfoCard
variant="fullHeight"
title={provider.title}
actions={
<Button
color="primary"
variant="outlined"
onClick={() => setRetry({})}
>
Sign In
</Button>
}
>
<Typography variant="body1">{provider.message}</Typography>
{error && error.name !== 'PopupRejectedError' && (
<Typography variant="body1" color="error">
{error.message}
</Typography>
)}
</InfoCard>
</GridItem>
</Grid>
</Content>
</Page>
);
};
export const SignInPage = (props: Props) => {
if ('provider' in props) {
return <SingleSignInPage {...props} />;
}
return <MultiSignInPage {...props} />;
};