From e1caa7bc85b886f6e585ed9b66747ff199d751b6 Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Sun, 28 Jul 2024 14:12:12 -0400 Subject: [PATCH] create auth error api ref Signed-off-by: Stephen Glass --- packages/app-defaults/src/defaults/apis.ts | 9 ++++ .../AuthErrorApi/SignInAuthErrorApi.ts | 41 +++++++++++++++++ .../implementations/AuthErrorApi/index.ts} | 14 +----- .../auth/microsoft/MicrosoftAuth.ts | 4 -- .../implementations/auth/oauth2/OAuth2.ts | 11 +---- .../implementations/auth/saml/SamlAuth.ts | 9 +--- .../src/apis/implementations/index.ts | 1 + packages/core-app-api/src/lib/index.ts | 1 - .../src/layout/SignInPage/SignInPage.tsx | 8 ++-- .../src/layout/SignInPage/providers.tsx | 17 +++++++ .../src/apis/definitions/AuthErrorApi.ts | 45 +++++++++++++++++++ .../src/apis/definitions/auth.ts | 5 --- .../src/apis/definitions/index.ts | 1 + 13 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.ts rename packages/core-app-api/src/{lib/signInAuthError.ts => apis/implementations/AuthErrorApi/index.ts} (56%) create mode 100644 packages/core-plugin-api/src/apis/definitions/AuthErrorApi.ts diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts index 4e9e1a492c..84902b9f52 100644 --- a/packages/app-defaults/src/defaults/apis.ts +++ b/packages/app-defaults/src/defaults/apis.ts @@ -35,6 +35,7 @@ import { createFetchApi, FetchMiddlewares, VMwareCloudAuth, + SignInAuthErrorApi, } from '@backstage/core-app-api'; import { @@ -58,6 +59,7 @@ import { bitbucketServerAuthApiRef, atlassianAuthApiRef, vmwareCloudAuthApiRef, + authErrorApiRef, } from '@backstage/core-plugin-api'; import { permissionApiRef, @@ -287,4 +289,11 @@ export const apis = [ factory: ({ config, discovery, identity }) => IdentityPermissionApi.create({ config, discovery, identity }), }), + createApiFactory({ + api: authErrorApiRef, + deps: { + discovery: discoveryApiRef, + }, + factory: ({ discovery }) => SignInAuthErrorApi.create({ discovery }), + }), ]; diff --git a/packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.ts b/packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.ts new file mode 100644 index 0000000000..0197f253ab --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.ts @@ -0,0 +1,41 @@ +/* + * 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 { DiscoveryApi, AuthErrorApi } from '@backstage/core-plugin-api'; +import { deserializeError } from '@backstage/errors'; + +/** + * The default implementation of the AuthErrorApi, which simply calls auth error endpoint. + * @public + */ +export class SignInAuthErrorApi implements AuthErrorApi { + private constructor(private readonly discoveryApi: DiscoveryApi) {} + + static create(options: { discovery: DiscoveryApi }) { + const { discovery } = options; + return new SignInAuthErrorApi(discovery); + } + + async getSignInAuthError(): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('auth'); + const response = await fetch(`${baseUrl}/.backstage/error`, { + credentials: 'include', + }); + const data = await response.json(); + + return data ? deserializeError(data) : undefined; + } +} diff --git a/packages/core-app-api/src/lib/signInAuthError.ts b/packages/core-app-api/src/apis/implementations/AuthErrorApi/index.ts similarity index 56% rename from packages/core-app-api/src/lib/signInAuthError.ts rename to packages/core-app-api/src/apis/implementations/AuthErrorApi/index.ts index a86a55ee4c..6f3a3ae269 100644 --- a/packages/core-app-api/src/lib/signInAuthError.ts +++ b/packages/core-app-api/src/apis/implementations/AuthErrorApi/index.ts @@ -13,17 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DiscoveryApi } from '@backstage/core-plugin-api'; -import { deserializeError } from '@backstage/errors'; -export async function getSignInAuthError( - discoveryApi: DiscoveryApi, -): Promise { - const baseUrl = await discoveryApi.getBaseUrl('auth'); - const response = await fetch(`${baseUrl}/.backstage/error`, { - credentials: 'include', - }); - const data = await response.json(); - - return data ? deserializeError(data) : undefined; -} +export { SignInAuthErrorApi } from './SignInAuthErrorApi'; diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts index 049022702e..c57c65d830 100644 --- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts @@ -175,10 +175,6 @@ export default class MicrosoftAuth { return this.microsoftGraph().signOut(); } - getSignInAuthError() { - return this.microsoftGraph().getSignInAuthError(); - } - sessionState$() { return this.microsoftGraph().sessionState$(); } diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 1ac5199b8f..dbad031652 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -31,12 +31,10 @@ import { SessionApi, BackstageIdentityApi, BackstageUserIdentity, - DiscoveryApi, } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; import { OAuth2Session } from './types'; import { OAuthApiCreateOptions } from '../types'; -import { getSignInAuthError } from '../../../../lib'; /** * OAuth2 create options. @@ -154,21 +152,18 @@ export default class OAuth2 }, }); - return new OAuth2({ sessionManager, scopeTransform, discoveryApi }); + return new OAuth2({ sessionManager, scopeTransform }); } private readonly sessionManager: SessionManager; private readonly scopeTransform: (scopes: string[]) => string[]; - private readonly discoveryApi: DiscoveryApi; private constructor(options: { sessionManager: SessionManager; scopeTransform: (scopes: string[]) => string[]; - discoveryApi: DiscoveryApi; }) { this.sessionManager = options.sessionManager; this.scopeTransform = options.scopeTransform; - this.discoveryApi = options.discoveryApi; } async signIn() { @@ -229,8 +224,4 @@ export default class OAuth2 return new Set(scopeTransform(scopeList)); } - - async getSignInAuthError() { - return getSignInAuthError(this.discoveryApi); - } } diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts index 325be93c4c..dad62f1d06 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -22,7 +22,6 @@ import { SessionApi, SessionState, BackstageIdentityResponse, - DiscoveryApi, } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; import { DirectAuthConnector } from '../../../../lib/AuthConnector'; @@ -33,7 +32,6 @@ import { import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { AuthApiCreateOptions } from '../types'; import { SamlSession, samlSessionSchema } from './types'; -import { getSignInAuthError } from '../../../../lib'; export type SamlAuthResponse = { profile: ProfileInfo; @@ -77,7 +75,7 @@ export default class SamlAuth schema: samlSessionSchema, }); - return new SamlAuth(authSessionStore, discoveryApi); + return new SamlAuth(authSessionStore); } sessionState$(): Observable { @@ -86,7 +84,6 @@ export default class SamlAuth private constructor( private readonly sessionManager: SessionManager, - private readonly discoveryApi: DiscoveryApi, ) {} async signIn() { @@ -107,8 +104,4 @@ export default class SamlAuth const session = await this.sessionManager.getSession(options); return session?.profile; } - - async getSignInAuthError() { - return getSignInAuthError(this.discoveryApi); - } } diff --git a/packages/core-app-api/src/apis/implementations/index.ts b/packages/core-app-api/src/apis/implementations/index.ts index 1c79d3d164..7643dfcd82 100644 --- a/packages/core-app-api/src/apis/implementations/index.ts +++ b/packages/core-app-api/src/apis/implementations/index.ts @@ -30,3 +30,4 @@ export * from './FeatureFlagsApi'; export * from './FetchApi'; export * from './OAuthRequestApi'; export * from './StorageApi'; +export * from './AuthErrorApi'; diff --git a/packages/core-app-api/src/lib/index.ts b/packages/core-app-api/src/lib/index.ts index d5aa2b9e3b..1327aab4c2 100644 --- a/packages/core-app-api/src/lib/index.ts +++ b/packages/core-app-api/src/lib/index.ts @@ -16,6 +16,5 @@ export * from './subjects'; export * from './loginPopup'; -export * from './signInAuthError'; export * from './AuthConnector'; export * from './AuthSessionManager'; diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index 207d7e664e..f0af0c5d35 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -19,6 +19,7 @@ import { configApiRef, SignInPageProps, useApi, + authErrorApiRef, } from '@backstage/core-plugin-api'; import { UserIdentity } from './UserIdentity'; import Button from '@material-ui/core/Button'; @@ -98,6 +99,7 @@ export const SingleSignInPage = ({ const classes = useStyles(); const authApi = useApi(provider.apiRef); const configApi = useApi(configApiRef); + const authErrorApi = useApi(authErrorApiRef); const { t } = useTranslationRef(coreComponentsTranslationRef); const [error, setError] = useState(); @@ -155,9 +157,9 @@ export const SingleSignInPage = ({ } }; - const [_state, actions] = useAsync(async () => { + const [_, { execute }] = useAsync(async () => { if (searchParams.get('error') !== 'false') { - const errorResponse = await authApi.getSignInAuthError(); + const errorResponse = await authErrorApi.getSignInAuthError(); if (errorResponse) { setError(errorResponse); } @@ -165,7 +167,7 @@ export const SingleSignInPage = ({ }); useMountEffect(() => { - actions.execute(); + execute(); login({ checkExisting: true }); }); diff --git a/packages/core-components/src/layout/SignInPage/providers.tsx b/packages/core-components/src/layout/SignInPage/providers.tsx index e456a6948b..9b73cfc561 100644 --- a/packages/core-components/src/layout/SignInPage/providers.tsx +++ b/packages/core-components/src/layout/SignInPage/providers.tsx @@ -21,6 +21,7 @@ import { useApiHolder, errorApiRef, IdentityApi, + authErrorApiRef, } from '@backstage/core-plugin-api'; import { IdentityProviders, @@ -31,6 +32,8 @@ import { commonProvider } from './commonProvider'; import { guestProvider } from './guestProvider'; import { customProvider } from './customProvider'; import { IdentityApiSignOutProxy } from './IdentityApiSignOutProxy'; +import { useSearchParams } from 'react-router-dom'; +import { useMountEffect, useAsync } from '@react-hookz/web'; const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider'; @@ -86,8 +89,22 @@ export const useSignInProviders = ( ) => { const errorApi = useApi(errorApiRef); const apiHolder = useApiHolder(); + const authErrorApi = useApi(authErrorApiRef); const [loading, setLoading] = useState(true); + const [searchParams, _setSearchParams] = useSearchParams(); + + const [_, { execute }] = useAsync(async () => { + if (searchParams.get('error') !== 'false') { + const errorResponse = await authErrorApi.getSignInAuthError(); + if (errorResponse) { + errorApi.post(errorResponse); + } + } + }); + + useMountEffect(execute); + // This decorates the result with sign out logic from this hook const handleWrappedResult = useCallback( (identityApi: IdentityApi) => { diff --git a/packages/core-plugin-api/src/apis/definitions/AuthErrorApi.ts b/packages/core-plugin-api/src/apis/definitions/AuthErrorApi.ts new file mode 100644 index 0000000000..57c6b1088f --- /dev/null +++ b/packages/core-plugin-api/src/apis/definitions/AuthErrorApi.ts @@ -0,0 +1,45 @@ +/* + * 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 { ApiRef, createApiRef } from '../system'; + +/** + * A wrapper for retrieving any errors caught in authentication redirect flow + * + * @public + */ +export type AuthErrorApi = { + /** + * Get any caught errors during the auth redirect flow + */ + getSignInAuthError(): Promise; +}; + +/** + * The {@link ApiRef} of {@link AuthErrorApi}. + * + * @remarks + * + * This is a wrapper that uses fetch to retrieve any caught errors + * in the authentication redirect flow process. This API calls a + * Backstage endpoint to read any error stored in authentication error + * cookie and returns it to the user. + * + * @public + */ +export const authErrorApiRef: ApiRef = createApiRef({ + id: 'core.authError', +}); diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index c29d965890..d89544cf68 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -291,11 +291,6 @@ export type SessionApi = { */ signOut(): Promise; - /** - * Get any caught errors during the auth redirect flow - */ - getSignInAuthError(): Promise; - /** * Observe the current state of the auth session. Emits the current state on subscription. */ diff --git a/packages/core-plugin-api/src/apis/definitions/index.ts b/packages/core-plugin-api/src/apis/definitions/index.ts index 67d442587d..406dc1980e 100644 --- a/packages/core-plugin-api/src/apis/definitions/index.ts +++ b/packages/core-plugin-api/src/apis/definitions/index.ts @@ -33,3 +33,4 @@ export * from './FetchApi'; export * from './IdentityApi'; export * from './OAuthRequestApi'; export * from './StorageApi'; +export * from './AuthErrorApi';