From 8fe235710154a4f6deaf6173ef15b421a37a62ee Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 Jun 2022 17:53:16 +0200 Subject: [PATCH] core-app-api: navigate to app base URL on signout Signed-off-by: Patrik Oldsberg --- .changeset/nervous-humans-sip.md | 5 ++ .../IdentityApi/AppIdentityProxy.test.ts | 16 +++++- .../IdentityApi/AppIdentityProxy.ts | 9 +++- packages/core-app-api/src/app/AppManager.tsx | 50 +++++++++++-------- 4 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 .changeset/nervous-humans-sip.md diff --git a/.changeset/nervous-humans-sip.md b/.changeset/nervous-humans-sip.md new file mode 100644 index 0000000000..aa1ca2c60a --- /dev/null +++ b/.changeset/nervous-humans-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +The `signOut` method of the `IdentityApi` will now navigate the user back to the base URL of the app as indicated by the `app.baseUrl` config. diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts index aa15182499..3d4f83b05d 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts @@ -31,7 +31,7 @@ describe('AppIdentityProxy', () => { it('should forward user identities', async () => { const proxy = new AppIdentityProxy(); - proxy.setTarget(mockIdentityApi); + proxy.setTarget(mockIdentityApi, { signOutTargetUrl: '/' }); const logs = await withLogCollector(async () => { mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({ @@ -55,7 +55,7 @@ describe('AppIdentityProxy', () => { it('should warn about invalid user entity refs', async () => { const proxy = new AppIdentityProxy(); - proxy.setTarget(mockIdentityApi); + proxy.setTarget(mockIdentityApi, { signOutTargetUrl: '/' }); const logs = await withLogCollector(async () => { mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({ @@ -79,4 +79,16 @@ describe('AppIdentityProxy', () => { error: [], }); }); + + it('should navigate to target URL on sign out', async () => { + const proxy = new AppIdentityProxy(); + proxy.setTarget(mockIdentityApi, { signOutTargetUrl: '/foo' }); + Object.defineProperty(window, 'location', { + writable: true, + value: {}, + }); + + await proxy.signOut(); + expect(location.href).toBe('/foo'); + }); }); 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 3ef3a1f6d3..2df351bc18 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -49,6 +49,7 @@ export class AppIdentityProxy implements IdentityApi { private target?: CompatibilityIdentityApi; private waitForTarget: Promise; private resolveTarget: (api: CompatibilityIdentityApi) => void = () => {}; + private signOutTargetUrl = '/'; constructor() { this.waitForTarget = new Promise(resolve => { @@ -57,8 +58,12 @@ export class AppIdentityProxy implements IdentityApi { } // This is called by the app manager once the sign-in page provides us with an implementation - setTarget(identityApi: CompatibilityIdentityApi) { + setTarget( + identityApi: CompatibilityIdentityApi, + targetOptions: { signOutTargetUrl: string }, + ) { this.target = identityApi; + this.signOutTargetUrl = targetOptions.signOutTargetUrl; this.resolveTarget(identityApi); } @@ -119,6 +124,6 @@ export class AppIdentityProxy implements IdentityApi { async signOut(): Promise { await this.waitForTarget.then(target => target.signOut()); - location.reload(); + location.href = this.signOutTargetUrl; } } diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 75bab5c2df..f47ddf676a 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -334,41 +334,49 @@ export class AppManager implements BackstageApp { children: ReactElement; }) => { const [identityApi, setIdentityApi] = useState(); + const configApi = useApi(configApiRef); + const basePath = getBasePath(configApi); if (!identityApi) { return ; } - this.appIdentityProxy.setTarget(identityApi); + this.appIdentityProxy.setTarget(identityApi, { + signOutTargetUrl: basePath || '/', + }); return children; }; const AppRouter = ({ children }: PropsWithChildren<{}>) => { const configApi = useApi(configApiRef); - const mountPath = `${getBasePath(configApi)}/*`; + const basePath = getBasePath(configApi); + const mountPath = `${basePath}/*`; const { routeObjects } = useContext(InternalAppContext); // If the app hasn't configured a sign-in page, we just continue as guest. if (!SignInPageComponent) { - this.appIdentityProxy.setTarget({ - getUserId: () => 'guest', - getIdToken: async () => undefined, - getProfile: () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getProfileInfo: async () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getBackstageIdentity: async () => ({ - type: 'user', - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }), - getCredentials: async () => ({}), - signOut: async () => {}, - }); + this.appIdentityProxy.setTarget( + { + getUserId: () => 'guest', + getIdToken: async () => undefined, + getProfile: () => ({ + email: 'guest@example.com', + displayName: 'Guest', + }), + getProfileInfo: async () => ({ + email: 'guest@example.com', + displayName: 'Guest', + }), + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/guest', + ownershipEntityRefs: ['user:default/guest'], + }), + getCredentials: async () => ({}), + signOut: async () => {}, + }, + { signOutTargetUrl: basePath || '/' }, + ); return (