-
-
Notifications
You must be signed in to change notification settings - Fork 475
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(core): add sso only email guard (#6576)
* fix(core): add sso only email guard add sso only email guard to registration and profile fulfilling flow * chore: update changeset update changeset * chore(core): update content update content * fix(core): update content update content
- Loading branch information
Showing
6 changed files
with
213 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
"@logto/core": patch | ||
--- | ||
|
||
prevent user registration and profile fulfillment with SSO-only email domains | ||
|
||
Emails associated with SSO-enabled domains should only be used through the SSO authentication process. | ||
|
||
Bug fix: | ||
|
||
- Creating a new user with a verification record that contains an SSO-only email domain should return a 422 `RequestError` with the error code `session.sso_required`. | ||
- Updating a user profile with an SSO-only email domain should return a 422 `RequestError` with the error code `session.sso_required`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
...ntegration-tests/src/tests/api/experience-api/register-interaction/enterprise-sso.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import { ConnectorType, InteractionEvent, SignInIdentifier } from '@logto/schemas'; | ||
import { generateStandardId } from '@logto/shared'; | ||
|
||
import { mockSocialConnectorId } from '#src/__mocks__/connectors-mock.js'; | ||
import { updateSignInExperience } from '#src/api/sign-in-experience.js'; | ||
import { SsoConnectorApi } from '#src/api/sso-connector.js'; | ||
import { initExperienceClient } from '#src/helpers/client.js'; | ||
import { | ||
clearConnectorsByTypes, | ||
setEmailConnector, | ||
setSocialConnector, | ||
} from '#src/helpers/connector.js'; | ||
import { | ||
successFullyCreateSocialVerification, | ||
successFullyVerifySocialAuthorization, | ||
} from '#src/helpers/experience/social-verification.js'; | ||
import { | ||
successfullySendVerificationCode, | ||
successfullyVerifyVerificationCode, | ||
} from '#src/helpers/experience/verification-code.js'; | ||
import { expectRejects } from '#src/helpers/index.js'; | ||
import { UserApiTest } from '#src/helpers/user.js'; | ||
import { generateEmail } from '#src/utils.js'; | ||
|
||
describe('should reject the email registration if the email domain is enabled for SSO only', () => { | ||
const ssoConnectorApi = new SsoConnectorApi(); | ||
const domain = 'foo.com'; | ||
const email = generateEmail(domain); | ||
const userApi = new UserApiTest(); | ||
const identifier = Object.freeze({ type: SignInIdentifier.Email, value: email }); | ||
|
||
beforeAll(async () => { | ||
await Promise.all([setEmailConnector(), ssoConnectorApi.createMockOidcConnector([domain])]); | ||
await updateSignInExperience({ | ||
singleSignOnEnabled: true, | ||
signUp: { identifiers: [SignInIdentifier.Email], password: false, verify: true }, | ||
}); | ||
}); | ||
|
||
afterAll(async () => { | ||
await Promise.all([ssoConnectorApi.cleanUp(), userApi.cleanUp()]); | ||
}); | ||
|
||
it('should block email verification code registration', async () => { | ||
const client = await initExperienceClient(InteractionEvent.Register); | ||
|
||
const { verificationId, code } = await successfullySendVerificationCode(client, { | ||
identifier, | ||
interactionEvent: InteractionEvent.Register, | ||
}); | ||
|
||
await successfullyVerifyVerificationCode(client, { | ||
identifier, | ||
verificationId, | ||
code, | ||
}); | ||
|
||
await expectRejects( | ||
client.identifyUser({ | ||
verificationId, | ||
}), | ||
{ | ||
code: `session.sso_enabled`, | ||
status: 422, | ||
} | ||
); | ||
}); | ||
|
||
it('should block email profile update', async () => { | ||
const client = await initExperienceClient(InteractionEvent.Register); | ||
|
||
const { verificationId, code } = await successfullySendVerificationCode(client, { | ||
identifier, | ||
interactionEvent: InteractionEvent.Register, | ||
}); | ||
|
||
await successfullyVerifyVerificationCode(client, { | ||
identifier, | ||
verificationId, | ||
code, | ||
}); | ||
|
||
await expectRejects( | ||
client.updateProfile({ | ||
type: SignInIdentifier.Email, | ||
verificationId, | ||
}), | ||
{ | ||
code: `session.sso_enabled`, | ||
status: 422, | ||
} | ||
); | ||
}); | ||
|
||
describe('social register and link account', () => { | ||
const connectorIdMap = new Map<string, string>(); | ||
const state = 'state'; | ||
const redirectUri = 'http://localhost:3000'; | ||
const socialUserId = generateStandardId(); | ||
|
||
beforeAll(async () => { | ||
await clearConnectorsByTypes([ConnectorType.Social]); | ||
const { id: socialConnectorId } = await setSocialConnector(); | ||
connectorIdMap.set(mockSocialConnectorId, socialConnectorId); | ||
}); | ||
|
||
afterAll(async () => { | ||
await clearConnectorsByTypes([ConnectorType.Social]); | ||
}); | ||
|
||
it('should block social register with SSO only email identifier', async () => { | ||
const connectorId = connectorIdMap.get(mockSocialConnectorId)!; | ||
const client = await initExperienceClient(InteractionEvent.Register); | ||
|
||
const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, { | ||
redirectUri, | ||
state, | ||
}); | ||
|
||
await successFullyVerifySocialAuthorization(client, connectorId, { | ||
verificationId, | ||
connectorData: { | ||
state, | ||
redirectUri, | ||
code: 'fake_code', | ||
userId: socialUserId, | ||
email, | ||
}, | ||
}); | ||
|
||
await expectRejects( | ||
client.identifyUser({ | ||
verificationId, | ||
}), | ||
{ | ||
code: `session.sso_enabled`, | ||
status: 422, | ||
} | ||
); | ||
}); | ||
|
||
it('should block social link email with SSO only email identifier', async () => { | ||
const connectorId = connectorIdMap.get(mockSocialConnectorId)!; | ||
const client = await initExperienceClient(InteractionEvent.Register); | ||
|
||
const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, { | ||
redirectUri, | ||
state, | ||
}); | ||
|
||
await successFullyVerifySocialAuthorization(client, connectorId, { | ||
verificationId, | ||
connectorData: { | ||
state, | ||
redirectUri, | ||
code: 'fake_code', | ||
userId: socialUserId, | ||
}, | ||
}); | ||
|
||
await expectRejects(client.identifyUser({ verificationId }), { | ||
code: 'user.missing_profile', | ||
status: 422, | ||
}); | ||
|
||
const { code, verificationId: emailVerificationId } = await successfullySendVerificationCode( | ||
client, | ||
{ | ||
identifier, | ||
interactionEvent: InteractionEvent.Register, | ||
} | ||
); | ||
|
||
await successfullyVerifyVerificationCode(client, { | ||
identifier, | ||
verificationId: emailVerificationId, | ||
code, | ||
}); | ||
|
||
await expectRejects( | ||
client.updateProfile({ | ||
type: SignInIdentifier.Email, | ||
verificationId: emailVerificationId, | ||
}), | ||
{ | ||
code: `session.sso_enabled`, | ||
status: 422, | ||
} | ||
); | ||
}); | ||
}); | ||
}); |