create auth error api ref

Signed-off-by: Stephen Glass <stephen@stephen.glass>
This commit is contained in:
Stephen Glass
2024-07-28 14:12:12 -04:00
parent 31edacd9b1
commit e1caa7bc85
13 changed files with 122 additions and 44 deletions
@@ -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 }),
}),
];
@@ -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<Error | undefined> {
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;
}
}
@@ -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<Error | undefined> {
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';
@@ -175,10 +175,6 @@ export default class MicrosoftAuth {
return this.microsoftGraph().signOut();
}
getSignInAuthError() {
return this.microsoftGraph().getSignInAuthError();
}
sessionState$() {
return this.microsoftGraph().sessionState$();
}
@@ -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<OAuth2Session>;
private readonly scopeTransform: (scopes: string[]) => string[];
private readonly discoveryApi: DiscoveryApi;
private constructor(options: {
sessionManager: SessionManager<OAuth2Session>;
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);
}
}
@@ -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<SessionState> {
@@ -86,7 +84,6 @@ export default class SamlAuth
private constructor(
private readonly sessionManager: SessionManager<SamlSession>,
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);
}
}
@@ -30,3 +30,4 @@ export * from './FeatureFlagsApi';
export * from './FetchApi';
export * from './OAuthRequestApi';
export * from './StorageApi';
export * from './AuthErrorApi';
-1
View File
@@ -16,6 +16,5 @@
export * from './subjects';
export * from './loginPopup';
export * from './signInAuthError';
export * from './AuthConnector';
export * from './AuthSessionManager';
@@ -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<Error>();
@@ -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 });
});
@@ -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) => {
@@ -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<Error | undefined>;
};
/**
* 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<AuthErrorApi> = createApiRef({
id: 'core.authError',
});
@@ -291,11 +291,6 @@ export type SessionApi = {
*/
signOut(): Promise<void>;
/**
* Get any caught errors during the auth redirect flow
*/
getSignInAuthError(): Promise<Error | undefined>;
/**
* Observe the current state of the auth session. Emits the current state on subscription.
*/
@@ -33,3 +33,4 @@ export * from './FetchApi';
export * from './IdentityApi';
export * from './OAuthRequestApi';
export * from './StorageApi';
export * from './AuthErrorApi';