diff --git a/.changeset/ninety-keys-serve.md b/.changeset/ninety-keys-serve.md
new file mode 100644
index 0000000000..d15884b1c4
--- /dev/null
+++ b/.changeset/ninety-keys-serve.md
@@ -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.
diff --git a/.changeset/shiny-rabbits-unite.md b/.changeset/shiny-rabbits-unite.md
new file mode 100644
index 0000000000..dca9ee0dd9
--- /dev/null
+++ b/.changeset/shiny-rabbits-unite.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core': patch
+---
+
+Fix check that determines whether popup was closed or the messaging was misconfigured.
diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt
index 425e6eea6f..d545b380f5 100644
--- a/.github/styles/vocab.txt
+++ b/.github/styles/vocab.txt
@@ -130,6 +130,7 @@ middleware
minikube
Minikube
misconfiguration
+misconfigured
misgendering
mkdocs
Mkdocs
diff --git a/docs/auth/index.md b/docs/auth/index.md
index cf468c7bb7..a8ddf17c6b 100644
--- a/docs/auth/index.md
+++ b/docs/auth/index.md
@@ -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
diff --git a/docs/tutorials/quickstart-app-auth.md b/docs/tutorials/quickstart-app-auth.md
index 1c661d274c..d322e2ec3c 100644
--- a/docs/tutorials/quickstart-app-auth.md
+++ b/docs/tutorials/quickstart-app-auth.md
@@ -349,22 +349,18 @@ const app = createApp({
apis,
plugins: Object.values(plugins),
components: {
- SignInPage: props => {
- return (
-
- );
- },
+ SignInPage: props => (
+
+ ),
},
});
```
diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts
index 2e14447882..716c4f9651 100644
--- a/packages/core-api/src/lib/loginPopup.ts
+++ b/packages/core-api/src/lib/loginPopup.ts
@@ -82,7 +82,9 @@ export function showLoginPopup(options: LoginPopupOptions): Promise {
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 {
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'
}`;
diff --git a/packages/core/src/layout/SignInPage/SignInPage.tsx b/packages/core/src/layout/SignInPage/SignInPage.tsx
index 83ef8fe8a8..c3e5b75bb7 100644
--- a/packages/core/src/layout/SignInPage/SignInPage.tsx
+++ b/packages/core/src/layout/SignInPage/SignInPage.tsx
@@ -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 = ({
);
};
+
+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();
+
+ 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 (
+
+
+
+
+
+ setRetry({})}
+ >
+ Sign In
+
+ }
+ >
+ {provider.message}
+ {error && error.name !== 'PopupRejectedError' && (
+
+ {error.message}
+
+ )}
+
+
+
+
+
+ );
+};
+
+export const SignInPage = (props: Props) => {
+ if ('provider' in props) {
+ return ;
+ }
+
+ return ;
+};