From b0f0dc41b01e980c450a8cd57b0c0025fcb2c68d Mon Sep 17 00:00:00 2001 From: simeng-li Date: Fri, 12 Jul 2024 18:04:49 +0800 Subject: [PATCH] feat(core): implement get sso connectors implement get sso connectors endpoint --- .../sign-in-experience-validator.ts | 36 ++++++++------- .../enterprise-sso-verification.ts | 32 +++++++++++++ .../src/client/experience/index.ts | 9 ++++ .../enterprise-sso-verification.test.ts | 46 +++++++++++++++++++ 4 files changed, 106 insertions(+), 17 deletions(-) diff --git a/packages/core/src/routes/experience/classes/validators/sign-in-experience-validator.ts b/packages/core/src/routes/experience/classes/validators/sign-in-experience-validator.ts index 26f095f3a91..5bd7935ce2b 100644 --- a/packages/core/src/routes/experience/classes/validators/sign-in-experience-validator.ts +++ b/packages/core/src/routes/experience/classes/validators/sign-in-experience-validator.ts @@ -71,7 +71,7 @@ export class SignInExperienceValidator { } } - async verifyIdentificationMethod( + public async verifyIdentificationMethod( event: InteractionEvent, verificationRecord: VerificationRecord ) { @@ -93,7 +93,21 @@ export class SignInExperienceValidator { await this.guardSsoOnlyEmailIdentifier(verificationRecord); } - private async getSignInExperienceData() { + public async getEnabledSsoConnectorsByEmail(email: string) { + const domain = email.split('@')[1]; + const { singleSignOnEnabled } = await this.getSignInExperienceData(); + + if (!singleSignOnEnabled || !domain) { + return []; + } + + const { getAvailableSsoConnectors } = this.libraries.ssoConnectors; + const availableSsoConnectors = await getAvailableSsoConnectors(); + + return availableSsoConnectors.filter(({ domains }) => domains.includes(domain)); + } + + public async getSignInExperienceData() { this.signInExperienceDataCache ||= await this.queries.signInExperiences.findDefaultSignInExperience(); @@ -117,29 +131,17 @@ export class SignInExperienceValidator { return; } - const domain = emailIdentifier.split('@')[1]; - const { singleSignOnEnabled } = await this.getSignInExperienceData(); - - if (!singleSignOnEnabled || !domain) { - return; - } - - const { getAvailableSsoConnectors } = this.libraries.ssoConnectors; - const availableSsoConnectors = await getAvailableSsoConnectors(); - - const domainEnabledConnectors = availableSsoConnectors.filter(({ domains }) => - domains.includes(domain) - ); + const enabledSsoConnectors = await this.getEnabledSsoConnectorsByEmail(emailIdentifier); assertThat( - domainEnabledConnectors.length === 0, + enabledSsoConnectors.length === 0, new RequestError( { code: 'session.sso_enabled', status: 422, }, { - ssoConnectors: domainEnabledConnectors, + ssoConnectors: enabledSsoConnectors, } ) ); diff --git a/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts b/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts index 05464cee4c8..bdb1d86e704 100644 --- a/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts +++ b/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts @@ -100,4 +100,36 @@ export default function enterpriseSsoVerificationRoutes { + const { email } = ctx.guard.query; + const { + experienceInteraction: { signInExperienceValidator }, + } = ctx; + + assertThat( + email.split('@')[1], + new RequestError({ code: 'guard.invalid_input', status: 400, email }) + ); + + const connectors = await signInExperienceValidator.getEnabledSsoConnectorsByEmail(email); + + ctx.body = { + connectorIds: connectors.map(({ id }) => id), + }; + + return next(); + } + ); } diff --git a/packages/integration-tests/src/client/experience/index.ts b/packages/integration-tests/src/client/experience/index.ts index 2bacb75b6cc..8a06e389f4f 100644 --- a/packages/integration-tests/src/client/experience/index.ts +++ b/packages/integration-tests/src/client/experience/index.ts @@ -152,6 +152,15 @@ export class ExperienceClient extends MockClient { .json<{ verificationId: string }>(); } + public async getAvailableSsoConnectors(email: string) { + return api + .get(`${experienceRoutes.verification}/sso/connectors`, { + headers: { cookie: this.interactionCookie }, + searchParams: { email }, + }) + .json<{ connectorIds: string[] }>(); + } + public async createTotpSecret() { return api .post(`${experienceRoutes.verification}/totp/secret`, { diff --git a/packages/integration-tests/src/tests/api/experience-api/verifications/enterprise-sso-verification.test.ts b/packages/integration-tests/src/tests/api/experience-api/verifications/enterprise-sso-verification.test.ts index f1b98dfe255..a33a05a0ae8 100644 --- a/packages/integration-tests/src/tests/api/experience-api/verifications/enterprise-sso-verification.test.ts +++ b/packages/integration-tests/src/tests/api/experience-api/verifications/enterprise-sso-verification.test.ts @@ -198,4 +198,50 @@ devFeatureTest.describe('enterprise sso verification', () => { }); }); }); + + describe('getSsoConnectorsByEmail', () => { + const ssoConnectorApi = new SsoConnectorApi(); + const domain = `foo${randomString()}.com`; + + beforeAll(async () => { + await ssoConnectorApi.createMockOidcConnector([domain]); + + await updateSignInExperience({ + singleSignOnEnabled: true, + }); + }); + + afterAll(async () => { + await ssoConnectorApi.cleanUp(); + }); + + it('should get sso connectors with given email properly', async () => { + const client = await initExperienceClient(); + + const response = await client.getAvailableSsoConnectors('bar@' + domain); + + expect(response.connectorIds.length).toBeGreaterThan(0); + expect(response.connectorIds[0]).toBe(ssoConnectorApi.firstConnectorId); + }); + + it('should return empty array if no sso connectors found', async () => { + const client = await initExperienceClient(); + + const response = await client.getAvailableSsoConnectors('bar@invalid.com'); + + expect(response.connectorIds.length).toBe(0); + }); + + it('should return empty array if sso is not enabled', async () => { + await updateSignInExperience({ + singleSignOnEnabled: false, + }); + + const client = await initExperienceClient(); + + const response = await client.getAvailableSsoConnectors('bar@' + domain); + + expect(response.connectorIds.length).toBe(0); + }); + }); });