core-plugin-api: Refactor IdentityApi
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: Johan Haals <johan.haals@gmail.com> Co-authored-by: blam <ben@blam.sh> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
IdentityApi,
|
||||
ProfileInfo,
|
||||
BackstageUserIdentity,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
function mkError(thing: string) {
|
||||
return new Error(
|
||||
`Tried to access IdentityApi ${thing} before app was loaded`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the connection between the App-wide IdentityApi
|
||||
* and sign-in page.
|
||||
*/
|
||||
export class AppIdentityProxy implements IdentityApi {
|
||||
private target?: IdentityApi;
|
||||
|
||||
// This is called by the app manager once the sign-in page provides us with an implementation
|
||||
setTarget(identityApi: IdentityApi) {
|
||||
this.target = identityApi;
|
||||
}
|
||||
|
||||
getUserId(): string {
|
||||
if (!this.target) {
|
||||
throw mkError('getUserId');
|
||||
}
|
||||
return this.target.getUserId();
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
if (!this.target) {
|
||||
throw mkError('getProfile');
|
||||
}
|
||||
return this.target.getProfile();
|
||||
}
|
||||
|
||||
async getProfileInfo(): Promise<ProfileInfo> {
|
||||
if (!this.target) {
|
||||
throw mkError('getProfileInfo');
|
||||
}
|
||||
return this.target.getProfileInfo();
|
||||
}
|
||||
|
||||
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
|
||||
if (!this.target) {
|
||||
throw mkError('getBackstageIdentity');
|
||||
}
|
||||
return this.target.getBackstageIdentity();
|
||||
}
|
||||
|
||||
async getCredentials(): Promise<{ token?: string | undefined }> {
|
||||
if (!this.target) {
|
||||
throw mkError('getCredentials');
|
||||
}
|
||||
return this.target.getCredentials();
|
||||
}
|
||||
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
if (!this.target) {
|
||||
throw mkError('getIdToken');
|
||||
}
|
||||
return this.target.getIdToken();
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
if (!this.target) {
|
||||
throw mkError('signOut');
|
||||
}
|
||||
await this.target.signOut();
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
IdentityApi,
|
||||
ProfileInfo,
|
||||
BackstageUserIdentity,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export class GuestUserIdentity implements IdentityApi {
|
||||
getUserId(): string {
|
||||
return 'guest';
|
||||
}
|
||||
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
return {
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
};
|
||||
}
|
||||
|
||||
async getProfileInfo(): Promise<ProfileInfo> {
|
||||
return {
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
};
|
||||
}
|
||||
|
||||
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
|
||||
const userEntityRef = `user:default/guest`;
|
||||
return {
|
||||
type: 'user',
|
||||
userEntityRef,
|
||||
ownershipEntityRefs: [userEntityRef],
|
||||
};
|
||||
}
|
||||
|
||||
async getCredentials(): Promise<{ token?: string | undefined }> {
|
||||
return {};
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
IdentityApi,
|
||||
ProfileInfo,
|
||||
BackstageUserIdentity,
|
||||
SignInResult,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
function parseJwtPayload(token: string) {
|
||||
const [_header, payload, _signature] = token.split('.');
|
||||
return JSON.parse(atob(payload));
|
||||
}
|
||||
|
||||
export class LegacyUserIdentity implements IdentityApi {
|
||||
constructor(private readonly result: SignInResult) {}
|
||||
|
||||
getUserId(): string {
|
||||
return this.result.userId;
|
||||
}
|
||||
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
return this.result.getIdToken?.();
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
return this.result.profile;
|
||||
}
|
||||
|
||||
async getProfileInfo(): Promise<ProfileInfo> {
|
||||
return this.result.profile;
|
||||
}
|
||||
|
||||
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
|
||||
const token = await this.getIdToken();
|
||||
|
||||
if (!token) {
|
||||
const userEntityRef = `user:default/${this.getUserId()}`;
|
||||
return {
|
||||
type: 'user',
|
||||
userEntityRef,
|
||||
ownershipEntityRefs: [userEntityRef],
|
||||
};
|
||||
}
|
||||
|
||||
const { sub, ent } = parseJwtPayload(token);
|
||||
return {
|
||||
type: 'user',
|
||||
userEntityRef: sub,
|
||||
ownershipEntityRefs: ent ?? [sub],
|
||||
};
|
||||
}
|
||||
|
||||
async getCredentials(): Promise<{ token?: string | undefined }> {
|
||||
const token = await this.result.getIdToken?.();
|
||||
return { token };
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
return this.result.signOut?.();
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 { IdentityApi, ProfileInfo } from '@backstage/core-plugin-api';
|
||||
import { SignInResult } from './types';
|
||||
|
||||
/**
|
||||
* Implementation of the connection between the App-wide IdentityApi
|
||||
* and sign-in page.
|
||||
*/
|
||||
export class AppIdentity implements IdentityApi {
|
||||
private hasIdentity = false;
|
||||
private userId?: string;
|
||||
private profile?: ProfileInfo;
|
||||
private idTokenFunc?: () => Promise<string>;
|
||||
private signOutFunc?: () => Promise<void>;
|
||||
|
||||
getUserId(): string {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
'Tried to access IdentityApi userId before app was loaded',
|
||||
);
|
||||
}
|
||||
return this.userId!;
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
'Tried to access IdentityApi profile before app was loaded',
|
||||
);
|
||||
}
|
||||
return this.profile!;
|
||||
}
|
||||
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
'Tried to access IdentityApi idToken before app was loaded',
|
||||
);
|
||||
}
|
||||
return this.idTokenFunc?.();
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
'Tried to access IdentityApi signOutFunc before app was loaded',
|
||||
);
|
||||
}
|
||||
await this.signOutFunc?.();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// This is indirectly called by the sign-in page to continue into the app.
|
||||
setSignInResult(result: SignInResult) {
|
||||
if (this.hasIdentity) {
|
||||
return;
|
||||
}
|
||||
if (!result.userId) {
|
||||
throw new Error('Invalid sign-in result, userId not set');
|
||||
}
|
||||
if (!result.profile) {
|
||||
throw new Error('Invalid sign-in result, profile not set');
|
||||
}
|
||||
this.hasIdentity = true;
|
||||
this.userId = result.userId;
|
||||
this.profile = result.profile;
|
||||
this.idTokenFunc = result.getIdToken;
|
||||
this.signOutFunc = result.signOut;
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,15 @@ import {
|
||||
AppThemeApi,
|
||||
ConfigApi,
|
||||
featureFlagsApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
BackstagePlugin,
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
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,
|
||||
@@ -66,7 +69,7 @@ import { RoutingProvider } from '../routing/RoutingProvider';
|
||||
import { RouteTracker } from '../routing/RouteTracker';
|
||||
import { validateRoutes } from '../routing/validation';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { AppIdentity } from './AppIdentity';
|
||||
import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import {
|
||||
AppComponents,
|
||||
AppConfigLoader,
|
||||
@@ -189,7 +192,7 @@ export class AppManager implements BackstageApp {
|
||||
private readonly defaultApis: Iterable<AnyApiFactory>;
|
||||
private readonly bindRoutes: AppOptions['bindRoutes'];
|
||||
|
||||
private readonly identityApi = new AppIdentity();
|
||||
private readonly appIdentityProxy = new AppIdentityProxy();
|
||||
private readonly apiFactoryRegistry: ApiFactoryRegistry;
|
||||
|
||||
constructor(options: AppOptions) {
|
||||
@@ -344,14 +347,23 @@ export class AppManager implements BackstageApp {
|
||||
component: ComponentType<SignInPageProps>;
|
||||
children: ReactElement;
|
||||
}) => {
|
||||
const [result, setResult] = useState<SignInResult>();
|
||||
const [identityApi, setIdentityApi] = useState<IdentityApi>();
|
||||
|
||||
if (result) {
|
||||
this.identityApi.setSignInResult(result);
|
||||
return children;
|
||||
const setLegacyResult = (result: SignInResult) => {
|
||||
setIdentityApi(new LegacyUserIdentity(result));
|
||||
};
|
||||
|
||||
if (!identityApi) {
|
||||
return (
|
||||
<Component
|
||||
onResult={setLegacyResult}
|
||||
onSignInSuccess={setIdentityApi}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Component onResult={setResult} />;
|
||||
this.appIdentityProxy.setTarget(identityApi);
|
||||
return children;
|
||||
};
|
||||
|
||||
const AppRouter = ({ children }: PropsWithChildren<{}>) => {
|
||||
@@ -360,13 +372,7 @@ export class AppManager implements BackstageApp {
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
this.identityApi.setSignInResult({
|
||||
userId: 'guest',
|
||||
profile: {
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
},
|
||||
});
|
||||
this.appIdentityProxy.setTarget(new GuestUserIdentity());
|
||||
|
||||
return (
|
||||
<RouterComponent>
|
||||
@@ -430,7 +436,7 @@ export class AppManager implements BackstageApp {
|
||||
this.apiFactoryRegistry.register('static', {
|
||||
api: identityApiRef,
|
||||
deps: {},
|
||||
factory: () => this.identityApi,
|
||||
factory: () => this.appIdentityProxy,
|
||||
});
|
||||
|
||||
// It's possible to replace the feature flag API, but since we must have at least
|
||||
@@ -485,3 +491,56 @@ export class AppManager implements BackstageApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface FooPropsV1 {
|
||||
foo: () => undefined;
|
||||
}
|
||||
|
||||
interface FooPropsV2 {
|
||||
foo: () => undefined;
|
||||
bar: () => undefined;
|
||||
}
|
||||
|
||||
// type FooProps = {
|
||||
// foo: () => undefined
|
||||
// } | {
|
||||
// foo: () => undefined
|
||||
// bar: () => undefined
|
||||
// }
|
||||
|
||||
interface CreateDerpOptions {
|
||||
components: {
|
||||
Foo: (props: FooPropsV1 | FooPropsV2) => JSX.Element;
|
||||
};
|
||||
}
|
||||
|
||||
interface Derp {
|
||||
getComponents(): {
|
||||
Foo: (props: FooPropsV1) => JSX.Element;
|
||||
};
|
||||
}
|
||||
|
||||
function createDerp(options: CreateDerpOptions): Derp {
|
||||
return { getComponents: () => options.components };
|
||||
}
|
||||
|
||||
function CustomFoo(props: FooPropsV1) {
|
||||
return <div>{props.foo()}</div>;
|
||||
}
|
||||
|
||||
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} />;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
PluginOutput,
|
||||
IdentityApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { AppConfig } from '@backstage/config';
|
||||
|
||||
@@ -42,6 +43,7 @@ export type BootErrorPageProps = {
|
||||
* The outcome of signing in on the sign-in page.
|
||||
*
|
||||
* @public
|
||||
* @deprecated replaced by passing the {@link IdentityApi} to the {@link SignInPageProps.onSignInSuccess} instead.
|
||||
*/
|
||||
export type SignInResult = {
|
||||
/**
|
||||
@@ -70,8 +72,14 @@ export type SignInResult = {
|
||||
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.
|
||||
*/
|
||||
onSignInSuccess(identityApi: IdentityApi): void;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BackstageIdentity,
|
||||
BackstageIdentityResponse,
|
||||
configApiRef,
|
||||
SignInPageProps,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { UserIdentity } from './UserIdentity';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
@@ -87,7 +88,7 @@ export const MultiSignInPage = ({
|
||||
};
|
||||
|
||||
export const SingleSignInPage = ({
|
||||
onResult,
|
||||
onSignInSuccess,
|
||||
provider,
|
||||
auto,
|
||||
}: SingleSignInPageProps) => {
|
||||
@@ -105,54 +106,47 @@ export const SingleSignInPage = ({
|
||||
type LoginOpts = { checkExisting?: boolean; showPopup?: boolean };
|
||||
const login = async ({ checkExisting, showPopup }: LoginOpts) => {
|
||||
try {
|
||||
let identity: BackstageIdentity | undefined;
|
||||
let identityResponse: BackstageIdentityResponse | undefined;
|
||||
if (checkExisting) {
|
||||
// Do an initial check if any logged-in session exists
|
||||
identity = await authApi.getBackstageIdentity({
|
||||
identityResponse = await authApi.getBackstageIdentity({
|
||||
optional: true,
|
||||
});
|
||||
}
|
||||
|
||||
// If no session exists, show the sign-in page
|
||||
if (!identity && (showPopup || auto)) {
|
||||
if (!identityResponse && (showPopup || auto)) {
|
||||
// Unless auto is set to true, this step should not happen.
|
||||
// When user intentionally clicks the Sign In button, autoShowPopup is set to true
|
||||
setShowLoginPage(true);
|
||||
identity = await authApi.getBackstageIdentity({
|
||||
identityResponse = await authApi.getBackstageIdentity({
|
||||
instantPopup: true,
|
||||
});
|
||||
if (!identity) {
|
||||
if (!identityResponse) {
|
||||
throw new Error(
|
||||
`The ${provider.title} provider is not configured to support sign-in`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!identity) {
|
||||
if (!identityResponse) {
|
||||
setShowLoginPage(true);
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
authApi,
|
||||
profile,
|
||||
}),
|
||||
);
|
||||
} catch (err: any) {
|
||||
// User closed the sign-in modal
|
||||
setError(err);
|
||||
setShowLoginPage(true);
|
||||
}
|
||||
};
|
||||
|
||||
useMount(() => login({ checkExisting: true }));
|
||||
|
||||
return showLoginPage ? (
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
IdentityApi,
|
||||
ProfileInfo,
|
||||
ProfileInfoApi,
|
||||
BackstageUserIdentity,
|
||||
BackstageIdentityApi,
|
||||
SessionApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export class UserIdentity implements IdentityApi {
|
||||
static from(options: {
|
||||
identity: BackstageUserIdentity;
|
||||
authApi: ProfileInfoApi & BackstageIdentityApi & SessionApi;
|
||||
/**
|
||||
* Passing a profile synchronously allows the deprecated `getProfile` method to be
|
||||
* called by consumers of the {@link IdentityApi}. If you do not have any consumers
|
||||
* of that method than this is safe to leave out.
|
||||
*
|
||||
* @deprecated Only provide this if you have plugins that call the synchronous `getProfile` method, which is also deprecated.
|
||||
*/
|
||||
profile?: ProfileInfo;
|
||||
}) {
|
||||
return new UserIdentity(options.identity, options.authApi, options.profile);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly identity: BackstageUserIdentity,
|
||||
private readonly authApi: ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi,
|
||||
private readonly profile?: ProfileInfo,
|
||||
) {}
|
||||
|
||||
getUserId(): string {
|
||||
const ref = this.identity.userEntityRef;
|
||||
const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref);
|
||||
if (!match) {
|
||||
throw new TypeError(`Invalid user entity reference "${ref}"`);
|
||||
}
|
||||
|
||||
return match[3];
|
||||
}
|
||||
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
const identity = await this.authApi.getBackstageIdentity();
|
||||
return identity!.token;
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
if (!this.profile) {
|
||||
throw new Error(
|
||||
'The identity API does not implement synchronous profile fetching, use getProfileInfo() instead',
|
||||
);
|
||||
}
|
||||
return this.profile;
|
||||
}
|
||||
|
||||
async getProfileInfo(): Promise<ProfileInfo> {
|
||||
const profile = await this.authApi.getProfile();
|
||||
return profile!;
|
||||
}
|
||||
|
||||
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
|
||||
return this.identity;
|
||||
}
|
||||
|
||||
async getCredentials(): Promise<{ token?: string | undefined }> {
|
||||
const identity = await this.authApi.getBackstageIdentity();
|
||||
return { token: identity!.token };
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
return this.authApi.signOut();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,34 @@
|
||||
import { ApiRef, createApiRef } from '../system';
|
||||
import { ProfileInfo } from './auth';
|
||||
|
||||
/*
|
||||
|
||||
- [ ] IdentityApi getProfile, make async
|
||||
- [ ] BackstageIdentity (settle or remove)
|
||||
- [ ] Evolution plan for utility APIs
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* User identity information within Backstage.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type BackstageUserIdentity = {
|
||||
type: 'user';
|
||||
|
||||
/**
|
||||
* The entityRef of the user in the catalog.
|
||||
* For example User:default/sandra
|
||||
*/
|
||||
userEntityRef: string;
|
||||
|
||||
/**
|
||||
* The user and group entities that the user claims ownership through
|
||||
*/
|
||||
ownershipEntityRefs: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The Identity API used to identify and get information about the signed in user.
|
||||
*
|
||||
@@ -26,25 +54,45 @@ export type IdentityApi = {
|
||||
* The ID of the signed in user. This ID is not meant to be presented to the user, but used
|
||||
* as an opaque string to pass on to backends or use in frontend logic.
|
||||
*
|
||||
* TODO: The intention of the user ID is to be able to tie the user to an identity
|
||||
* that is known by the catalog and/or identity backend. It should for example
|
||||
* be possible to fetch all owned components using this ID.
|
||||
* @deprecated use {@link IdentityApi.getIdentity} instead.
|
||||
*/
|
||||
getUserId(): string;
|
||||
|
||||
/**
|
||||
* The profile of the signed in user.
|
||||
*/
|
||||
getProfile(): ProfileInfo;
|
||||
|
||||
/**
|
||||
* An OpenID Connect ID Token which proves the identity of the signed in user.
|
||||
*
|
||||
* The ID token will be undefined if the signed in user does not have a verified
|
||||
* identity, such as a demo user or mocked user for e2e tests.
|
||||
*
|
||||
* @deprecated use {@link IdentityApi.getCredentials} instead.
|
||||
*/
|
||||
getIdToken(): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* The profile of the signed in user.
|
||||
*
|
||||
* @deprecated use {@link IdentityApi.getProfileInfo} instead.
|
||||
*/
|
||||
getProfile(): ProfileInfo;
|
||||
|
||||
/**
|
||||
* The profile of the signed in user.
|
||||
*/
|
||||
getProfileInfo(): Promise<ProfileInfo>;
|
||||
|
||||
/**
|
||||
* User identity information within Backstage.
|
||||
*/
|
||||
getBackstageIdentity(): Promise<BackstageUserIdentity>;
|
||||
|
||||
/**
|
||||
* Provides credentials in the form of a token which proves the identity of the signed in user.
|
||||
*
|
||||
* The token will be undefined if the signed in user does not have a verified
|
||||
* identity, such as a demo user or mocked user for e2e tests.
|
||||
*/
|
||||
getCredentials(): Promise<{ token?: string }>;
|
||||
|
||||
/**
|
||||
* Sign out the current user
|
||||
*/
|
||||
|
||||
@@ -160,7 +160,31 @@ export type BackstageIdentityApi = {
|
||||
*/
|
||||
getBackstageIdentity(
|
||||
options?: AuthRequestOptions,
|
||||
): Promise<BackstageIdentity | undefined>;
|
||||
): Promise<BackstageIdentityResponse | undefined>;
|
||||
};
|
||||
|
||||
/**
|
||||
* User identity information within Backstage.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type BackstageUserIdentity = {
|
||||
/**
|
||||
* The type of identity that this structure represents. In the frontend app
|
||||
* this will currently always be 'user'.
|
||||
*/
|
||||
type: 'user';
|
||||
|
||||
/**
|
||||
* The entityRef of the user in the catalog.
|
||||
* For example User:default/sandra
|
||||
*/
|
||||
userEntityRef: string;
|
||||
|
||||
/**
|
||||
* The user and group entities that the user claims ownership through
|
||||
*/
|
||||
ownershipEntityRefs: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -168,23 +192,31 @@ export type BackstageIdentityApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type BackstageIdentity = {
|
||||
export type BackstageIdentityResponse = {
|
||||
/**
|
||||
* The backstage user ID.
|
||||
*
|
||||
* @deprecated The identity is now provided via the `identity` field instead.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* @deprecated This is deprecated, use `token` instead.
|
||||
*/
|
||||
idToken: string;
|
||||
|
||||
/**
|
||||
* The token used to authenticate the user within Backstage.
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* Identity information derived from the token.
|
||||
*/
|
||||
identity: BackstageUserIdentity;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated use {@link BackstageIdentityResponse} instead.
|
||||
*/
|
||||
export type BackstageIdentity = BackstageIdentityResponse;
|
||||
|
||||
/**
|
||||
* Profile information of the user.
|
||||
*
|
||||
|
||||
@@ -140,32 +140,51 @@ export type AuthResponse<ProviderInfo> = {
|
||||
backstageIdentity?: BackstageIdentity;
|
||||
};
|
||||
|
||||
export type BackstageIdentity = {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type BackstageIdentityResponse = {
|
||||
/**
|
||||
* An opaque ID that uniquely identifies the user within Backstage.
|
||||
*
|
||||
* This is typically the same as the user entity `metadata.name`.
|
||||
*
|
||||
* @deprecated Use the `identity` field instead
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* This is deprecated, use `token` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The token used to authenticate the user within Backstage.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The entity that the user is represented by within Backstage.
|
||||
*
|
||||
* This entity may or may not exist within the Catalog, and it can be used
|
||||
* to read and store additional metadata about the user.
|
||||
*
|
||||
* @deprecated Use the `identity` field instead.
|
||||
*/
|
||||
entity?: Entity;
|
||||
|
||||
/**
|
||||
* The token used to authenticate the user within Backstage.
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/**
|
||||
* A plaintext description of the identity that is encapsulated within the token.
|
||||
*/
|
||||
identity?: {
|
||||
type: 'user';
|
||||
|
||||
/**
|
||||
* The entityRef of the user in the catalog.
|
||||
* For example User:default/sandra
|
||||
*/
|
||||
userEntityRef: string;
|
||||
|
||||
/**
|
||||
* The user and group entities that the user claims ownership through
|
||||
*/
|
||||
ownershipEntityRefs: string[];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -173,6 +192,8 @@ export type BackstageIdentity = {
|
||||
*
|
||||
* It is also temporarily used as the profile of the signed-in user's Backstage
|
||||
* identity, but we want to replace that with data from identity and/org catalog service
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ProfileInfo = {
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user