diff --git a/packages/app/package.json b/packages/app/package.json index 3980d6b507..9401ca89be 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -48,6 +48,7 @@ "@backstage/plugin-airbrake": "workspace:^", "@backstage/plugin-apache-airflow": "workspace:^", "@backstage/plugin-api-docs": "workspace:^", + "@backstage/plugin-auth-react": "workspace:^", "@backstage/plugin-azure-devops": "workspace:^", "@backstage/plugin-azure-sites": "workspace:^", "@backstage/plugin-badges": "workspace:^", diff --git a/packages/app/src/index-public-experimental.tsx b/packages/app/src/index-public-experimental.tsx index 59768a2dd1..59f89c1157 100644 --- a/packages/app/src/index-public-experimental.tsx +++ b/packages/app/src/index-public-experimental.tsx @@ -21,16 +21,14 @@ import { OAuthRequestDialog, SignInPage, } from '@backstage/core-components'; +import { RedirectToRoot } from '@backstage/plugin-auth-react'; import React from 'react'; -import useAsync from 'react-use/lib/useAsync'; import ReactDOM from 'react-dom/client'; import { providers } from '../src/identityProviders'; import { configApiRef, createApiFactory, discoveryApiRef, - identityApiRef, - useApi, } from '@backstage/core-plugin-api'; import { AuthProxyDiscoveryApi } from '../src/AuthProxyDiscoveryApi'; @@ -56,36 +54,6 @@ const app = createApp({ }, }); -function RedirectToRoot() { - const identityApi = useApi(identityApiRef); - - const { value, loading, error } = useAsync(async () => { - const { token } = await identityApi.getCredentials(); - if (!token) { - throw new Error('Expected Backstage token in sign-in response'); - } - return token; - }, [identityApi]); - - if (loading) { - return null; - } else if (error) { - return <>An error occurred: {error}; - } - - return ( -
form?.submit()} - action={window.location.href} - method="POST" - > - - - -
- ); -} - const App = app.createRoot( <> diff --git a/plugins/auth-react/api-report.md b/plugins/auth-react/api-report.md index 01ec9b028a..7f1489543b 100644 --- a/plugins/auth-react/api-report.md +++ b/plugins/auth-react/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { default as React_2 } from 'react'; import { ReactNode } from 'react'; // @public @@ -17,6 +18,9 @@ export type CookieAuthRefreshProviderProps = { children: ReactNode; }; +// @public +export function RedirectToRoot(): React_2.JSX.Element | null; + // @public export function useCookieAuthRefresh(options: { pluginId: string; diff --git a/plugins/auth-react/src/components/RedirectToRoot/RedirectToRoot.test.tsx b/plugins/auth-react/src/components/RedirectToRoot/RedirectToRoot.test.tsx new file mode 100644 index 0000000000..d1171a4f81 --- /dev/null +++ b/plugins/auth-react/src/components/RedirectToRoot/RedirectToRoot.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2024 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 React from 'react'; +import { screen, waitFor } from '@testing-library/react'; +import { TestApiProvider, renderInTestApp } from '@backstage/test-utils'; +import { identityApiRef } from '@backstage/core-plugin-api'; +import { RedirectToRoot } from './RedirectToRoot'; + +describe('RedirectToRoot', () => { + const identityApiMock = { getCredentials: jest.fn() }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render an error message if token is not available', async () => { + identityApiMock.getCredentials.mockResolvedValue({ token: undefined }); + + await renderInTestApp( + + + , + ); + + await waitFor(() => + expect( + screen.getByText( + 'An error occurred: Expected Backstage token in sign-in response', + ), + ).toBeInTheDocument(), + ); + }); + + it('should render a form with token if token is available', async () => { + identityApiMock.getCredentials.mockResolvedValue({ token: 'test-token' }); + + await renderInTestApp( + + + , + ); + + await waitFor(() => + expect(screen.getByText('Continue')).toBeInTheDocument(), + ); + + expect(screen.getByDisplayValue('sign-in')).toBeInTheDocument(); + expect(screen.getByDisplayValue('test-token')).toBeInTheDocument(); + }); +}); diff --git a/plugins/auth-react/src/components/RedirectToRoot/RedirectToRoot.tsx b/plugins/auth-react/src/components/RedirectToRoot/RedirectToRoot.tsx new file mode 100644 index 0000000000..86b40b44e1 --- /dev/null +++ b/plugins/auth-react/src/components/RedirectToRoot/RedirectToRoot.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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 React from 'react'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; +import { useAsync, useMountEffect } from '@react-hookz/web'; + +/** + * @public + * A component that redirects to the root of the app after a successful sign-in. + */ +export function RedirectToRoot() { + const identityApi = useApi(identityApiRef); + + const [state, actions] = useAsync(async () => { + const { token } = await identityApi.getCredentials(); + if (!token) { + throw new Error('Expected Backstage token in sign-in response'); + } + return token; + }); + + useMountEffect(actions.execute); + + if (state.status === 'error' && state.error) { + return <>An error occurred: {state.error.message}; + } + + if (state.status === 'success' && state.result) { + return ( +
form?.submit()} + action={window.location.href} + method="POST" + > + + + +
+ ); + } + + return null; +} diff --git a/plugins/auth-react/src/components/RedirectToRoot/index.ts b/plugins/auth-react/src/components/RedirectToRoot/index.ts new file mode 100644 index 0000000000..1e9e211adc --- /dev/null +++ b/plugins/auth-react/src/components/RedirectToRoot/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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. + */ + +export { RedirectToRoot } from './RedirectToRoot'; diff --git a/plugins/auth-react/src/components/index.ts b/plugins/auth-react/src/components/index.ts index f561672096..c744c9b0d8 100644 --- a/plugins/auth-react/src/components/index.ts +++ b/plugins/auth-react/src/components/index.ts @@ -17,4 +17,5 @@ // The index file in ./components/ is typically responsible for selecting // which components are public API and should be exported from the package. +export * from './RedirectToRoot'; export * from './CookieAuthRefreshProvider'; diff --git a/yarn.lock b/yarn.lock index 9c7429a3c5..ce9665c22d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5118,6 +5118,7 @@ __metadata: "@testing-library/react": ^14.0.0 "@testing-library/user-event": ^14.0.0 "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + msw: ^1.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 languageName: unknown @@ -27441,6 +27442,7 @@ __metadata: "@backstage/plugin-airbrake": "workspace:^" "@backstage/plugin-apache-airflow": "workspace:^" "@backstage/plugin-api-docs": "workspace:^" + "@backstage/plugin-auth-react": "workspace:^" "@backstage/plugin-azure-devops": "workspace:^" "@backstage/plugin-azure-sites": "workspace:^" "@backstage/plugin-badges": "workspace:^"