From 50241829860f8240166076094506aa10ae8b4a9f Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 5 Aug 2024 09:05:08 +0800 Subject: [PATCH] refactor(core): should not guard sso authentication flow (#6394) should not guard mfa and profile fulfillment for the sso authentication flow --- .../classes/experience-interaction.ts | 23 ++++++++-- .../enterprise-sso.test.ts | 45 ++++++++++++++++++- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/packages/core/src/routes/experience/classes/experience-interaction.ts b/packages/core/src/routes/experience/classes/experience-interaction.ts index 1af2e005c..79f7ed0a2 100644 --- a/packages/core/src/routes/experience/classes/experience-interaction.ts +++ b/packages/core/src/routes/experience/classes/experience-interaction.ts @@ -247,10 +247,17 @@ export default class ExperienceInteraction { * Validate the interaction verification records against the sign-in experience and user MFA settings. * The interaction is verified if at least one user enabled MFA verification record is present and verified. * - * @throws — RequestError with 404 if the if the user is not identified or not found + * @remarks + * - EnterpriseSso verified interaction does not require MFA verification. + * + * @throws {RequestError} with 404 if the if the user is not identified or not found * @throws {RequestError} with 403 if the mfa verification is required but not verified */ public async guardMfaVerificationStatus() { + if (this.hasVerifiedSsoIdentity) { + return; + } + const user = await this.getIdentifiedUser(); const mfaSettings = await this.signInExperienceValidator.getMfaSettings(); const mfaValidator = new MfaValidator(mfaSettings, user); @@ -338,13 +345,17 @@ export default class ExperienceInteraction { await this.profile.validateAvailability(); // Profile fulfilled - await this.profile.assertUserMandatoryProfileFulfilled(); + if (!this.hasVerifiedSsoIdentity) { + await this.profile.assertUserMandatoryProfileFulfilled(); + } // Revalidate the new MFA data if any await this.mfa.checkAvailability(); // MFA fulfilled - await this.mfa.assertUserMandatoryMfaFulfilled(); + if (!this.hasVerifiedSsoIdentity) { + await this.mfa.assertUserMandatoryMfaFulfilled(); + } const { socialIdentity, enterpriseSsoIdentity, ...rest } = this.profile.data; const { mfaSkipped, mfaVerifications } = this.mfa.toUserMfaVerifications(); @@ -534,6 +545,12 @@ export default class ExperienceInteraction { return verificationRecord?.identifier.value === value && verificationRecord.isVerified; } + private get hasVerifiedSsoIdentity() { + const ssoVerificationRecord = this.verificationRecords.get(VerificationType.EnterpriseSso); + + return Boolean(ssoVerificationRecord?.isVerified); + } + /** * Clean up the interaction storage. */ diff --git a/packages/integration-tests/src/tests/api/experience-api/sign-in-interaction/enterprise-sso.test.ts b/packages/integration-tests/src/tests/api/experience-api/sign-in-interaction/enterprise-sso.test.ts index 557758395..1bd82bb83 100644 --- a/packages/integration-tests/src/tests/api/experience-api/sign-in-interaction/enterprise-sso.test.ts +++ b/packages/integration-tests/src/tests/api/experience-api/sign-in-interaction/enterprise-sso.test.ts @@ -1,9 +1,11 @@ +import { MfaFactor, SignInIdentifier } from '@logto/schemas'; import { generateStandardId } from '@logto/shared'; -import { deleteUser, getUser } from '#src/api/admin-user.js'; +import { createUserMfaVerification, deleteUser, getUser } from '#src/api/admin-user.js'; import { updateSignInExperience } from '#src/api/sign-in-experience.js'; import { SsoConnectorApi } from '#src/api/sso-connector.js'; import { signInWithEnterpriseSso } from '#src/helpers/experience/index.js'; +import { enableMandatoryMfaWithTotp } from '#src/helpers/sign-in-experience.js'; import { generateNewUser } from '#src/helpers/user.js'; import { devFeatureTest, generateEmail } from '#src/utils.js'; @@ -78,4 +80,45 @@ devFeatureTest.describe('enterprise sso sign-in and sign-up', () => { await deleteUser(userId); }); + + describe('should not check mfa and profile fulfillment for the enterprise sso authentication flow', () => { + const email = generateEmail(domain); + const enterpriseSsoIdentityId = generateStandardId(); + const userIdMap = new Map(); + + beforeAll(async () => { + await updateSignInExperience({ + signUp: { identifiers: [SignInIdentifier.Username], password: true, verify: false }, + }); + await enableMandatoryMfaWithTotp(); + }); + + it('should successfully sign-up with enterprise sso without profile and mfa fulfillment', async () => { + const userId = await signInWithEnterpriseSso( + ssoConnectorApi.firstConnectorId!, + { + sub: enterpriseSsoIdentityId, + email, + email_verified: true, + }, + true + ); + + userIdMap.set('ssoUser', userId); + }); + + it('should successfully sign-in with enterprise sso without mfa validation', async () => { + const userId = userIdMap.get('ssoUser')!; + + await createUserMfaVerification(userId, MfaFactor.TOTP); + + await signInWithEnterpriseSso(ssoConnectorApi.firstConnectorId!, { + sub: enterpriseSsoIdentityId, + email, + email_verified: true, + }); + + await deleteUser(userId); + }); + }); });