diff --git a/.changeset/fifty-horses-battle.md b/.changeset/fifty-horses-battle.md new file mode 100644 index 0000000000..c7164c7309 --- /dev/null +++ b/.changeset/fifty-horses-battle.md @@ -0,0 +1,24 @@ +--- +'@backstage/core-plugin-api': minor +--- + +Removed deprecated `IdentityApi` methods: `getUserId`, `getIdToken`, and `getProfile`. + +Existing usage of `getUserId` can be replaced by `getBackstageIdentity`, more precisely the equivalent of the previous `userId` can be retrieved like this: + +```ts +import { parseEntityRef } from '@backstage/catalog-model'; + +const identity = await identityApi.getBackstageIdentity(); +const { name: userId } = parseEntityRef(identity.userEntityRef); +``` + +Note that it is recommended to consume the entire `userEntityRef` rather than parsing out just the name, in order to support namespaces. + +Existing usage of `getIdToken` can be replaced by `getCredentials`, like this: + +```ts +const { token } = await identityApi.getCredentials(); +``` + +And existing usage of `getProfile` is replaced by `getProfileInfo`, which returns the same profile object, but is now async. diff --git a/.changeset/mean-rings-burn.md b/.changeset/mean-rings-burn.md new file mode 100644 index 0000000000..2996de5c72 --- /dev/null +++ b/.changeset/mean-rings-burn.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-app-api': minor +'@backstage/core-plugin-api': minor +--- + +Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback. diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index af25679878..cc38618a00 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -563,14 +563,6 @@ export type SignInPageProps = { onSignInSuccess(identityApi: IdentityApi): void; }; -// @public @deprecated -export type SignInResult = { - userId: string; - profile: ProfileInfo; - getIdToken?: () => Promise; - signOut?: () => Promise; -}; - // @public export class UnhandledErrorForwarder { static forward(errorApi: ErrorApi, errorContext: ErrorApiErrorContext): void; diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts index 8a2f12594c..6868ebe265 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -26,15 +26,30 @@ function mkError(thing: string) { ); } +function logDeprecation(thing: string) { + // eslint-disable-next-line no-console + console.warn( + `WARNING: Call to ${thing} is deprecated and will break in the future`, + ); +} + +// We use this for a period of backwards compatibility. It is a hidden +// compatibility that will allow old plugins to continue working for a limited time. +type CompatibilityIdentityApi = IdentityApi & { + getUserId?(): string; + getIdToken?(): Promise; + getProfile?(): ProfileInfo; +}; + /** * Implementation of the connection between the App-wide IdentityApi * and sign-in page. */ export class AppIdentityProxy implements IdentityApi { - private target?: IdentityApi; + private target?: CompatibilityIdentityApi; // This is called by the app manager once the sign-in page provides us with an implementation - setTarget(identityApi: IdentityApi) { + setTarget(identityApi: CompatibilityIdentityApi) { this.target = identityApi; } @@ -42,6 +57,10 @@ export class AppIdentityProxy implements IdentityApi { if (!this.target) { throw mkError('getUserId'); } + if (!this.target.getUserId) { + throw new Error('IdentityApi does not implement getUserId'); + } + logDeprecation('getUserId'); return this.target.getUserId(); } @@ -49,6 +68,10 @@ export class AppIdentityProxy implements IdentityApi { if (!this.target) { throw mkError('getProfile'); } + if (!this.target.getProfile) { + throw new Error('IdentityApi does not implement getProfile'); + } + logDeprecation('getProfile'); return this.target.getProfile(); } @@ -77,6 +100,10 @@ export class AppIdentityProxy implements IdentityApi { if (!this.target) { throw mkError('getIdToken'); } + if (!this.target.getIdToken) { + throw new Error('IdentityApi does not implement getIdToken'); + } + logDeprecation('getIdToken'); return this.target.getIdToken(); } diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index bacaf83fbd..902e64c09f 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -18,7 +18,6 @@ import { ComponentType } from 'react'; import { AnyApiFactory, AppTheme, - ProfileInfo, IconComponent, BackstagePlugin, RouteRef, @@ -38,31 +37,6 @@ export type BootErrorPageProps = { error: Error; }; -/** - * The outcome of signing in on the sign-in page. - * - * @public - * @deprecated replaced by passing the {@link @backstage/core-plugin-api#IdentityApi} to the {@link SignInPageProps.onSignInSuccess} instead. - */ -export type SignInResult = { - /** - * User ID that will be returned by the IdentityApi - */ - userId: string; - - profile: ProfileInfo; - - /** - * Function used to retrieve an ID token for the signed in user. - */ - getIdToken?: () => Promise; - - /** - * Sign out handler that will be called if the user requests to sign out. - */ - signOut?: () => Promise; -}; - /** * Props for the `SignInPage` component of {@link AppComponents}. * diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 29847b079e..070131af96 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -38,7 +38,6 @@ import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { SessionApi } from '@backstage/core-plugin-api'; import { SignInPageProps } from '@backstage/core-plugin-api'; -import { SignInResult } from '@backstage/core-plugin-api'; import { SparklinesLineProps } from 'react-sparklines'; import { SparklinesProps } from 'react-sparklines'; import { StyledComponentProps } from '@material-ui/core/styles'; @@ -2395,7 +2394,12 @@ export class UserIdentity implements IdentityApi { profile?: ProfileInfo; }): IdentityApi; static createGuest(): IdentityApi; - static fromLegacy(result: SignInResult): IdentityApi; + static fromLegacy(result: { + userId: string; + profile: ProfileInfo; + getIdToken?: () => Promise; + signOut?: () => Promise; + }): IdentityApi; // (undocumented) getBackstageIdentity(): Promise; // (undocumented) diff --git a/packages/core-components/src/layout/SignInPage/IdentityApiSignOutProxy.ts b/packages/core-components/src/layout/SignInPage/IdentityApiSignOutProxy.ts index ec45bc7399..396913b9b7 100644 --- a/packages/core-components/src/layout/SignInPage/IdentityApiSignOutProxy.ts +++ b/packages/core-components/src/layout/SignInPage/IdentityApiSignOutProxy.ts @@ -20,30 +20,46 @@ import { ProfileInfo, } from '@backstage/core-plugin-api'; +// Similar to the AppIdentityApi we provide backwards compatibility for a limited time +type CompatibilityIdentityApi = IdentityApi & { + getUserId?(): string; + getIdToken?(): Promise; + getProfile?(): ProfileInfo; +}; + export class IdentityApiSignOutProxy implements IdentityApi { private constructor( private readonly config: { - identityApi: IdentityApi; + identityApi: CompatibilityIdentityApi; signOut: IdentityApi['signOut']; }, ) {} static from(config: { - identityApi: IdentityApi; + identityApi: CompatibilityIdentityApi; signOut: IdentityApi['signOut']; }): IdentityApi { return new IdentityApiSignOutProxy(config); } getUserId(): string { + if (!this.config.identityApi.getUserId) { + throw new Error(`SignOutProxy IdentityApi.getUserId is not implemented`); + } return this.config.identityApi.getUserId(); } getIdToken(): Promise { + if (!this.config.identityApi.getIdToken) { + throw new Error(`SignOutProxy IdentityApi.getIdToken is not implemented`); + } return this.config.identityApi.getIdToken(); } getProfile(): ProfileInfo { + if (!this.config.identityApi.getProfile) { + throw new Error(`SignOutProxy IdentityApi.getProfile is not implemented`); + } return this.config.identityApi.getProfile(); } diff --git a/packages/core-components/src/layout/SignInPage/LegacyUserIdentity.ts b/packages/core-components/src/layout/SignInPage/LegacyUserIdentity.ts index e28f94072e..cc51d2b9a8 100644 --- a/packages/core-components/src/layout/SignInPage/LegacyUserIdentity.ts +++ b/packages/core-components/src/layout/SignInPage/LegacyUserIdentity.ts @@ -18,7 +18,6 @@ import { IdentityApi, ProfileInfo, BackstageUserIdentity, - SignInResult, } from '@backstage/core-plugin-api'; function parseJwtPayload(token: string) { @@ -26,14 +25,22 @@ function parseJwtPayload(token: string) { return JSON.parse(atob(payload)); } +type LegacySignInResult = { + userId: string; + profile: ProfileInfo; + getIdToken?: () => Promise; + signOut?: () => Promise; +}; + +/** @internal */ export class LegacyUserIdentity implements IdentityApi { - private constructor(private readonly result: SignInResult) {} + private constructor(private readonly result: LegacySignInResult) {} getUserId(): string { return this.result.userId; } - static fromResult(result: SignInResult): LegacyUserIdentity { + static fromResult(result: LegacySignInResult): LegacyUserIdentity { return new LegacyUserIdentity(result); } diff --git a/packages/core-components/src/layout/SignInPage/UserIdentity.ts b/packages/core-components/src/layout/SignInPage/UserIdentity.ts index 7781c79154..781cb47c0e 100644 --- a/packages/core-components/src/layout/SignInPage/UserIdentity.ts +++ b/packages/core-components/src/layout/SignInPage/UserIdentity.ts @@ -21,12 +21,17 @@ import { BackstageUserIdentity, BackstageIdentityApi, SessionApi, - SignInResult, } from '@backstage/core-plugin-api'; import { GuestUserIdentity } from './GuestUserIdentity'; import { LegacyUserIdentity } from './LegacyUserIdentity'; +// TODO(Rugvip): This and the other IdentityApi implementations still implement +// the old removed methods. This is to allow for backwards compatibility +// with old plugins that still consume this API. We will leave these in +// place as a hidden compatibility for a couple of months. +// The AppIdentityProxy warns in case any of these methods are called. + /** * An implementation of the IdentityApi that is constructed using * various backstage user identity representations. @@ -49,7 +54,24 @@ export class UserIdentity implements IdentityApi { * * @public */ - static fromLegacy(result: SignInResult): IdentityApi { + static fromLegacy(result: { + /** + * User ID that will be returned by the IdentityApi + */ + userId: string; + + profile: ProfileInfo; + + /** + * Function used to retrieve an ID token for the signed in user. + */ + getIdToken?: () => Promise; + + /** + * Sign out handler that will be called if the user requests to sign out. + */ + signOut?: () => Promise; + }): IdentityApi { return LegacyUserIdentity.fromResult(result); } diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index de25d24660..875c27dae9 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -12,7 +12,6 @@ import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api'; import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api'; import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; -import { ProfileInfo as ProfileInfo_2 } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; @@ -512,9 +511,6 @@ export type IconComponent = ComponentType<{ // @public export type IdentityApi = { - getUserId(): string; - getIdToken(): Promise; - getProfile(): ProfileInfo; getProfileInfo(): Promise; getBackstageIdentity(): Promise; getCredentials(): Promise<{ @@ -732,14 +728,6 @@ export type SignInPageProps = { onSignInSuccess(identityApi: IdentityApi_2): void; }; -// @public @deprecated -export type SignInResult = { - userId: string; - profile: ProfileInfo_2; - getIdToken?: () => Promise; - signOut?: () => Promise; -}; - // @public export interface StorageApi { forBucket(name: string): StorageApi; diff --git a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts index fe4de89106..1b127a1971 100644 --- a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts @@ -22,31 +22,6 @@ import { BackstageUserIdentity, ProfileInfo } from './auth'; * @public */ 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. - * - * @deprecated use {@link IdentityApi.getBackstageIdentity} instead. - */ - getUserId(): string; - - /** - * 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. */ diff --git a/packages/core-plugin-api/src/app/types.ts b/packages/core-plugin-api/src/app/types.ts index df78728a21..2a71eda1f4 100644 --- a/packages/core-plugin-api/src/app/types.ts +++ b/packages/core-plugin-api/src/app/types.ts @@ -20,7 +20,6 @@ // eslint-disable-next-line no-restricted-imports export type { BootErrorPageProps, - SignInResult, SignInPageProps, ErrorBoundaryFallbackProps, AppComponents,