From 32b04436608362c868a6fc9736f9124de5ec9b39 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 26 Nov 2021 16:05:32 +0100 Subject: [PATCH] core-plugin-api: Use LegacyUserIdentity helper Co-authored-by: blam Signed-off-by: Johan Haals --- .../IdentityApi/LegacyUserIdentity.ts | 6 +- packages/core-app-api/src/app/AppManager.tsx | 101 ++++++++---------- packages/core-app-api/src/app/types.ts | 6 -- .../src/layout/SignInPage/SignInPage.tsx | 8 +- .../src/layout/SignInPage/auth0Provider.tsx | 42 ++++---- .../src/layout/SignInPage/commonProvider.tsx | 45 ++++---- .../src/layout/SignInPage/customProvider.tsx | 19 ++-- .../src/layout/SignInPage/guestProvider.tsx | 15 +-- .../src/layout/SignInPage/providers.tsx | 27 +++-- .../src/layout/SignInPage/types.ts | 4 +- plugins/auth-backend/src/index.ts | 2 +- plugins/auth-backend/src/providers/types.ts | 4 +- 12 files changed, 129 insertions(+), 150 deletions(-) diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts index 5c74472cdb..ff6d38c860 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts @@ -27,12 +27,16 @@ function parseJwtPayload(token: string) { } export class LegacyUserIdentity implements IdentityApi { - constructor(private readonly result: SignInResult) {} + private constructor(private readonly result: SignInResult) {} getUserId(): string { return this.result.userId; } + static fromResult(result: SignInResult): LegacyUserIdentity { + return new LegacyUserIdentity(result); + } + async getIdToken(): Promise { return this.result.getIdToken?.(); } diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 13ac8b4177..31e093a145 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -51,7 +51,6 @@ import { ExternalRouteRef, } from '@backstage/core-plugin-api'; import { GuestUserIdentity } from '../apis/implementations/IdentityApi/GuestUserIdentity'; -import { LegacyUserIdentity } from '../apis/implementations/IdentityApi/LegacyUserIdentity'; import { ApiFactoryRegistry, ApiResolver } from '../apis/system'; import { childDiscoverer, @@ -78,7 +77,6 @@ import { AppRouteBinder, BackstageApp, SignInPageProps, - SignInResult, } from './types'; import { AppThemeProvider } from './AppThemeProvider'; import { defaultConfigLoader } from './defaultConfigLoader'; @@ -349,17 +347,8 @@ export class AppManager implements BackstageApp { }) => { const [identityApi, setIdentityApi] = useState(); - const setLegacyResult = (result: SignInResult) => { - setIdentityApi(new LegacyUserIdentity(result)); - }; - if (!identityApi) { - return ( - - ); + return ; } this.appIdentityProxy.setTarget(identityApi); @@ -492,55 +481,55 @@ export class AppManager implements BackstageApp { } } -interface FooPropsV1 { - foo: () => undefined; -} - -interface FooPropsV2 { - foo: () => undefined; - bar: () => undefined; -} - -// type FooProps = { -// foo: () => undefined -// } | { -// foo: () => undefined -// bar: () => undefined +// interface FooPropsV1 { +// foo: () => undefined; // } -interface CreateDerpOptions { - components: { - Foo: (props: FooPropsV1 | FooPropsV2) => JSX.Element; - }; -} +// interface FooPropsV2 { +// foo: () => undefined; +// bar: () => undefined; +// } -interface Derp { - getComponents(): { - Foo: (props: FooPropsV1) => JSX.Element; - }; -} +// // type FooProps = { +// // foo: () => undefined +// // } | { +// // foo: () => undefined +// // bar: () => undefined +// // } -function createDerp(options: CreateDerpOptions): Derp { - return { getComponents: () => options.components }; -} +// interface CreateDerpOptions { +// components: { +// Foo: (props: FooPropsV1 | FooPropsV2) => JSX.Element; +// }; +// } -function CustomFoo(props: FooPropsV1) { - return
{props.foo()}
; -} +// interface Derp { +// getComponents(): { +// Foo: (props: FooPropsV1) => JSX.Element; +// }; +// } -function NewCustomFoo(props: FooPropsV2) { - return ( -
- {props.foo()} {props.bar()} -
- ); -} +// function createDerp(options: CreateDerpOptions): Derp { +// return { getComponents: () => options.components }; +// } -const derp = createDerp({ - components: { - Foo: NewCustomFoo, - }, -}); +// function CustomFoo(props: FooPropsV1) { +// return
{props.foo()}
; +// } -const { Foo } = derp.getComponents(); -const _foo = undefined} />; +// function NewCustomFoo(props: FooPropsV2) { +// return ( +//
+// {props.foo()} {props.bar()} +//
+// ); +// } + +// const derp = createDerp({ +// components: { +// Foo: NewCustomFoo, +// }, +// }); + +// const { Foo } = derp.getComponents(); +// const _foo = undefined} />; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 9e89bc0b56..0921bd2e17 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -70,12 +70,6 @@ export type SignInResult = { * @public */ export type SignInPageProps = { - /** - * Set the sign-in result for the app. This should only be called once. - * @deprecated use {@link SignInPageProps.onSignInSuccess} instead. - */ - onResult(result: SignInResult): void; - /** * Set the IdentityApi on successful sign in. This should only be called once. */ diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index be2287f705..d0704c2b85 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -50,7 +50,7 @@ type SingleSignInPageProps = SignInPageProps & { export type Props = MultiSignInPageProps | SingleSignInPageProps; export const MultiSignInPage = ({ - onResult, + onSignInSuccess, providers = [], title, align = 'left', @@ -61,7 +61,7 @@ export const MultiSignInPage = ({ const signInProviders = getSignInProviders(providers); const [loading, providerElements] = useSignInProviders( signInProviders, - onResult, + onSignInSuccess, ); if (loading) { @@ -88,9 +88,9 @@ export const MultiSignInPage = ({ }; export const SingleSignInPage = ({ - onSignInSuccess, provider, auto, + onSignInSuccess, }: SingleSignInPageProps) => { const classes = useStyles(); const authApi = useApi(provider.apiRef); @@ -133,7 +133,9 @@ export const SingleSignInPage = ({ setShowLoginPage(true); return; } + const profile = await authApi.getProfile(); + onSignInSuccess( UserIdentity.from({ identity: identityResponse.identity, diff --git a/packages/core-components/src/layout/SignInPage/auth0Provider.tsx b/packages/core-components/src/layout/SignInPage/auth0Provider.tsx index e1372a2362..8e2728a95e 100644 --- a/packages/core-components/src/layout/SignInPage/auth0Provider.tsx +++ b/packages/core-components/src/layout/SignInPage/auth0Provider.tsx @@ -26,17 +26,18 @@ import { errorApiRef, } from '@backstage/core-plugin-api'; import { ForwardedError } from '@backstage/errors'; +import { UserIdentity } from './UserIdentity'; -const Component: ProviderComponent = ({ onResult }) => { +const Component: ProviderComponent = ({ onSignInSuccess }) => { const auth0AuthApi = useApi(auth0AuthApiRef); const errorApi = useApi(errorApiRef); const handleLogin = async () => { try { - const identity = await auth0AuthApi.getBackstageIdentity({ + const identityResponse = await auth0AuthApi.getBackstageIdentity({ instantPopup: true, }); - if (!identity) { + if (!identityResponse) { throw new Error( 'The Auth0 provider is not configured to support sign-in', ); @@ -44,15 +45,13 @@ const Component: ProviderComponent = ({ onResult }) => { const profile = await auth0AuthApi.getProfile(); - onResult({ - userId: identity!.id, - profile: profile!, - getIdToken: () => - auth0AuthApi.getBackstageIdentity().then(i => i!.token ?? i!.idToken), - signOut: async () => { - await auth0AuthApi.signOut(); - }, - }); + onSignInSuccess( + UserIdentity.from({ + identity: identityResponse.identity, + authApi: auth0AuthApi, + profile, + }), + ); } catch (error) { errorApi.post(new ForwardedError('Auth0 login failed', error)); } @@ -77,25 +76,20 @@ const Component: ProviderComponent = ({ onResult }) => { const loader: ProviderLoader = async apis => { const auth0AuthApi = apis.get(auth0AuthApiRef)!; - const identity = await auth0AuthApi.getBackstageIdentity({ + const identityResponse = await auth0AuthApi.getBackstageIdentity({ optional: true, }); - if (!identity) { + if (!identityResponse) { return undefined; } const profile = await auth0AuthApi.getProfile(); - - return { - userId: identity.id, - profile: profile!, - getIdToken: () => - auth0AuthApi.getBackstageIdentity().then(i => i!.token ?? i!.idToken), - signOut: async () => { - await auth0AuthApi.signOut(); - }, - }; + return UserIdentity.from({ + identity: identityResponse.identity, + authApi: auth0AuthApi, + profile, + }); }; export const auth0Provider: SignInProvider = { Component, loader }; diff --git a/packages/core-components/src/layout/SignInPage/commonProvider.tsx b/packages/core-components/src/layout/SignInPage/commonProvider.tsx index 515ae01e4d..48dd7df62c 100644 --- a/packages/core-components/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/commonProvider.tsx @@ -27,36 +27,33 @@ import { import { useApi, errorApiRef } from '@backstage/core-plugin-api'; import { GridItem } from './styles'; import { ForwardedError } from '@backstage/errors'; +import { UserIdentity } from './UserIdentity'; -const Component: ProviderComponent = ({ config, onResult }) => { +const Component: ProviderComponent = ({ config, onSignInSuccess }) => { const { apiRef, title, message } = config as SignInProviderConfig; const authApi = useApi(apiRef); const errorApi = useApi(errorApiRef); const handleLogin = async () => { try { - const identity = await authApi.getBackstageIdentity({ + const identityResponse = await authApi.getBackstageIdentity({ instantPopup: true, }); - if (!identity) { + if (!identityResponse) { throw new Error( `The ${title} provider is not configured to support sign-in`, ); } const profile = await authApi.getProfile(); - onResult({ - userId: identity!.id, - profile: profile!, - getIdToken: () => { - return authApi - .getBackstageIdentity() - .then(i => i!.token ?? i!.idToken); - }, - signOut: async () => { - await authApi.signOut(); - }, - }); + + onSignInSuccess( + UserIdentity.from({ + identity: identityResponse.identity, + profile, + authApi, + }), + ); } catch (error) { errorApi.post(new ForwardedError('Login failed', error)); } @@ -82,25 +79,21 @@ const Component: ProviderComponent = ({ config, onResult }) => { const loader: ProviderLoader = async (apis, apiRef) => { const authApi = apis.get(apiRef)!; - const identity = await authApi.getBackstageIdentity({ + const identityResponse = await authApi.getBackstageIdentity({ optional: true, }); - if (!identity) { + if (!identityResponse) { return undefined; } const profile = await authApi.getProfile(); - return { - userId: identity.id, - profile: profile!, - getIdToken: () => - authApi.getBackstageIdentity().then(i => i!.token ?? i!.idToken), - signOut: async () => { - await authApi.signOut(); - }, - }; + return UserIdentity.from({ + identity: identityResponse.identity, + profile, + authApi, + }); }; export const commonProvider: SignInProvider = { Component, loader }; diff --git a/packages/core-components/src/layout/SignInPage/customProvider.tsx b/packages/core-components/src/layout/SignInPage/customProvider.tsx index da1848ac05..77bde5223c 100644 --- a/packages/core-components/src/layout/SignInPage/customProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/customProvider.tsx @@ -26,6 +26,7 @@ import isEmpty from 'lodash/isEmpty'; import { InfoCard } from '../InfoCard/InfoCard'; import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; import { GridItem } from './styles'; +import { LegacyUserIdentity } from '@backstage/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity'; // accept base64url format according to RFC7515 (https://tools.ietf.org/html/rfc7515#section-3) const ID_TOKEN_REGEX = /^[a-z0-9_\-]+\.[a-z0-9_\-]+\.[a-z0-9_\-]+$/i; @@ -60,7 +61,7 @@ const asInputRef = (renderResult: UseFormRegisterReturn) => { }; }; -const Component: ProviderComponent = ({ onResult }) => { +const Component: ProviderComponent = ({ onSignInSuccess }) => { const classes = useFormStyles(); const { register, handleSubmit, formState } = useForm({ mode: 'onChange', @@ -69,13 +70,15 @@ const Component: ProviderComponent = ({ onResult }) => { const { errors } = formState; const handleResult = ({ userId, idToken }: Data) => { - onResult({ - userId, - profile: { - email: `${userId}@example.com`, - }, - getIdToken: idToken ? async () => idToken : undefined, - }); + onSignInSuccess( + LegacyUserIdentity.fromResult({ + userId, + profile: { + email: `${userId}@example.com`, + }, + getIdToken: idToken ? async () => idToken : undefined, + }), + ); }; return ( diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index e91d9cbc75..9e0326c7df 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -20,16 +20,9 @@ import Button from '@material-ui/core/Button'; import { InfoCard } from '../InfoCard/InfoCard'; import { GridItem } from './styles'; import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; +import { GuestUserIdentity } from '@backstage/core-app-api/src/apis/implementations/IdentityApi/GuestUserIdentity'; -const result = { - userId: 'guest', - profile: { - email: 'guest@example.com', - displayName: 'Guest', - }, -}; - -const Component: ProviderComponent = ({ onResult }) => ( +const Component: ProviderComponent = ({ onSignInSuccess }) => ( ( @@ -56,7 +49,7 @@ const Component: ProviderComponent = ({ onResult }) => ( ); const loader: ProviderLoader = async () => { - return result; + return new GuestUserIdentity(); }; export const guestProvider: SignInProvider = { Component, loader }; diff --git a/packages/core-components/src/layout/SignInPage/providers.tsx b/packages/core-components/src/layout/SignInPage/providers.tsx index 19915e6aa9..1ab7369dab 100644 --- a/packages/core-components/src/layout/SignInPage/providers.tsx +++ b/packages/core-components/src/layout/SignInPage/providers.tsx @@ -17,10 +17,10 @@ import React, { useLayoutEffect, useState, useMemo, useCallback } from 'react'; import { SignInPageProps, - SignInResult, useApi, useApiHolder, errorApiRef, + IdentityApi, } from '@backstage/core-plugin-api'; import { IdentityProviders, @@ -81,7 +81,7 @@ export function getSignInProviders( export const useSignInProviders = ( providers: SignInProviderType, - onResult: SignInPageProps['onResult'], + onSignInSuccess: SignInPageProps['onSignInSuccess'], ) => { const errorApi = useApi(errorApiRef); const apiHolder = useApiHolder(); @@ -89,16 +89,16 @@ export const useSignInProviders = ( // This decorates the result with sign out logic from this hook const handleWrappedResult = useCallback( - (result: SignInResult) => { - onResult({ - ...result, + (identityApi: IdentityApi) => { + onSignInSuccess({ + ...identityApi, signOut: async () => { localStorage.removeItem(PROVIDER_STORAGE_KEY); - await result.signOut?.(); + await identityApi.signOut?.(); }, }); }, - [onResult], + [onSignInSuccess], ); // In this effect we check if the user has already selected an existing login @@ -151,7 +151,14 @@ export const useSignInProviders = ( return () => { didCancel = true; }; - }, [loading, errorApi, onResult, apiHolder, providers, handleWrappedResult]); + }, [ + loading, + errorApi, + onSignInSuccess, + apiHolder, + providers, + handleWrappedResult, + ]); // This renders all available sign-in providers const elements = useMemo( @@ -161,7 +168,7 @@ export const useSignInProviders = ( const { Component } = provider.components; - const handleResult = (result: SignInResult) => { + const handleSignInSuccess = (result: IdentityApi) => { localStorage.setItem(PROVIDER_STORAGE_KEY, provider.id); handleWrappedResult(result); @@ -171,7 +178,7 @@ export const useSignInProviders = ( ); }), diff --git a/packages/core-components/src/layout/SignInPage/types.ts b/packages/core-components/src/layout/SignInPage/types.ts index 1e4e4a2997..5b458448bf 100644 --- a/packages/core-components/src/layout/SignInPage/types.ts +++ b/packages/core-components/src/layout/SignInPage/types.ts @@ -17,12 +17,12 @@ import { ComponentType } from 'react'; import { SignInPageProps, - SignInResult, ApiHolder, ApiRef, ProfileInfoApi, BackstageIdentityApi, SessionApi, + IdentityApi, } from '@backstage/core-plugin-api'; export type SignInProviderConfig = { @@ -41,7 +41,7 @@ export type ProviderComponent = ComponentType< export type ProviderLoader = ( apis: ApiHolder, apiRef: ApiRef, -) => Promise; +) => Promise; export type SignInProvider = { Component: ProviderComponent; diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index 894f8c1e55..66c1b42dd9 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -29,7 +29,7 @@ export * from './providers'; // ensuresXRequestedWith and postMessageResponse to safely handle CORS requests for login. The WebMessageResponse type in flow is used to type the response from the login-popup export * from './lib/flow'; -// OAuth wrapper over a passport or a custom `startegy`. +// OAuth wrapper over a passport or a custom `strategy`. export * from './lib/oauth'; export * from './lib/catalog'; diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 04da05a314..52cff0bdf9 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -137,7 +137,7 @@ export type AuthProviderFactory = ( export type AuthResponse = { providerInfo: ProviderInfo; profile: ProfileInfo; - backstageIdentity?: BackstageIdentity; + backstageIdentity?: BackstageIdentityResponse; }; /** @@ -230,7 +230,7 @@ export type SignInResolver = ( catalogIdentityClient: CatalogIdentityClient; logger: Logger; }, -) => Promise; +) => Promise; export type AuthHandlerResult = { profile: ProfileInfo };