mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core,experience): extract sso register api (#4934)
* refactor(core,experience): extract sso register api extract sso register api * feat(core): add signInMode guard to sso register api (#4935) add signInMode guard to sso register api * chore(core): adjust the comments adjust the comments
This commit is contained in:
parent
24972ce46e
commit
6d57a58d68
8 changed files with 289 additions and 90 deletions
|
@ -1,4 +1,4 @@
|
|||
import { InteractionEvent } from '@logto/schemas';
|
||||
import { InteractionEvent, SignInMode } from '@logto/schemas';
|
||||
import type Router from 'koa-router';
|
||||
import { type IRouterParamContext } from 'koa-router';
|
||||
import { z } from 'zod';
|
||||
|
@ -13,19 +13,22 @@ import assertThat from '#src/utils/assert-that.js';
|
|||
import { interactionPrefix, ssoPath } from './const.js';
|
||||
import type { WithInteractionDetailsContext } from './middleware/koa-interaction-details.js';
|
||||
import koaInteractionHooks from './middleware/koa-interaction-hooks.js';
|
||||
import koaInteractionSie from './middleware/koa-interaction-sie.js';
|
||||
import { getInteractionStorage, storeInteractionResult } from './utils/interaction.js';
|
||||
import { getSingleSignOnAuthenticationResult } from './utils/single-sign-on-session.js';
|
||||
import {
|
||||
authorizationUrlPayloadGuard,
|
||||
getSsoAuthorizationUrl,
|
||||
getSsoAuthentication,
|
||||
handleSsoAuthentication,
|
||||
registerWithSsoAuthentication,
|
||||
} from './utils/single-sign-on.js';
|
||||
|
||||
export default function singleSignOnRoutes<T extends IRouterParamContext>(
|
||||
router: Router<unknown, WithInteractionDetailsContext<WithLogContext<T>>>,
|
||||
tenant: TenantContext
|
||||
) {
|
||||
const { provider, libraries } = tenant;
|
||||
const { provider, libraries, queries } = tenant;
|
||||
|
||||
const { ssoConnectors: ssoConnectorsLibrary } = libraries;
|
||||
|
||||
|
@ -115,6 +118,58 @@ export default function singleSignOnRoutes<T extends IRouterParamContext>(
|
|||
}
|
||||
);
|
||||
|
||||
// Register a new user with the given SSO connector authentication result
|
||||
router.post(
|
||||
`${interactionPrefix}/${ssoPath}/:connectorId/registration`,
|
||||
koaGuard({
|
||||
params: z.object({
|
||||
connectorId: z.string(),
|
||||
}),
|
||||
status: [200, 404, 403],
|
||||
response: z.object({
|
||||
redirectTo: z.string(),
|
||||
}),
|
||||
}),
|
||||
koaInteractionSie(queries),
|
||||
koaInteractionHooks(libraries),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
createLog,
|
||||
assignInteractionHookResult,
|
||||
guard: { params },
|
||||
} = ctx;
|
||||
const {
|
||||
signInExperience: { signInMode },
|
||||
} = ctx;
|
||||
|
||||
assertThat(
|
||||
signInMode !== SignInMode.SignIn,
|
||||
new RequestError({ code: 'auth.forbidden', status: 403 })
|
||||
);
|
||||
|
||||
const registerEventUpdateLog = createLog(`Interaction.Register.Update`);
|
||||
registerEventUpdateLog.append({ event: 'register' });
|
||||
|
||||
// Update the interaction session event to register if no related user account found.
|
||||
// Set the merge flag to true to merge the register event with the existing sso interaction session
|
||||
await storeInteractionResult({ event: InteractionEvent.Register }, ctx, provider, true);
|
||||
|
||||
// Throw 404 if no related session found
|
||||
const authenticationResult = await getSingleSignOnAuthenticationResult(
|
||||
ctx,
|
||||
provider,
|
||||
params.connectorId
|
||||
);
|
||||
|
||||
const accountId = await registerWithSsoAuthentication(ctx, tenant, authenticationResult);
|
||||
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId } });
|
||||
assignInteractionHookResult({ userId: accountId });
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
// Get the available SSO connectors for the user to choose from by a given email
|
||||
router.get(
|
||||
`${interactionPrefix}/${ssoPath}/connectors`,
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import { type SocialUserInfo } from '@logto/connector-kit';
|
||||
import { type IdentifierPayload } from '@logto/schemas';
|
||||
import { type Context } from 'koa';
|
||||
import type Provider from 'oidc-provider';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { type SsoConnectorLibrary } from '#src/libraries/sso-connector.js';
|
||||
import {
|
||||
type SingleSignOnConnectorSession,
|
||||
singleSignOnConnectorSessionGuard,
|
||||
} from '#src/sso/types/session.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
// Guard the SSO only email identifier
|
||||
|
@ -53,35 +46,3 @@ export const verifySsoOnlyEmailIdentifier = async (
|
|||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the single sign on session data from the oidc provider session storage.
|
||||
*
|
||||
* @remark Forked from ./social-verification.ts.
|
||||
* Use SingleSignOnSession guard instead of ConnectorSession guard.
|
||||
*/
|
||||
export const getSingleSignOnSessionResult = async (
|
||||
ctx: Context,
|
||||
provider: Provider
|
||||
): Promise<SingleSignOnConnectorSession> => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
const singleSignOnSessionResult = z
|
||||
.object({
|
||||
connectorSession: singleSignOnConnectorSessionGuard,
|
||||
})
|
||||
.safeParse(result);
|
||||
|
||||
assertThat(
|
||||
result && singleSignOnSessionResult.success,
|
||||
'session.connector_validation_session_not_found'
|
||||
);
|
||||
|
||||
// Clear the session after the session data is retrieved
|
||||
const { connectorSession, ...rest } = result;
|
||||
await provider.interactionResult(ctx.req, ctx.res, {
|
||||
...rest,
|
||||
});
|
||||
|
||||
return singleSignOnSessionResult.data.connectorSession;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { type Context } from 'koa';
|
||||
import type Provider from 'oidc-provider';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
type SingleSignOnConnectorSession,
|
||||
singleSignOnConnectorSessionGuard,
|
||||
singleSignOnInteractionIdentifierResultGuard,
|
||||
type SingleSignOnInteractionIdentifierResult,
|
||||
} from '#src/sso/types/session.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
/**
|
||||
* Get the single sign on session data from the oidc provider session storage.
|
||||
*
|
||||
* @remark Forked from ./social-verification.ts.
|
||||
* Use SingleSignOnSession guard instead of ConnectorSession guard.
|
||||
*/
|
||||
export const getSingleSignOnSessionResult = async (
|
||||
ctx: Context,
|
||||
provider: Provider
|
||||
): Promise<SingleSignOnConnectorSession> => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
const singleSignOnSessionResult = z
|
||||
.object({
|
||||
connectorSession: singleSignOnConnectorSessionGuard,
|
||||
})
|
||||
.safeParse(result);
|
||||
|
||||
assertThat(
|
||||
result && singleSignOnSessionResult.success,
|
||||
'session.connector_validation_session_not_found'
|
||||
);
|
||||
|
||||
// Clear the session after the session data is retrieved
|
||||
const { connectorSession, ...rest } = result;
|
||||
await provider.interactionResult(ctx.req, ctx.res, {
|
||||
...rest,
|
||||
});
|
||||
|
||||
return singleSignOnSessionResult.data.connectorSession;
|
||||
};
|
||||
|
||||
export const assignSingleSignOnAuthenticationResult = async (
|
||||
ctx: Context,
|
||||
provider: Provider,
|
||||
singleSignOnIdentifier: SingleSignOnInteractionIdentifierResult['singleSignOnIdentifier']
|
||||
) => {
|
||||
const details = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
await provider.interactionResult(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
{ ...details.result, singleSignOnIdentifier },
|
||||
{ mergeWithLastSubmission: true }
|
||||
);
|
||||
};
|
||||
|
||||
export const getSingleSignOnAuthenticationResult = async (
|
||||
ctx: Context,
|
||||
provider: Provider,
|
||||
connectorId: string
|
||||
): Promise<SingleSignOnInteractionIdentifierResult['singleSignOnIdentifier']> => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
const singleSignOnInteractionIdentifierResult =
|
||||
singleSignOnInteractionIdentifierResultGuard.safeParse(result);
|
||||
|
||||
assertThat(
|
||||
singleSignOnInteractionIdentifierResult.success,
|
||||
'session.connector_session_not_found'
|
||||
);
|
||||
|
||||
const { singleSignOnIdentifier } = singleSignOnInteractionIdentifierResult.data;
|
||||
|
||||
assertThat(
|
||||
singleSignOnIdentifier.connectorId === connectorId,
|
||||
'session.connector_session_not_found'
|
||||
);
|
||||
|
||||
return singleSignOnIdentifier;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { InteractionEvent, type SsoConnector } from '@logto/schemas';
|
||||
import { type SsoConnector } from '@logto/schemas';
|
||||
import { createMockUtils } from '@logto/shared/esm';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
|
@ -27,9 +27,7 @@ const updateUserMock = jest.fn();
|
|||
const findUserByEmailMock = jest.fn();
|
||||
const insertUserMock = jest.fn();
|
||||
const generateUserIdMock = jest.fn().mockResolvedValue('foo');
|
||||
const storeInteractionResultMock = jest.fn();
|
||||
const getAvailableSsoConnectorsMock = jest.fn();
|
||||
const getSingleSignOnSessionResultMock = jest.fn();
|
||||
|
||||
class MockOidcSsoConnector extends OidcSsoConnector {
|
||||
override getAuthorizationUrl = getAuthorizationUrlMock;
|
||||
|
@ -37,21 +35,28 @@ class MockOidcSsoConnector extends OidcSsoConnector {
|
|||
override getUserInfo = getUserInfoMock;
|
||||
}
|
||||
|
||||
mockEsm('./interaction.js', () => ({
|
||||
storeInteractionResult: storeInteractionResultMock,
|
||||
const { storeInteractionResult: storeInteractionResultMock } = mockEsm('./interaction.js', () => ({
|
||||
storeInteractionResult: jest.fn(),
|
||||
}));
|
||||
|
||||
mockEsm('./single-sign-on-guard.js', () => ({
|
||||
const {
|
||||
getSingleSignOnSessionResult: getSingleSignOnSessionResultMock,
|
||||
assignSingleSignOnAuthenticationResult: assignSingleSignOnAuthenticationResultMock,
|
||||
} = mockEsm('./single-sign-on-session.js', () => ({
|
||||
getSingleSignOnSessionResult: jest.fn(),
|
||||
assignSingleSignOnAuthenticationResult: jest.fn(),
|
||||
}));
|
||||
|
||||
jest
|
||||
.spyOn(ssoConnectorFactories.OIDC, 'constructor')
|
||||
.mockImplementation((data: SsoConnector) => new MockOidcSsoConnector(data));
|
||||
|
||||
const { getSsoAuthorizationUrl, getSsoAuthentication, handleSsoAuthentication } = await import(
|
||||
'./single-sign-on.js'
|
||||
);
|
||||
const {
|
||||
getSsoAuthorizationUrl,
|
||||
getSsoAuthentication,
|
||||
handleSsoAuthentication,
|
||||
registerWithSsoAuthentication,
|
||||
} = await import('./single-sign-on.js');
|
||||
|
||||
describe('Single sign on util methods tests', () => {
|
||||
const mockContext: WithLogContext & WithInteractionDetailsContext = {
|
||||
|
@ -88,6 +93,14 @@ describe('Single sign on util methods tests', () => {
|
|||
}
|
||||
);
|
||||
|
||||
const mockIssuer = 'https://example.com';
|
||||
const mockSsoUserInfo = {
|
||||
id: 'identityId',
|
||||
email: 'foo@logto.io',
|
||||
name: 'foo',
|
||||
avatar: 'https://example.com',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
@ -140,7 +153,7 @@ describe('Single sign on util methods tests', () => {
|
|||
|
||||
it('should return the authentication result', async () => {
|
||||
const session = {
|
||||
connectorId: 'connectorId',
|
||||
connectorId: wellConfiguredSsoConnector.id,
|
||||
jti: 'jti',
|
||||
redirectUri: 'https://example.com',
|
||||
state: 'state',
|
||||
|
@ -169,18 +182,15 @@ describe('Single sign on util methods tests', () => {
|
|||
issuer: 'https://example.com',
|
||||
userInfo: { id: 'id', email: 'email' },
|
||||
});
|
||||
|
||||
expect(assignSingleSignOnAuthenticationResultMock).toBeCalledWith(mockContext, mockProvider, {
|
||||
connectorId: wellConfiguredSsoConnector.id,
|
||||
...result,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSsoAuthentication tests', () => {
|
||||
const mockIssuer = 'https://example.com';
|
||||
const mockSsoUserInfo = {
|
||||
id: 'identityId',
|
||||
email: 'foo@logto.io',
|
||||
name: 'foo',
|
||||
avatar: 'https://example.com',
|
||||
};
|
||||
|
||||
it('should signIn directly if the user is found', async () => {
|
||||
findUserSsoIdentityBySsoIdentityIdMock.mockResolvedValueOnce({
|
||||
id: 'ssoIdentityId',
|
||||
|
@ -255,30 +265,39 @@ describe('Single sign on util methods tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should register if no related user account found', async () => {
|
||||
it('should throw if no related user account found', async () => {
|
||||
findUserSsoIdentityBySsoIdentityIdMock.mockResolvedValueOnce(null);
|
||||
findUserByEmailMock.mockResolvedValueOnce(null);
|
||||
insertUserMock.mockResolvedValueOnce({ id: 'foo' });
|
||||
|
||||
const accountId = await handleSsoAuthentication(
|
||||
mockContext,
|
||||
tenant,
|
||||
wellConfiguredSsoConnector,
|
||||
{
|
||||
await expect(async () =>
|
||||
handleSsoAuthentication(mockContext, tenant, mockSsoConnector, {
|
||||
issuer: mockIssuer,
|
||||
userInfo: mockSsoUserInfo,
|
||||
}
|
||||
})
|
||||
).rejects.toMatchObject(
|
||||
new RequestError(
|
||||
{
|
||||
code: 'user.identity_not_exist',
|
||||
status: 422,
|
||||
},
|
||||
{}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerWithSsoAuthentication tests', () => {
|
||||
it('should register if no related user account found', async () => {
|
||||
insertUserMock.mockResolvedValueOnce({ id: 'foo' });
|
||||
|
||||
const accountId = await registerWithSsoAuthentication(mockContext, tenant, {
|
||||
connectorId: wellConfiguredSsoConnector.id,
|
||||
issuer: mockIssuer,
|
||||
userInfo: mockSsoUserInfo,
|
||||
});
|
||||
|
||||
expect(accountId).toBe('foo');
|
||||
|
||||
// Should update the interaction session event to register
|
||||
expect(storeInteractionResultMock).toBeCalledWith(
|
||||
{ event: InteractionEvent.Register },
|
||||
mockContext,
|
||||
mockProvider
|
||||
);
|
||||
|
||||
// Should create new user
|
||||
expect(insertUserMock).toBeCalledWith(
|
||||
{
|
||||
|
|
|
@ -19,8 +19,10 @@ import type Queries from '#src/tenants/Queries.js';
|
|||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import { storeInteractionResult } from './interaction.js';
|
||||
import { getSingleSignOnSessionResult } from './single-sign-on-guard.js';
|
||||
import {
|
||||
getSingleSignOnSessionResult,
|
||||
assignSingleSignOnAuthenticationResult,
|
||||
} from './single-sign-on-session.js';
|
||||
import { assignConnectorSessionResult } from './social-verification.js';
|
||||
|
||||
export const authorizationUrlPayloadGuard = z.object({
|
||||
|
@ -109,6 +111,12 @@ export const getSsoAuthentication = async (
|
|||
userInfo,
|
||||
};
|
||||
|
||||
// Assign the single sign on authentication to the interaction result
|
||||
await assignSingleSignOnAuthenticationResult(ctx, provider, {
|
||||
connectorId,
|
||||
...result,
|
||||
});
|
||||
|
||||
log.append({ issuer, userInfo });
|
||||
|
||||
return result;
|
||||
|
@ -160,13 +168,13 @@ export const handleSsoAuthentication = async (
|
|||
});
|
||||
}
|
||||
|
||||
// Update the interaction session event to register if no related user account found
|
||||
const registerEventUpdateLog = createLog(`Interaction.Register.Update`);
|
||||
registerEventUpdateLog.append({ event: 'register' });
|
||||
await storeInteractionResult({ event: InteractionEvent.Register }, ctx, provider);
|
||||
|
||||
// Register
|
||||
return registerWithSsoAuthentication(ctx, tenant, connectorData, ssoAuthentication);
|
||||
throw new RequestError(
|
||||
{
|
||||
code: 'user.identity_not_exist',
|
||||
status: 422,
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
const signInWithSsoAuthentication = async (
|
||||
|
@ -281,18 +289,17 @@ const signInAndLinkWithSsoAuthentication = async (
|
|||
return userId;
|
||||
};
|
||||
|
||||
const registerWithSsoAuthentication = async (
|
||||
export const registerWithSsoAuthentication = async (
|
||||
ctx: WithLogContext,
|
||||
{
|
||||
queries: { userSsoIdentities: userSsoIdentitiesQueries },
|
||||
libraries: { users: usersLibrary },
|
||||
}: TenantContext,
|
||||
{ id: connectorId }: SupportedSsoConnector,
|
||||
ssoAuthentication: SsoAuthenticationResult
|
||||
ssoAuthentication: SsoAuthenticationResult & { connectorId: string }
|
||||
) => {
|
||||
const { createLog } = ctx;
|
||||
const log = createLog(`Interaction.Register.Submit`);
|
||||
const { issuer, userInfo } = ssoAuthentication;
|
||||
const { issuer, userInfo, connectorId } = ssoAuthentication;
|
||||
|
||||
// Only sync the name, avatar and email (conflict email account will be guarded ahead)
|
||||
const syncingProfile = {
|
||||
|
|
|
@ -38,3 +38,24 @@ export const samlConnectorAssertionSessionGuard = z.object({
|
|||
export type CreateSingleSignOnSession = (storage: SingleSignOnConnectorSession) => Promise<void>;
|
||||
|
||||
export type GetSingleSignOnSession = () => Promise<SingleSignOnConnectorSession>;
|
||||
|
||||
/**
|
||||
* Single sign on interaction identifier session
|
||||
*
|
||||
* @remark this session is used to store the authentication result from the identity provider. {@link /packages/core/src/routes/interaction/utils/single-sign-on.ts}
|
||||
* This session is needed because we need to split the authentication process into sign in and sign up two parts.
|
||||
* If the SSO identity is found in DB we will directly sign in the user.
|
||||
* If the SSO identity is not found in DB we will throw an error and let the client to create a new user.
|
||||
* In the SSO registration endpoint, we will validate this session data and create a new user accordingly.
|
||||
*/
|
||||
export const singleSignOnInteractionIdentifierResultGuard = z.object({
|
||||
singleSignOnIdentifier: z.object({
|
||||
connectorId: z.string(),
|
||||
issuer: z.string(),
|
||||
userInfo: extendedSocialUserInfoGuard,
|
||||
}),
|
||||
});
|
||||
|
||||
export type SingleSignOnInteractionIdentifierResult = z.infer<
|
||||
typeof singleSignOnInteractionIdentifierResultGuard
|
||||
>;
|
||||
|
|
|
@ -38,3 +38,6 @@ export const singleSignOnAuthorization = async (connectorId: string, payload: un
|
|||
json: payload,
|
||||
})
|
||||
.json<Response>();
|
||||
|
||||
export const singleSignOnRegistration = async (connectorId: string) =>
|
||||
api.post(`${ssoPrefix}/${connectorId}/registration`).json<Response>();
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
import { SignInMode } from '@logto/schemas';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { singleSignOnAuthorization } from '@/apis/single-sign-on';
|
||||
import { singleSignOnAuthorization, singleSignOnRegistration } from '@/apis/single-sign-on';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useErrorHandler from '@/hooks/use-error-handler';
|
||||
import { useSieMethods } from '@/hooks/use-sie';
|
||||
import useTerms from '@/hooks/use-terms';
|
||||
import useToast from '@/hooks/use-toast';
|
||||
import { parseQueryParameters } from '@/utils';
|
||||
import { stateValidation } from '@/utils/social-connectors';
|
||||
|
||||
const useSingleSignOnRegister = () => {
|
||||
const handleError = useErrorHandler();
|
||||
const request = useApi(singleSignOnRegistration);
|
||||
const { termsValidation } = useTerms();
|
||||
|
||||
return useCallback(
|
||||
async (connectorId: string) => {
|
||||
// Agree to terms and conditions first before proceeding
|
||||
if (!(await termsValidation())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [error, result] = await request(connectorId);
|
||||
|
||||
if (error) {
|
||||
await handleError(error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (result?.redirectTo) {
|
||||
window.location.replace(result.redirectTo);
|
||||
}
|
||||
},
|
||||
[handleError, request, termsValidation]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Single Sign On authentication callback handler.
|
||||
*
|
||||
|
@ -24,10 +55,12 @@ const useSingleSignOnListener = (connectorId: string) => {
|
|||
const [isConsumed, setIsConsumed] = useState(false);
|
||||
const [searchParameters, setSearchParameters] = useSearchParams();
|
||||
const { setToast } = useToast();
|
||||
const { signInMode } = useSieMethods();
|
||||
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const singleSignOnAuthorizationRequest = useApi(singleSignOnAuthorization);
|
||||
const registerSingleSignOnIdentity = useSingleSignOnRegister();
|
||||
|
||||
const singleSignOnHandler = useCallback(
|
||||
async (connectorId: string, data: Record<string, unknown>) => {
|
||||
|
@ -38,7 +71,18 @@ const useSingleSignOnListener = (connectorId: string) => {
|
|||
});
|
||||
|
||||
if (error) {
|
||||
await handleError(error);
|
||||
await handleError(error, {
|
||||
'user.identity_not_exist': async (error) => {
|
||||
// Should not let user register new social account under sign-in only mode
|
||||
if (signInMode === SignInMode.SignIn) {
|
||||
setToast(error.message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await registerSingleSignOnIdentity(connectorId);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -46,7 +90,13 @@ const useSingleSignOnListener = (connectorId: string) => {
|
|||
window.location.replace(result.redirectTo);
|
||||
}
|
||||
},
|
||||
[handleError, singleSignOnAuthorizationRequest]
|
||||
[
|
||||
handleError,
|
||||
registerSingleSignOnIdentity,
|
||||
setToast,
|
||||
signInMode,
|
||||
singleSignOnAuthorizationRequest,
|
||||
]
|
||||
);
|
||||
|
||||
// Single Sign On Callback Handler
|
||||
|
|
Loading…
Reference in a new issue