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:
@@ -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?.();
|
||||
}
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user