core-plugin-api: Use LegacyUserIdentity helper

Co-authored-by: blam <ben@blam.sh>
Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Johan Haals
2021-11-26 16:05:32 +01:00
committed by blam
parent 6304c8f947
commit 32b0443660
12 changed files with 129 additions and 150 deletions
@@ -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<string | undefined> {
return this.result.getIdToken?.();
}
+45 -56
View File
@@ -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<IdentityApi>();
const setLegacyResult = (result: SignInResult) => {
setIdentityApi(new LegacyUserIdentity(result));
};
if (!identityApi) {
return (
<Component
onResult={setLegacyResult}
onSignInSuccess={setIdentityApi}
/>
);
return <Component onSignInSuccess={setIdentityApi} />;
}
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 <div>{props.foo()}</div>;
}
// interface Derp {
// getComponents(): {
// Foo: (props: FooPropsV1) => JSX.Element;
// };
// }
function NewCustomFoo(props: FooPropsV2) {
return (
<div>
{props.foo()} {props.bar()}
</div>
);
}
// function createDerp(options: CreateDerpOptions): Derp {
// return { getComponents: () => options.components };
// }
const derp = createDerp({
components: {
Foo: NewCustomFoo,
},
});
// function CustomFoo(props: FooPropsV1) {
// return <div>{props.foo()}</div>;
// }
const { Foo } = derp.getComponents();
const _foo = <Foo foo={() => undefined} />;
// function NewCustomFoo(props: FooPropsV2) {
// return (
// <div>
// {props.foo()} {props.bar()}
// </div>
// );
// }
// const derp = createDerp({
// components: {
// Foo: NewCustomFoo,
// },
// });
// const { Foo } = derp.getComponents();
// const _foo = <Foo foo={() => undefined} />;
-6
View File
@@ -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.
*/
@@ -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,
@@ -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 };
@@ -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 };
@@ -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<Data>({
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 (
@@ -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 }) => (
<GridItem>
<InfoCard
title="Guest"
@@ -38,7 +31,7 @@ const Component: ProviderComponent = ({ onResult }) => (
<Button
color="primary"
variant="outlined"
onClick={() => onResult(result)}
onClick={() => onSignInSuccess(new GuestUserIdentity())}
>
Enter
</Button>
@@ -56,7 +49,7 @@ const Component: ProviderComponent = ({ onResult }) => (
);
const loader: ProviderLoader = async () => {
return result;
return new GuestUserIdentity();
};
export const guestProvider: SignInProvider = { Component, loader };
@@ -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 = (
<Component
key={provider.id}
config={provider.config!}
onResult={handleResult}
onSignInSuccess={handleSignInSuccess}
/>
);
}),
@@ -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<ProfileInfoApi & BackstageIdentityApi & SessionApi>,
) => Promise<SignInResult | undefined>;
) => Promise<IdentityApi | undefined>;
export type SignInProvider = {
Component: ProviderComponent;
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -137,7 +137,7 @@ export type AuthProviderFactory = (
export type AuthResponse<ProviderInfo> = {
providerInfo: ProviderInfo;
profile: ProfileInfo;
backstageIdentity?: BackstageIdentity;
backstageIdentity?: BackstageIdentityResponse;
};
/**
@@ -230,7 +230,7 @@ export type SignInResolver<AuthResult> = (
catalogIdentityClient: CatalogIdentityClient;
logger: Logger;
},
) => Promise<BackstageIdentity>;
) => Promise<BackstageIdentityResponse>;
export type AuthHandlerResult = { profile: ProfileInfo };