From 6304c8f94714b6fecb6759d831d5c1d2214b5aa2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 24 Nov 2021 11:59:59 +0100 Subject: [PATCH] core-plugin-api: Refactor IdentityApi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- .../IdentityApi/AppIdentityProxy.ts | 90 ++++++++++++++++++ .../IdentityApi/GuestUserIdentity.ts | 60 ++++++++++++ .../IdentityApi/LegacyUserIdentity.ts | 76 ++++++++++++++++ packages/core-app-api/src/app/AppIdentity.ts | 85 ----------------- packages/core-app-api/src/app/AppManager.tsx | 89 +++++++++++++++--- packages/core-app-api/src/app/types.ts | 8 ++ .../src/layout/SignInPage/SignInPage.tsx | 38 ++++---- .../src/layout/SignInPage/UserIdentity.ts | 91 +++++++++++++++++++ .../src/apis/definitions/IdentityApi.ts | 64 +++++++++++-- .../src/apis/definitions/auth.ts | 46 ++++++++-- plugins/auth-backend/src/providers/types.ts | 45 ++++++--- 11 files changed, 543 insertions(+), 149 deletions(-) create mode 100644 packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts create mode 100644 packages/core-app-api/src/apis/implementations/IdentityApi/GuestUserIdentity.ts create mode 100644 packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts delete mode 100644 packages/core-app-api/src/app/AppIdentity.ts create mode 100644 packages/core-components/src/layout/SignInPage/UserIdentity.ts diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts new file mode 100644 index 0000000000..8a2f12594c --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -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 { + if (!this.target) { + throw mkError('getProfileInfo'); + } + return this.target.getProfileInfo(); + } + + async getBackstageIdentity(): Promise { + 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 { + if (!this.target) { + throw mkError('getIdToken'); + } + return this.target.getIdToken(); + } + + async signOut(): Promise { + if (!this.target) { + throw mkError('signOut'); + } + await this.target.signOut(); + location.reload(); + } +} diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/GuestUserIdentity.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/GuestUserIdentity.ts new file mode 100644 index 0000000000..db4731704c --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/GuestUserIdentity.ts @@ -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 { + return undefined; + } + + getProfile(): ProfileInfo { + return { + email: 'guest@example.com', + displayName: 'Guest', + }; + } + + async getProfileInfo(): Promise { + return { + email: 'guest@example.com', + displayName: 'Guest', + }; + } + + async getBackstageIdentity(): Promise { + const userEntityRef = `user:default/guest`; + return { + type: 'user', + userEntityRef, + ownershipEntityRefs: [userEntityRef], + }; + } + + async getCredentials(): Promise<{ token?: string | undefined }> { + return {}; + } + + async signOut(): Promise {} +} diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts new file mode 100644 index 0000000000..5c74472cdb --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/LegacyUserIdentity.ts @@ -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 { + return this.result.getIdToken?.(); + } + + getProfile(): ProfileInfo { + return this.result.profile; + } + + async getProfileInfo(): Promise { + return this.result.profile; + } + + async getBackstageIdentity(): Promise { + 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 { + return this.result.signOut?.(); + } +} diff --git a/packages/core-app-api/src/app/AppIdentity.ts b/packages/core-app-api/src/app/AppIdentity.ts deleted file mode 100644 index 64e698102f..0000000000 --- a/packages/core-app-api/src/app/AppIdentity.ts +++ /dev/null @@ -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; - private signOutFunc?: () => Promise; - - 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 { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi idToken before app was loaded', - ); - } - return this.idTokenFunc?.(); - } - - async signOut(): Promise { - 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; - } -} diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 5903a8c874..13ac8b4177 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -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; 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; children: ReactElement; }) => { - const [result, setResult] = useState(); + const [identityApi, setIdentityApi] = useState(); - if (result) { - this.identityApi.setSignInResult(result); - return children; + const setLegacyResult = (result: SignInResult) => { + setIdentityApi(new LegacyUserIdentity(result)); + }; + + if (!identityApi) { + return ( + + ); } - return ; + 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 ( @@ -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
{props.foo()}
; +} + +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 f16c8ac656..9e89bc0b56 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -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; }; /** diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index bd4fe37480..be2287f705 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -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 ? ( diff --git a/packages/core-components/src/layout/SignInPage/UserIdentity.ts b/packages/core-components/src/layout/SignInPage/UserIdentity.ts new file mode 100644 index 0000000000..daf9111d84 --- /dev/null +++ b/packages/core-components/src/layout/SignInPage/UserIdentity.ts @@ -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 { + 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 { + const profile = await this.authApi.getProfile(); + return profile!; + } + + async getBackstageIdentity(): Promise { + return this.identity; + } + + async getCredentials(): Promise<{ token?: string | undefined }> { + const identity = await this.authApi.getBackstageIdentity(); + return { token: identity!.token }; + } + + async signOut(): Promise { + return this.authApi.signOut(); + } +} diff --git a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts index 9f68a8b2cc..38376491ca 100644 --- a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts @@ -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; + /** + * The profile of the signed in user. + * + * @deprecated use {@link IdentityApi.getProfileInfo} instead. + */ + getProfile(): ProfileInfo; + + /** + * The profile of the signed in user. + */ + getProfileInfo(): Promise; + + /** + * User identity information within Backstage. + */ + getBackstageIdentity(): Promise; + + /** + * 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 */ diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index 8fe532f3a6..8da7fbe6fd 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -160,7 +160,31 @@ export type BackstageIdentityApi = { */ getBackstageIdentity( options?: AuthRequestOptions, - ): Promise; + ): Promise; +}; + +/** + * 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. * diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 089c82b293..04da05a314 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -140,32 +140,51 @@ export type AuthResponse = { 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 = { /**