From 25a869f57981800a4571cf0541503a0450c2abd2 Mon Sep 17 00:00:00 2001 From: Kinga Sieminiak Date: Tue, 4 Aug 2020 09:03:34 +0200 Subject: [PATCH 1/4] Allow passing configurable list of identity providers to SignInPage component --- packages/app/src/App.tsx | 9 +- packages/app/src/authProviders.ts | 64 +++++++++++++ .../core/src/layout/SignInPage/SignInPage.tsx | 5 +- .../src/layout/SignInPage/commonProvider.tsx | 94 +++++++++++++++++++ packages/core/src/layout/SignInPage/index.ts | 3 + .../core/src/layout/SignInPage/providers.tsx | 58 +++++------- packages/core/src/layout/SignInPage/types.ts | 38 +++++++- 7 files changed, 225 insertions(+), 46 deletions(-) create mode 100644 packages/app/src/authProviders.ts create mode 100644 packages/core/src/layout/SignInPage/commonProvider.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 9e9ca1edd0..d4d0cc0490 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -19,25 +19,20 @@ import { AlertDisplay, OAuthRequestDialog, SignInPage, - useApi, - configApiRef, } from '@backstage/core'; import React, { FC } from 'react'; import Root from './components/Root'; import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; +import { providers } from './authProviders'; const app = createApp({ apis, plugins: Object.values(plugins), components: { SignInPage: props => { - const configApi = useApi(configApiRef); - const providersConfig = configApi.getOptionalConfig('auth.providers'); - const providers = providersConfig?.keys() ?? []; - - return ; + return ; }, }, }); diff --git a/packages/app/src/authProviders.ts b/packages/app/src/authProviders.ts new file mode 100644 index 0000000000..d95848e014 --- /dev/null +++ b/packages/app/src/authProviders.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + googleAuthApiRef, + gitlabAuthApiRef, + oktaAuthApiRef, + githubAuthApiRef, + commonProvider, + customProvider, + guestProvider, +} from '@backstage/core'; + +export const providers = [ + { + id: 'google-auth-provider', + title: 'Google', + message: 'Sign In using Google', + apiRef: googleAuthApiRef, + provider: commonProvider, + }, + { + id: 'gitlab-auth-provider', + title: 'Gitlab', + message: 'Sign In using Gitlab', + apiRef: gitlabAuthApiRef, + provider: commonProvider, + }, + { + id: 'github-auth-provider', + title: 'Github', + message: 'Sign In using Github', + apiRef: githubAuthApiRef, + provider: commonProvider, + }, + { + id: 'okta-auth-provider', + title: 'Okta', + message: 'Sign In using Okta', + apiRef: oktaAuthApiRef, + provider: commonProvider, + }, + { + id: 'guest-provider', + provider: guestProvider, + }, + { + id: 'custom-provider', + provider: customProvider, + }, +]; diff --git a/packages/core/src/layout/SignInPage/SignInPage.tsx b/packages/core/src/layout/SignInPage/SignInPage.tsx index 8a1191f442..e07e6490ae 100644 --- a/packages/core/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core/src/layout/SignInPage/SignInPage.tsx @@ -21,11 +21,12 @@ import { Content } from '../Content/Content'; import { ContentHeader } from '../ContentHeader/ContentHeader'; import { Grid } from '@material-ui/core'; import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api'; -import { useSignInProviders, SignInProviderId } from './providers'; +import { useSignInProviders } from './providers'; +import { SignInProviderConfig } from './types'; import { Progress } from '../../components/Progress'; export type Props = SignInPageProps & { - providers: SignInProviderId[]; + providers: SignInProviderConfig[]; }; export const SignInPage: FC = ({ onResult, providers }) => { diff --git a/packages/core/src/layout/SignInPage/commonProvider.tsx b/packages/core/src/layout/SignInPage/commonProvider.tsx new file mode 100644 index 0000000000..da0247fd6f --- /dev/null +++ b/packages/core/src/layout/SignInPage/commonProvider.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Grid, Typography, Button } from '@material-ui/core'; +import { InfoCard } from '../InfoCard/InfoCard'; +import { + ProviderComponent, + ProviderLoader, + SignInProvider, + CommonSignInConfig, +} from './types'; +import { useApi, errorApiRef } from '@backstage/core-api'; + +const Component: ProviderComponent = ({ provider, onResult }) => { + const { apiRef, title, message } = provider as CommonSignInConfig; + const authApi = useApi(apiRef); + const errorApi = useApi(errorApiRef); + + const handleLogin = async () => { + try { + 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); + }, + logout: async () => { + await authApi.logout(); + }, + }); + } catch (error) { + errorApi.post(error); + } + }; + + return ( + + + Sign In + + } + > + {message} + + + ); +}; + +const loader: ProviderLoader = async (apis, apiRef) => { + const authApi = apis.get(apiRef)!; + + const identity = await authApi.getBackstageIdentity({ + optional: true, + }); + + if (!identity) { + return undefined; + } + + const profile = await authApi.getProfile(); + + return { + userId: identity.id, + profile: profile!, + getIdToken: () => authApi.getBackstageIdentity().then(i => i!.idToken), + logout: async () => { + await authApi.logout(); + }, + }; +}; + +export const commonProvider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/index.ts b/packages/core/src/layout/SignInPage/index.ts index 49f55aefc5..e58d6a85df 100644 --- a/packages/core/src/layout/SignInPage/index.ts +++ b/packages/core/src/layout/SignInPage/index.ts @@ -15,3 +15,6 @@ */ export { SignInPage } from './SignInPage'; +export { commonProvider } from './commonProvider'; +export { guestProvider } from './guestProvider'; +export { customProvider } from './customProvider'; diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx index 1eab51e3ee..9ab742b516 100644 --- a/packages/core/src/layout/SignInPage/providers.tsx +++ b/packages/core/src/layout/SignInPage/providers.tsx @@ -15,12 +15,6 @@ */ import React, { useLayoutEffect, useState, useMemo, useCallback } from 'react'; -import { guestProvider } from './guestProvider'; -import { googleProvider } from './googleProvider'; -import { customProvider } from './customProvider'; -import { gitlabProvider } from './gitlabProvider'; -import { oktaProvider } from './oktaProvider'; -import { githubProvider } from './githubProvider'; import { SignInPageProps, SignInResult, @@ -28,24 +22,12 @@ import { useApiHolder, errorApiRef, } from '@backstage/core-api'; -import { SignInProvider } from './types'; +import { SignInProviderConfig, CommonSignInConfig } from './types'; const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider'; -// Separate list here to avoid exporting internal types -export type SignInProviderId = 'guest' | string; - -const signInProviders: { [id in SignInProviderId]: SignInProvider } = { - guest: guestProvider, - google: googleProvider, - gitlab: gitlabProvider, - oauth2: customProvider, - okta: oktaProvider, - github: githubProvider, -}; - export const useSignInProviders = ( - providers: SignInProviderId[], + providers: SignInProviderConfig[], onResult: SignInPageProps['onResult'], ) => { const errorApi = useApi(errorApiRef); @@ -74,25 +56,29 @@ export const useSignInProviders = ( } // We can't use storageApi here, as it might have a dependency on the IdentityApi - const selectedProvider = localStorage.getItem( + const selectedProviderId = localStorage.getItem( PROVIDER_STORAGE_KEY, - ) as SignInProviderId; + ) as string; // No provider selected, let the user pick one - if (selectedProvider === null) { + if (selectedProviderId === null) { setLoading(false); return undefined; } - const provider = signInProviders[selectedProvider]; - if (!provider) { + const config = providers.find( + provider => provider.id === selectedProviderId, + ); + if (!config) { setLoading(false); return undefined; } let didCancel = false; - provider - .loader(apiHolder) + const { apiRef } = config as CommonSignInConfig; + + config.provider + .loader(apiHolder, apiRef) .then(result => { if (didCancel) { return; @@ -119,20 +105,22 @@ export const useSignInProviders = ( // This renders all available sign-in providers const elements = useMemo( () => - providers.map(providerId => { - const provider = signInProviders[providerId]; - if (!provider) { - throw new Error(`Unknown sign-in provider: ${providerId}`); - } - const { Component } = provider; + providers.map(config => { + const { Component } = config.provider; const handleResult = (result: SignInResult) => { - localStorage.setItem(PROVIDER_STORAGE_KEY, providerId); + localStorage.setItem(PROVIDER_STORAGE_KEY, config.id); handleWrappedResult(result); }; - return ; + return ( + + ); }), [providers, handleWrappedResult], ); diff --git a/packages/core/src/layout/SignInPage/types.ts b/packages/core/src/layout/SignInPage/types.ts index e13cda5ddd..089ebac823 100644 --- a/packages/core/src/layout/SignInPage/types.ts +++ b/packages/core/src/layout/SignInPage/types.ts @@ -15,12 +15,46 @@ */ import { ComponentType } from 'react'; -import { SignInPageProps, SignInResult, ApiHolder } from '@backstage/core-api'; +import { + SignInPageProps, + SignInResult, + ApiHolder, + ApiRef, + OAuthApi, + ProfileInfoApi, + BackstageIdentityApi, + SessionStateApi, + OpenIdConnectApi, +} from '@backstage/core-api'; -export type ProviderComponent = ComponentType; +export type SignInConfig = { + id: string; + provider: SignInProvider; +}; + +export type CommonSignInConfig = SignInConfig & { + title: string; + message: string; + apiRef: ApiRef< + OAuthApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionStateApi & + OpenIdConnectApi + >; +}; + +export type SignInProviderConfig = SignInConfig | CommonSignInConfig; + +export type ProviderComponent = ComponentType< + SignInPageProps & { provider: SignInProviderConfig } +>; export type ProviderLoader = ( apis: ApiHolder, + apiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi + >, ) => Promise; export type SignInProvider = { From 5d24ef4c689a936ddf33075494ef49c6cd0f5660 Mon Sep 17 00:00:00 2001 From: Kinga Sieminiak Date: Thu, 6 Aug 2020 09:41:20 +0200 Subject: [PATCH 2/4] Allow passing identity providers as a union type --- packages/app/src/App.tsx | 6 +- ...{authProviders.ts => identityProviders.ts} | 15 ---- .../core/src/layout/SignInPage/SignInPage.tsx | 14 ++-- .../src/layout/SignInPage/commonProvider.tsx | 6 +- packages/core/src/layout/SignInPage/index.ts | 3 - .../core/src/layout/SignInPage/providers.tsx | 70 +++++++++++++++---- packages/core/src/layout/SignInPage/types.ts | 15 +--- 7 files changed, 74 insertions(+), 55 deletions(-) rename packages/app/src/{authProviders.ts => identityProviders.ts} (80%) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index d4d0cc0490..9a9fdfe5c4 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -25,14 +25,16 @@ import Root from './components/Root'; import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; -import { providers } from './authProviders'; +import { providers } from './identityProviders'; const app = createApp({ apis, plugins: Object.values(plugins), components: { SignInPage: props => { - return ; + return ( + + ); }, }, }); diff --git a/packages/app/src/authProviders.ts b/packages/app/src/identityProviders.ts similarity index 80% rename from packages/app/src/authProviders.ts rename to packages/app/src/identityProviders.ts index d95848e014..e80b1d6fea 100644 --- a/packages/app/src/authProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -19,9 +19,6 @@ import { gitlabAuthApiRef, oktaAuthApiRef, githubAuthApiRef, - commonProvider, - customProvider, - guestProvider, } from '@backstage/core'; export const providers = [ @@ -30,35 +27,23 @@ export const providers = [ title: 'Google', message: 'Sign In using Google', apiRef: googleAuthApiRef, - provider: commonProvider, }, { id: 'gitlab-auth-provider', title: 'Gitlab', message: 'Sign In using Gitlab', apiRef: gitlabAuthApiRef, - provider: commonProvider, }, { id: 'github-auth-provider', title: 'Github', message: 'Sign In using Github', apiRef: githubAuthApiRef, - provider: commonProvider, }, { id: 'okta-auth-provider', title: 'Okta', message: 'Sign In using Okta', apiRef: oktaAuthApiRef, - provider: commonProvider, - }, - { - id: 'guest-provider', - provider: guestProvider, - }, - { - id: 'custom-provider', - provider: customProvider, }, ]; diff --git a/packages/core/src/layout/SignInPage/SignInPage.tsx b/packages/core/src/layout/SignInPage/SignInPage.tsx index e07e6490ae..52a27f190e 100644 --- a/packages/core/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core/src/layout/SignInPage/SignInPage.tsx @@ -21,18 +21,22 @@ import { Content } from '../Content/Content'; import { ContentHeader } from '../ContentHeader/ContentHeader'; import { Grid } from '@material-ui/core'; import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api'; -import { useSignInProviders } from './providers'; -import { SignInProviderConfig } from './types'; +import { useSignInProviders, getSignInProviders } from './providers'; +import { IdentityProviders } from './types'; import { Progress } from '../../components/Progress'; export type Props = SignInPageProps & { - providers: SignInProviderConfig[]; + providers: IdentityProviders; }; -export const SignInPage: FC = ({ onResult, providers }) => { +export const SignInPage: FC = ({ onResult, providers = [] }) => { const configApi = useApi(configApiRef); - const [loading, providerElements] = useSignInProviders(providers, onResult); + const signInProviders = getSignInProviders(providers); + const [loading, providerElements] = useSignInProviders( + signInProviders, + onResult, + ); if (loading) { return ; diff --git a/packages/core/src/layout/SignInPage/commonProvider.tsx b/packages/core/src/layout/SignInPage/commonProvider.tsx index da0247fd6f..f9611342a2 100644 --- a/packages/core/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core/src/layout/SignInPage/commonProvider.tsx @@ -21,12 +21,12 @@ import { ProviderComponent, ProviderLoader, SignInProvider, - CommonSignInConfig, + SignInConfig, } from './types'; import { useApi, errorApiRef } from '@backstage/core-api'; -const Component: ProviderComponent = ({ provider, onResult }) => { - const { apiRef, title, message } = provider as CommonSignInConfig; +const Component: ProviderComponent = ({ config, onResult }) => { + const { apiRef, title, message } = config as SignInConfig; const authApi = useApi(apiRef); const errorApi = useApi(errorApiRef); diff --git a/packages/core/src/layout/SignInPage/index.ts b/packages/core/src/layout/SignInPage/index.ts index e58d6a85df..49f55aefc5 100644 --- a/packages/core/src/layout/SignInPage/index.ts +++ b/packages/core/src/layout/SignInPage/index.ts @@ -15,6 +15,3 @@ */ export { SignInPage } from './SignInPage'; -export { commonProvider } from './commonProvider'; -export { guestProvider } from './guestProvider'; -export { customProvider } from './customProvider'; diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx index 9ab742b516..a0e1941d90 100644 --- a/packages/core/src/layout/SignInPage/providers.tsx +++ b/packages/core/src/layout/SignInPage/providers.tsx @@ -22,12 +22,53 @@ import { useApiHolder, errorApiRef, } from '@backstage/core-api'; -import { SignInProviderConfig, CommonSignInConfig } from './types'; +import { SignInConfig, IdentityProviders, SignInProvider } from './types'; +import { commonProvider } from './commonProvider'; +import { guestProvider } from './guestProvider'; +import { customProvider } from './customProvider'; const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider'; +export type SignInProviderId = 'guest' | 'custom' | string; + +export type SignInProviderType = { + [id in SignInProviderId]: { + components: SignInProvider; + id: SignInProviderId; + config?: SignInConfig; + }; +}; + +const signInProviders: { [id in SignInProviderId]: SignInProvider } = { + guest: guestProvider, + custom: customProvider, + common: commonProvider, +}; + +export function getSignInProviders( + identityProviders: IdentityProviders, +): SignInProviderType { + const providers = identityProviders.reduce( + (acc: SignInProviderType, config) => { + if (typeof config === 'string') { + acc[config] = { components: signInProviders[config], id: config }; + + return acc; + } + + const { id } = config as SignInConfig; + acc[id] = { components: signInProviders.common, id, config }; + + return acc; + }, + {}, + ); + + return providers; +} + export const useSignInProviders = ( - providers: SignInProviderConfig[], + providers: SignInProviderType, onResult: SignInPageProps['onResult'], ) => { const errorApi = useApi(errorApiRef); @@ -58,7 +99,7 @@ export const useSignInProviders = ( // We can't use storageApi here, as it might have a dependency on the IdentityApi const selectedProviderId = localStorage.getItem( PROVIDER_STORAGE_KEY, - ) as string; + ) as SignInProviderId; // No provider selected, let the user pick one if (selectedProviderId === null) { @@ -66,19 +107,16 @@ export const useSignInProviders = ( return undefined; } - const config = providers.find( - provider => provider.id === selectedProviderId, - ); - if (!config) { + const provider = providers[selectedProviderId]; + if (!provider) { setLoading(false); return undefined; } let didCancel = false; - const { apiRef } = config as CommonSignInConfig; - config.provider - .loader(apiHolder, apiRef) + provider.components + .loader(apiHolder, provider.config?.apiRef!) .then(result => { if (didCancel) { return; @@ -105,19 +143,21 @@ export const useSignInProviders = ( // This renders all available sign-in providers const elements = useMemo( () => - providers.map(config => { - const { Component } = config.provider; + Object.keys(providers).map(key => { + const provider = providers[key]; + + const { Component } = provider.components; const handleResult = (result: SignInResult) => { - localStorage.setItem(PROVIDER_STORAGE_KEY, config.id); + localStorage.setItem(PROVIDER_STORAGE_KEY, provider.id); handleWrappedResult(result); }; return ( ); diff --git a/packages/core/src/layout/SignInPage/types.ts b/packages/core/src/layout/SignInPage/types.ts index 089ebac823..9c501e8095 100644 --- a/packages/core/src/layout/SignInPage/types.ts +++ b/packages/core/src/layout/SignInPage/types.ts @@ -24,30 +24,21 @@ import { ProfileInfoApi, BackstageIdentityApi, SessionStateApi, - OpenIdConnectApi, } from '@backstage/core-api'; export type SignInConfig = { id: string; - provider: SignInProvider; -}; - -export type CommonSignInConfig = SignInConfig & { title: string; message: string; apiRef: ApiRef< - OAuthApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionStateApi & - OpenIdConnectApi + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi >; }; -export type SignInProviderConfig = SignInConfig | CommonSignInConfig; +export type IdentityProviders = ('guest' | 'custom' | SignInConfig)[]; export type ProviderComponent = ComponentType< - SignInPageProps & { provider: SignInProviderConfig } + SignInPageProps & { config: SignInConfig } >; export type ProviderLoader = ( From 262624bac6c6dd18ab772c3d4b4fc9c575af2ff1 Mon Sep 17 00:00:00 2001 From: Kinga Sieminiak Date: Thu, 6 Aug 2020 09:43:04 +0200 Subject: [PATCH 3/4] Remove unused components that have been merged a 'commonProvider' --- .../src/layout/SignInPage/githubProvider.tsx | 89 ------------------ .../src/layout/SignInPage/gitlabProvider.tsx | 88 ------------------ .../src/layout/SignInPage/googleProvider.tsx | 89 ------------------ .../src/layout/SignInPage/oktaProvider.tsx | 93 ------------------- 4 files changed, 359 deletions(-) delete mode 100644 packages/core/src/layout/SignInPage/githubProvider.tsx delete mode 100644 packages/core/src/layout/SignInPage/gitlabProvider.tsx delete mode 100644 packages/core/src/layout/SignInPage/googleProvider.tsx delete mode 100644 packages/core/src/layout/SignInPage/oktaProvider.tsx diff --git a/packages/core/src/layout/SignInPage/githubProvider.tsx b/packages/core/src/layout/SignInPage/githubProvider.tsx deleted file mode 100644 index fd7815e7b8..0000000000 --- a/packages/core/src/layout/SignInPage/githubProvider.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { Grid, Typography, Button } from '@material-ui/core'; -import { InfoCard } from '../InfoCard/InfoCard'; -import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; -import { useApi, githubAuthApiRef, errorApiRef } from '@backstage/core-api'; - -const Component: ProviderComponent = ({ onResult }) => { - const githubAuthApi = useApi(githubAuthApiRef); - const errorApi = useApi(errorApiRef); - - const handleLogin = async () => { - try { - const identity = await githubAuthApi.getBackstageIdentity({ - instantPopup: true, - }); - - const profile = await githubAuthApi.getProfile(); - onResult({ - userId: identity!.id, - profile: profile!, - getIdToken: () => { - return githubAuthApi.getBackstageIdentity().then(i => i!.idToken); - }, - logout: async () => { - await githubAuthApi.logout(); - }, - }); - } catch (error) { - errorApi.post(error); - } - }; - - return ( - - - Sign In - - } - > - Sign In using Github - - - ); -}; - -const loader: ProviderLoader = async apis => { - const githubAuthApi = apis.get(githubAuthApiRef)!; - - const identity = await githubAuthApi.getBackstageIdentity({ - optional: true, - }); - - if (!identity) { - return undefined; - } - - const profile = await githubAuthApi.getProfile(); - - return { - userId: identity.id, - profile: profile!, - getIdToken: () => - githubAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await githubAuthApi.logout(); - }, - }; -}; - -export const githubProvider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/gitlabProvider.tsx b/packages/core/src/layout/SignInPage/gitlabProvider.tsx deleted file mode 100644 index 5e417cb32d..0000000000 --- a/packages/core/src/layout/SignInPage/gitlabProvider.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { Grid, Typography, Button } from '@material-ui/core'; -import { InfoCard } from '../InfoCard/InfoCard'; -import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; -import { useApi, gitlabAuthApiRef, errorApiRef } from '@backstage/core-api'; - -const Component: ProviderComponent = ({ onResult }) => { - const gitlabAuthApi = useApi(gitlabAuthApiRef); - const errorApi = useApi(errorApiRef); - - const handleLogin = async () => { - try { - const identity = await gitlabAuthApi.getBackstageIdentity({ - instantPopup: true, - }); - - const profile = await gitlabAuthApi.getProfile(); - onResult({ - userId: identity!.id, - profile: profile!, - getIdToken: () => - gitlabAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await gitlabAuthApi.logout(); - }, - }); - } catch (error) { - errorApi.post(error); - } - }; - - return ( - - - Sign In - - } - > - Sign In using Gitlab - - - ); -}; - -const loader: ProviderLoader = async apis => { - const gitlabAuthApi = apis.get(gitlabAuthApiRef)!; - - const identity = await gitlabAuthApi.getBackstageIdentity({ - optional: true, - }); - - if (!identity) { - return undefined; - } - - const profile = await gitlabAuthApi.getProfile(); - - return { - userId: identity.id, - profile: profile!, - getIdToken: () => - gitlabAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await gitlabAuthApi.logout(); - }, - }; -}; - -export const gitlabProvider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/googleProvider.tsx b/packages/core/src/layout/SignInPage/googleProvider.tsx deleted file mode 100644 index 632b81ad17..0000000000 --- a/packages/core/src/layout/SignInPage/googleProvider.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { Grid, Typography, Button } from '@material-ui/core'; -import { InfoCard } from '../InfoCard/InfoCard'; -import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; -import { useApi, googleAuthApiRef, errorApiRef } from '@backstage/core-api'; - -const Component: ProviderComponent = ({ onResult }) => { - const googleAuthApi = useApi(googleAuthApiRef); - const errorApi = useApi(errorApiRef); - - const handleLogin = async () => { - try { - const identity = await googleAuthApi.getBackstageIdentity({ - instantPopup: true, - }); - - const profile = await googleAuthApi.getProfile(); - - onResult({ - userId: identity!.id, - profile: profile!, - getIdToken: () => - googleAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await googleAuthApi.logout(); - }, - }); - } catch (error) { - errorApi.post(error); - } - }; - - return ( - - - Sign In - - } - > - Sign In using Google - - - ); -}; - -const loader: ProviderLoader = async apis => { - const googleAuthApi = apis.get(googleAuthApiRef)!; - - const identity = await googleAuthApi.getBackstageIdentity({ - optional: true, - }); - - if (!identity) { - return undefined; - } - - const profile = await googleAuthApi.getProfile(); - - return { - userId: identity.id, - profile: profile!, - getIdToken: () => - googleAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await googleAuthApi.logout(); - }, - }; -}; - -export const googleProvider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/oktaProvider.tsx b/packages/core/src/layout/SignInPage/oktaProvider.tsx deleted file mode 100644 index cf15fe5b22..0000000000 --- a/packages/core/src/layout/SignInPage/oktaProvider.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { Grid, Typography, Button } from '@material-ui/core'; -import { InfoCard } from '../InfoCard/InfoCard'; -import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; -import { - useApi, - oktaAuthApiRef, - errorApiRef, -} from '@backstage/core-api'; - -const Component: ProviderComponent = ({ onResult }) => { - const oktaAuthApi = useApi(oktaAuthApiRef); - const errorApi = useApi(errorApiRef); - - const handleLogin = async () => { - try { - const identity = await oktaAuthApi.getBackstageIdentity({ - instantPopup: true, - }); - - const profile = await oktaAuthApi.getProfile(); - - onResult({ - userId: identity!.id, - profile: profile!, - getIdToken: () => - oktaAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await oktaAuthApi.logout(); - }, - }); - } catch (error) { - errorApi.post(error); - } - }; - - return ( - - - Sign In - - } - > - Sign In using Okta - - - ); -}; - -const loader: ProviderLoader = async apis => { - const oktaAuthApi = apis.get(oktaAuthApiRef)!; - - const identity = await oktaAuthApi.getBackstageIdentity({ - optional: true, - }); - - if (!identity) { - return undefined; - } - - const profile = await oktaAuthApi.getProfile(); - - return { - userId: identity.id, - profile: profile!, - getIdToken: () => - oktaAuthApi.getBackstageIdentity().then(i => i!.idToken), - logout: async () => { - await oktaAuthApi.logout(); - }, - }; -}; - -export const oktaProvider: SignInProvider = { Component, loader }; \ No newline at end of file From f6a98ced554ca63b64136fc16aad9b27792d5ba7 Mon Sep 17 00:00:00 2001 From: Kinga Sieminiak Date: Thu, 6 Aug 2020 14:17:33 +0200 Subject: [PATCH 4/4] Throw an error when IDs of providers are duplicated --- .../core/src/layout/SignInPage/providers.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx index a0e1941d90..ff60eba228 100644 --- a/packages/core/src/layout/SignInPage/providers.tsx +++ b/packages/core/src/layout/SignInPage/providers.tsx @@ -29,34 +29,42 @@ import { customProvider } from './customProvider'; const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider'; -export type SignInProviderId = 'guest' | 'custom' | string; - export type SignInProviderType = { - [id in SignInProviderId]: { + [key: string]: { components: SignInProvider; - id: SignInProviderId; + id: string; config?: SignInConfig; }; }; -const signInProviders: { [id in SignInProviderId]: SignInProvider } = { +const signInProviders: { [key: string]: SignInProvider } = { guest: guestProvider, custom: customProvider, common: commonProvider, }; +function validateIDs(id: string, providers: SignInProviderType): void { + if (id in providers) + throw new Error( + `"${id}" ID is duplicated. IDs of identity providers have to be unique.`, + ); +} + export function getSignInProviders( identityProviders: IdentityProviders, ): SignInProviderType { const providers = identityProviders.reduce( (acc: SignInProviderType, config) => { if (typeof config === 'string') { + validateIDs(config, acc); acc[config] = { components: signInProviders[config], id: config }; return acc; } const { id } = config as SignInConfig; + validateIDs(id, acc); + acc[id] = { components: signInProviders.common, id, config }; return acc; @@ -99,7 +107,7 @@ export const useSignInProviders = ( // We can't use storageApi here, as it might have a dependency on the IdentityApi const selectedProviderId = localStorage.getItem( PROVIDER_STORAGE_KEY, - ) as SignInProviderId; + ) as string; // No provider selected, let the user pick one if (selectedProviderId === null) {