Add onSignInStarted() and onSignInFailure() hooks. Clean up and renaming.

Signed-off-by: headphonejames <generalfuzz@gmail.com>
This commit is contained in:
headphonejames
2023-02-21 18:45:50 -08:00
parent 564de00947
commit ad15aaa285
9 changed files with 50 additions and 22 deletions
+9 -1
View File
@@ -45,9 +45,17 @@ export type BootErrorPageProps = {
*/
export type SignInPageProps = {
/**
* Set the IdentityApi on successful sign in. This should only be called once.
* Invoked when the sign-in process has started.
*/
onSignInStarted(): void;
/**
* Set the IdentityApi on successful sign-in. This should only be called once.
*/
onSignInSuccess(identityApi: IdentityApi): void;
/**
* Invoked when the sign-in process has failed.
*/
onSignInFailure(): void;
};
/**
@@ -28,21 +28,25 @@ import { useApi, errorApiRef } from '@backstage/core-plugin-api';
import { GridItem } from './styles';
import { ForwardedError } from '@backstage/errors';
import { UserIdentity } from './UserIdentity';
import { PROVIDER_STORAGE_KEY } from './providers';
const Component: ProviderComponent = ({ config, onSignInSuccess }) => {
const { apiRef, title, message, id } = config as SignInProviderConfig;
const Component: ProviderComponent = ({
config,
onSignInStarted,
onSignInSuccess,
onSignInFailure,
}) => {
const { apiRef, title, message } = config as SignInProviderConfig;
const authApi = useApi(apiRef);
const errorApi = useApi(errorApiRef);
const handleLogin = async () => {
try {
localStorage.setItem(PROVIDER_STORAGE_KEY, id);
onSignInStarted();
const identityResponse = await authApi.getBackstageIdentity({
instantPopup: true,
});
if (!identityResponse) {
localStorage.removeItem(PROVIDER_STORAGE_KEY);
onSignInFailure();
throw new Error(
`The ${title} provider is not configured to support sign-in`,
);
@@ -58,7 +62,7 @@ const Component: ProviderComponent = ({ config, onSignInSuccess }) => {
}),
);
} catch (error) {
localStorage.removeItem(PROVIDER_STORAGE_KEY);
onSignInFailure();
errorApi.post(new ForwardedError('Login failed', error));
}
};
@@ -61,7 +61,7 @@ const asInputRef = (renderResult: UseFormRegisterReturn) => {
};
};
const Component: ProviderComponent = ({ onSignInSuccess }) => {
const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => {
const classes = useFormStyles();
const { register, handleSubmit, formState } = useForm<Data>({
mode: 'onChange',
@@ -70,6 +70,7 @@ const Component: ProviderComponent = ({ onSignInSuccess }) => {
const { errors } = formState;
const handleResult = ({ userId, idToken }: Data) => {
onSignInStarted();
onSignInSuccess(
UserIdentity.fromLegacy({
userId,
@@ -22,7 +22,7 @@ import { GridItem } from './styles';
import { ProviderComponent, ProviderLoader, SignInProvider } from './types';
import { GuestUserIdentity } from './GuestUserIdentity';
const Component: ProviderComponent = ({ onSignInSuccess }) => (
const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => (
<GridItem>
<InfoCard
title="Guest"
@@ -31,7 +31,10 @@ const Component: ProviderComponent = ({ onSignInSuccess }) => (
<Button
color="primary"
variant="outlined"
onClick={() => onSignInSuccess(new GuestUserIdentity())}
onClick={() => {
onSignInStarted();
onSignInSuccess(new GuestUserIdentity());
}}
>
Enter
</Button>
@@ -176,11 +176,21 @@ export const useSignInProviders = (
handleWrappedResult(result);
};
const handleSignInStarted = () => {
localStorage.setItem(PROVIDER_STORAGE_KEY, provider.config!.id);
};
const handleSignInFailure = () => {
localStorage.removeItem(PROVIDER_STORAGE_KEY);
};
return (
<Component
key={provider.id}
config={provider.config!}
onSignInStarted={handleSignInStarted}
onSignInSuccess={handleSignInSuccess}
onSignInFailure={handleSignInFailure}
/>
);
}),
@@ -178,7 +178,7 @@ describe('OAuthAdapter', () => {
const state = {
...defaultState,
redirectUrl: 'http://localhost:3000',
authFlow: 'redirect',
flow: 'redirect',
};
const mockRequest = createEncodedQueryMockRequest(state);
@@ -506,7 +506,7 @@ describe('OAuthAdapter', () => {
expect(mockResponse.redirect).not.toHaveBeenCalled();
});
it('executed a response redirect when authFlow query string is set to "redirect"', async () => {
it('executed a response redirect when flow query string is set to "redirect"', async () => {
const handlers = {
start: jest.fn(async (_req: { state: OAuthState }) => ({
url: '/url',
@@ -531,7 +531,7 @@ describe('OAuthAdapter', () => {
...defaultState,
origin: 'http://other.domain',
redirectUrl: 'http://domain.org',
authFlow: 'redirect',
flow: 'redirect',
};
const mockRequest = {
@@ -55,7 +55,6 @@ export type OAuthAdapterOptions = {
providerId: string;
persistScopes?: boolean;
appOrigin: string;
redirectUrl?: string;
baseUrl: string;
cookieConfigurer: CookieConfigurer;
isOriginAllowed: (origin: string) => boolean;
@@ -69,7 +68,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
handlers: OAuthHandlers,
options: Pick<
OAuthAdapterOptions,
'providerId' | 'persistScopes' | 'callbackUrl' | 'redirectUrl'
'providerId' | 'persistScopes' | 'callbackUrl'
>,
): OAuthAdapter {
const { appUrl, baseUrl, isOriginAllowed } = config;
@@ -104,7 +103,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
const env = req.query.env?.toString();
const origin = req.query.origin?.toString();
const redirectUrl = req.query.redirectUrl?.toString();
const authFlow = req.query.authFlow?.toString();
const flow = req.query.authFlow?.toString();
if (!env) {
throw new InputError('No env provided in request query parameters');
}
@@ -115,7 +114,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
// set a nonce cookie before redirecting to oauth provider
this.setNonceCookie(res, nonce, cookieConfig);
const state: OAuthState = { nonce, env, origin, redirectUrl, authFlow };
const state: OAuthState = { nonce, env, origin, redirectUrl, flow: flow };
// If scopes are persisted then we pass them through the state so that we
// can set the cookie on successful auth
@@ -142,7 +141,6 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
try {
const state: OAuthState = readState(req.query.state?.toString() ?? '');
const redirectUrl = state.redirectUrl ?? '';
if (state.origin) {
try {
@@ -181,8 +179,13 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
response: { ...response, backstageIdentity: identity },
};
if (state.authFlow === 'redirect') {
res.redirect(redirectUrl);
if (state.flow === 'redirect') {
if (!state.redirectUrl) {
throw new InputError(
'No redirectUrl provided in request query parameters',
);
}
res.redirect(state.redirectUrl);
}
// post message back to popup if successful
return postMessageResponse(res, appOrigin, responseObj);
+1 -1
View File
@@ -91,7 +91,7 @@ export type OAuthState = {
origin?: string;
scope?: string;
redirectUrl?: string;
authFlow?: string;
flow?: string;
};
/** @public */
@@ -107,7 +107,6 @@ export async function createRouter(
...providerFactories,
};
const providersConfig = config.getConfig('auth.providers');
const configuredProviders = providersConfig.keys();
const isOriginAllowed = createOriginFilter(config);