From fe16e789ab4fd28ea8a208d16f2dfd22e720ad9e Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Sun, 16 Jun 2024 21:52:32 +0800 Subject: [PATCH] chore: add tests --- packages/core/src/__mocks__/connector.ts | 20 ++++----- .../sign-in-experience/index.test.ts | 44 +++++++++++++++++++ .../utils/social-verification.test.ts | 39 ++++++++++++++-- .../interaction/utils/social-verification.ts | 10 +++-- 4 files changed, 96 insertions(+), 17 deletions(-) diff --git a/packages/core/src/__mocks__/connector.ts b/packages/core/src/__mocks__/connector.ts index ee0a0239184f..46e9ce886f09 100644 --- a/packages/core/src/__mocks__/connector.ts +++ b/packages/core/src/__mocks__/connector.ts @@ -212,10 +212,18 @@ export const mockGoogleConnector: LogtoConnector = { dbEntry: { ...mockConnector, id: 'google', + config: { + clientId: 'fake_client_id', + clientSecret: 'fake_client_secret', + oneTap: { + isEnabled: true, + autoSelect: true, + }, + } }, metadata: { ...mockMetadata, - id: 'google', + id: 'google-universal', target: 'google', platform: ConnectorPlatform.Web, }, @@ -238,16 +246,6 @@ export const mockDemoSocialConnector: LogtoConnector = { ...mockLogtoConnector, }; -export const mockLogtoConnectors = [ - mockAliyunDmConnector, - mockAliyunSmsConnector, - mockFacebookConnector, - mockGithubConnector, - mockGoogleConnector, - mockWechatConnector, - mockWechatNativeConnector, -]; - export const socialTarget01 = 'socialTarget-id01'; export const socialTarget02 = 'socialTarget-id02'; diff --git a/packages/core/src/libraries/sign-in-experience/index.test.ts b/packages/core/src/libraries/sign-in-experience/index.test.ts index 365520059971..4c43db6f8a38 100644 --- a/packages/core/src/libraries/sign-in-experience/index.test.ts +++ b/packages/core/src/libraries/sign-in-experience/index.test.ts @@ -3,6 +3,8 @@ import { builtInLanguages } from '@logto/phrases-experience'; import type { CreateSignInExperience, SignInExperience } from '@logto/schemas'; import { + mockGithubConnector, + mockGoogleConnector, mockSignInExperience, mockSocialConnectors, socialTarget01, @@ -170,6 +172,48 @@ describe('getFullSignInExperience()', () => { googleOneTap: undefined, }); }); + + it('should return full sign-in experience with google one tap', async () => { + findDefaultSignInExperience.mockResolvedValueOnce({ + ...mockSignInExperience, + socialSignInConnectorTargets: ['github', 'facebook', 'google'], + }); + getLogtoConnectors.mockResolvedValueOnce([mockGoogleConnector, mockGithubConnector]); + ssoConnectorLibrary.getAvailableSsoConnectors.mockResolvedValueOnce([ + wellConfiguredSsoConnector, + ]); + + const fullSignInExperience = await getFullSignInExperience('en'); + const connectorFactory = ssoConnectorFactories[wellConfiguredSsoConnector.providerName]; + + expect(fullSignInExperience).toStrictEqual({ + ...mockSignInExperience, + socialConnectors: [ + { ...mockGithubConnector.metadata, id: mockGithubConnector.dbEntry.id }, + { ...mockGoogleConnector.metadata, id: mockGoogleConnector.dbEntry.id }, + ], + socialSignInConnectorTargets: ['github', 'facebook', 'google'], + forgotPassword: { + email: false, + phone: false, + }, + ssoConnectors: [ + { + id: wellConfiguredSsoConnector.id, + connectorName: connectorFactory.name.en, + logo: connectorFactory.logo, + darkLogo: connectorFactory.logoDark, + }, + ], + isDevelopmentTenant: false, + googleOneTap: { + isEnabled: true, + autoSelect: true, + clientId: 'fake_client_id', + connectorId: 'google', + }, + }); + }); }); describe('get sso connectors', () => { diff --git a/packages/core/src/routes/interaction/utils/social-verification.test.ts b/packages/core/src/routes/interaction/utils/social-verification.test.ts index 071181331865..7df4a16a61c5 100644 --- a/packages/core/src/routes/interaction/utils/social-verification.test.ts +++ b/packages/core/src/routes/interaction/utils/social-verification.test.ts @@ -1,4 +1,4 @@ -import { ConnectorType } from '@logto/connector-kit'; +import { ConnectorType, GoogleConnector } from '@logto/connector-kit'; import { createMockUtils } from '@logto/shared/esm'; import type { WithLogContext } from '#src/middleware/koa-audit-log.js'; @@ -27,8 +27,8 @@ mockEsm('#src/libraries/connector.js', () => ({ const { verifySocialIdentity } = await import('./social-verification.js'); -describe('social-verification', () => { - it('verifySocialIdentity', async () => { +describe('verifySocialIdentity', () => { + it('should verify social identity', async () => { // @ts-expect-error test mock context const ctx: WithLogContext = { ...createMockContext(), @@ -41,4 +41,37 @@ describe('social-verification', () => { expect(getUserInfo).toBeCalledWith(connectorId, connectorData, expect.anything()); expect(userInfo).toEqual({ id: 'foo' }); }); + + it('should throw error if csrf token is not matched for Google One Tap verification', async () => { + const ctx: WithLogContext = { + ...createMockContext(), + ...createMockLogContext(), + // @ts-expect-error test mock context + cookies: { get: jest.fn().mockReturnValue('token') }, + }; + const connectorId = GoogleConnector.factoryId; + const connectorData = { credential: 'credential' }; + + await expect(verifySocialIdentity({ connectorId, connectorData }, ctx, tenant)).rejects.toThrow( + 'CSRF token mismatch.' + ); + }); + + it('should verify Google One Tap verification', async () => { + const ctx: WithLogContext = { + ...createMockContext(), + ...createMockLogContext(), + // @ts-expect-error test mock context + cookies: { get: jest.fn().mockReturnValue('token') }, + }; + const connectorId = GoogleConnector.factoryId; + const connectorData = { + [GoogleConnector.oneTapParams.credential]: 'credential', + [GoogleConnector.oneTapParams.csrfToken]: 'token', + }; + + await expect( + verifySocialIdentity({ connectorId, connectorData }, ctx, tenant) + ).resolves.toEqual({ id: 'foo' }); + }); }); diff --git a/packages/core/src/routes/interaction/utils/social-verification.ts b/packages/core/src/routes/interaction/utils/social-verification.ts index 0d74df738898..83a000e732b8 100644 --- a/packages/core/src/routes/interaction/utils/social-verification.ts +++ b/packages/core/src/routes/interaction/utils/social-verification.ts @@ -63,9 +63,13 @@ export const verifySocialIdentity = async ( const log = ctx.createLog('Interaction.SignIn.Identifier.Social.Submit'); log.append({ connectorId, connectorData }); - // Verify Google One Tap CSRF token, if it exists - const csrfToken = connectorData[GoogleConnector.oneTapParams.csrfToken]; - if (csrfToken) { + // Verify the CSRF token if it's a Google connector and has credential (a Google One Tap + // verification) + if ( + connectorId === GoogleConnector.factoryId && + connectorData[GoogleConnector.oneTapParams.credential] + ) { + const csrfToken = connectorData[GoogleConnector.oneTapParams.csrfToken]; const value = ctx.cookies.get(GoogleConnector.oneTapParams.csrfToken); assertThat(value === csrfToken, 'session.csrf_token_mismatch'); }