diff --git a/packages/core/src/__mocks__/sso.ts b/packages/core/src/__mocks__/sso.ts index f70bf7477..001427586 100644 --- a/packages/core/src/__mocks__/sso.ts +++ b/packages/core/src/__mocks__/sso.ts @@ -11,7 +11,6 @@ export const mockSsoConnector = { domains: [], branding: {}, syncProfile: true, - ssoOnly: true, createdAt: Date.now(), } satisfies SsoConnector; @@ -28,6 +27,5 @@ export const wellConfiguredSsoConnector = { domains: ['foo.com'], branding: {}, syncProfile: true, - ssoOnly: true, createdAt: Date.now(), } satisfies SsoConnector; 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 917b67720..388bfa154 100644 --- a/packages/core/src/libraries/sign-in-experience/index.test.ts +++ b/packages/core/src/libraries/sign-in-experience/index.test.ts @@ -160,7 +160,6 @@ describe('getFullSignInExperience()', () => { { id: wellConfiguredSsoConnector.id, connectorName: wellConfiguredSsoConnector.connectorName, - ssoOnly: wellConfiguredSsoConnector.ssoOnly, logo: ssoConnectorFactories[wellConfiguredSsoConnector.providerName].logo, darkLogo: undefined, }, @@ -185,7 +184,6 @@ describe('get sso connectors', () => { { id: wellConfiguredSsoConnector.id, connectorName: wellConfiguredSsoConnector.connectorName, - ssoOnly: wellConfiguredSsoConnector.ssoOnly, logo: ssoConnectorFactories[wellConfiguredSsoConnector.providerName].logo, darkLogo: undefined, }, diff --git a/packages/core/src/libraries/sign-in-experience/index.ts b/packages/core/src/libraries/sign-in-experience/index.ts index 37f603b9f..3fa0e2921 100644 --- a/packages/core/src/libraries/sign-in-experience/index.ts +++ b/packages/core/src/libraries/sign-in-experience/index.ts @@ -72,13 +72,12 @@ export const createSignInExperienceLibrary = ( const ssoConnectors = await getAvailableSsoConnectors(); return ssoConnectors.map( - ({ providerName, connectorName, id, branding, ssoOnly }): SsoConnectorMetadata => { + ({ providerName, connectorName, id, branding }): SsoConnectorMetadata => { const factory = ssoConnectorFactories[providerName]; return { id, connectorName, - ssoOnly, logo: branding.logo ?? factory.logo, darkLogo: branding.darkLogo, }; diff --git a/packages/core/src/routes/interaction/utils/single-sign-on-guard.test.ts b/packages/core/src/routes/interaction/utils/single-sign-on-guard.test.ts index 0a276034a..9abd4eb2f 100644 --- a/packages/core/src/routes/interaction/utils/single-sign-on-guard.test.ts +++ b/packages/core/src/routes/interaction/utils/single-sign-on-guard.test.ts @@ -14,7 +14,7 @@ const mockSsoConnectorLibrary: SsoConnectorLibrary = { getSsoConnectorById: jest.fn(), }; -describe('verifySsoOnlyEmailIdentifier tests', () => { +describe('verifyEmailIdentifier tests', () => { it('should return if the identifier is not an email', async () => { await expect( verifySsoOnlyEmailIdentifier(mockSsoConnectorLibrary, { @@ -38,22 +38,6 @@ describe('verifySsoOnlyEmailIdentifier tests', () => { }) ).resolves.not.toThrow(); }); - it('should return if the connector is not sso only', async () => { - getAvailableSsoConnectorsMock.mockResolvedValueOnce([ - { - ...wellConfiguredSsoConnector, - domains: ['example.com'], - ssoOnly: false, - }, - ]); - - await expect( - verifySsoOnlyEmailIdentifier(mockSsoConnectorLibrary, { - email: 'foo@example.com', - password: 'bar', - }) - ).resolves.not.toThrow(); - }); it('should throw an error if multiple sso connectors found with the given email', async () => { const connector = { diff --git a/packages/core/src/routes/interaction/utils/single-sign-on-guard.ts b/packages/core/src/routes/interaction/utils/single-sign-on-guard.ts index 3ff20315c..1d1090f41 100644 --- a/packages/core/src/routes/interaction/utils/single-sign-on-guard.ts +++ b/packages/core/src/routes/interaction/utils/single-sign-on-guard.ts @@ -24,8 +24,8 @@ export const verifySsoOnlyEmailIdentifier = async ( return; } - const availableConnectors = availableSsoConnectors.filter( - ({ domains, ssoOnly }) => domains.includes(domain) && ssoOnly + const availableConnectors = availableSsoConnectors.filter(({ domains }) => + domains.includes(domain) ); assertThat( diff --git a/packages/core/src/routes/sso-connector/type.ts b/packages/core/src/routes/sso-connector/type.ts index ef4b8bc75..a13742108 100644 --- a/packages/core/src/routes/sso-connector/type.ts +++ b/packages/core/src/routes/sso-connector/type.ts @@ -22,7 +22,6 @@ export const ssoConnectorCreateGuard = SsoConnectors.createGuard domains: true, branding: true, syncProfile: true, - ssoOnly: true, }) // Provider name and connector name are required for creating a connector .merge(SsoConnectors.guard.pick({ providerName: true, connectorName: true })); @@ -42,7 +41,6 @@ export const ssoConnectorPatchGuard = SsoConnectors.guard domains: true, branding: true, syncProfile: true, - ssoOnly: true, connectorName: true, }) .partial(); diff --git a/packages/experience/src/__mocks__/logto.tsx b/packages/experience/src/__mocks__/logto.tsx index 9815658be..cca2385ea 100644 --- a/packages/experience/src/__mocks__/logto.tsx +++ b/packages/experience/src/__mocks__/logto.tsx @@ -45,7 +45,6 @@ export const mockSsoConnectors: SsoConnectorMetadata[] = [ { id: 'arbitrary-sso-connector', connectorName: 'AzureAD', - ssoOnly: true, logo: 'http://logto.dev/logto.png', }, ]; diff --git a/packages/integration-tests/src/constants.ts b/packages/integration-tests/src/constants.ts index 61b3ac749..c44559578 100644 --- a/packages/integration-tests/src/constants.ts +++ b/packages/integration-tests/src/constants.ts @@ -35,7 +35,6 @@ export const newOidcSsoConnectorPayload = { providerName: ProviderName.OIDC, connectorName: 'test-oidc', domains: ['example.io'], // Auto-generated email domain - ssoOnly: true, branding: { logo: 'https://logto.io/oidc-logo.png', darkLogo: 'https://logto.io/oidc-dark-logo.png', diff --git a/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier/happy-path.test.ts b/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier/happy-path.test.ts index a47c80693..a46bf9665 100644 --- a/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier/happy-path.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier/happy-path.test.ts @@ -6,7 +6,6 @@ import { patchInteractionIdentifiers, putInteractionProfile, deleteUser, - createUser, } from '#src/api/index.js'; import { createSsoConnector } from '#src/api/sso-connector.js'; import { newOidcSsoConnectorPayload } from '#src/constants.js'; @@ -22,7 +21,6 @@ import { enableAllVerificationCodeSignInMethods, } from '#src/helpers/sign-in-experience.js'; import { generateNewUser, generateNewUserProfile } from '#src/helpers/user.js'; -import { generatePassword, generateEmail } from '#src/utils.js'; describe('Sign-in flow using password identifiers', () => { beforeAll(async () => { @@ -99,34 +97,6 @@ describe('Sign-in flow using password identifiers', () => { await deleteUser(user.id); }); - it('should allow sign-in with email and password with SSO connector settings ssoOnly to false', async () => { - const password = generatePassword(); - const email = generateEmail('sso-email-password-sign-in-happy-path.io'); - const user = await createUser({ primaryEmail: email, password }); - const client = await initClient(); - - await createSsoConnector({ - ...newOidcSsoConnectorPayload, - domains: ['sso-email-password-sign-in-happy-path.io'], - ssoOnly: false, - }); - - await client.successSend(putInteraction, { - event: InteractionEvent.SignIn, - identifier: { - email, - password, - }, - }); - - const { redirectTo } = await client.submitInteraction(); - - await processSession(client, redirectTo); - await logoutClient(client); - - await deleteUser(user.id); - }); - it('sign-in with phone and password', async () => { const { userProfile, user } = await generateNewUser({ primaryPhone: true, password: true }); const client = await initClient(); diff --git a/packages/integration-tests/src/tests/api/interaction/single-sign-on/happy-path.test.ts b/packages/integration-tests/src/tests/api/interaction/single-sign-on/happy-path.test.ts index e7c271502..bd9822592 100644 --- a/packages/integration-tests/src/tests/api/interaction/single-sign-on/happy-path.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/single-sign-on/happy-path.test.ts @@ -13,7 +13,7 @@ describe('Single Sign On Happy Path', () => { const redirectUri = 'http://foo.dev/callback'; beforeAll(async () => { - const { id, connectorName, ssoOnly } = await createSsoConnector({ + const { id, connectorName } = await createSsoConnector({ providerName: ProviderName.OIDC, connectorName: 'test-oidc', domains: ['foo.com'], @@ -24,7 +24,7 @@ describe('Single Sign On Happy Path', () => { }, }); - connectorIdMap.set(id, { id, connectorName, ssoOnly, logo: '' }); + connectorIdMap.set(id, { id, connectorName, logo: '' }); }); afterAll(async () => { diff --git a/packages/integration-tests/src/tests/api/sso-connectors.test.ts b/packages/integration-tests/src/tests/api/sso-connectors.test.ts index d7ea3848d..401cc8598 100644 --- a/packages/integration-tests/src/tests/api/sso-connectors.test.ts +++ b/packages/integration-tests/src/tests/api/sso-connectors.test.ts @@ -68,7 +68,6 @@ describe('post sso-connectors', () => { expect(response).toHaveProperty('connectorName', 'test'); expect(response).toHaveProperty('config', {}); expect(response).toHaveProperty('domains', []); - expect(response).toHaveProperty('ssoOnly', false); expect(response).toHaveProperty('syncProfile', false); await deleteSsoConnectorById(response.id); @@ -95,7 +94,6 @@ describe('post sso-connectors', () => { connectorName: 'test', config, domains: ['test.com'], - ssoOnly: true, }; const response = await createSsoConnector(data); @@ -105,7 +103,6 @@ describe('post sso-connectors', () => { expect(response).toHaveProperty('connectorName', 'test'); expect(response).toHaveProperty('config', data.config); expect(response).toHaveProperty('domains', data.domains); - expect(response).toHaveProperty('ssoOnly', data.ssoOnly); expect(response).toHaveProperty('syncProfile', false); await deleteSsoConnectorById(response.id); @@ -156,7 +153,6 @@ describe('get sso-connector by id', () => { expect(connector).toHaveProperty('connectorName', 'integration_test connector'); expect(connector).toHaveProperty('config', {}); expect(connector).toHaveProperty('domains', []); - expect(connector).toHaveProperty('ssoOnly', false); expect(connector).toHaveProperty('syncProfile', false); await deleteSsoConnectorById(id); @@ -198,7 +194,6 @@ describe('patch sso-connector by id', () => { const connector = await patchSsoConnectorById(id, { connectorName: 'integration_test connector updated', domains: ['test.com'], - ssoOnly: true, }); expect(connector).toHaveProperty('id', id); @@ -206,7 +201,6 @@ describe('patch sso-connector by id', () => { expect(connector).toHaveProperty('connectorName', 'integration_test connector updated'); expect(connector).toHaveProperty('config', {}); expect(connector).toHaveProperty('domains', ['test.com']); - expect(connector).toHaveProperty('ssoOnly', true); expect(connector).toHaveProperty('syncProfile', false); await deleteSsoConnectorById(id); @@ -227,7 +221,6 @@ describe('patch sso-connector by id', () => { expect(connector).toHaveProperty('connectorName', 'integration_test connector'); expect(connector).toHaveProperty('config', {}); expect(connector).toHaveProperty('domains', []); - expect(connector).toHaveProperty('ssoOnly', false); expect(connector).toHaveProperty('syncProfile', false); await deleteSsoConnectorById(id); diff --git a/packages/integration-tests/src/tests/api/well-known.test.ts b/packages/integration-tests/src/tests/api/well-known.test.ts index ea81fce0d..c39c10bdc 100644 --- a/packages/integration-tests/src/tests/api/well-known.test.ts +++ b/packages/integration-tests/src/tests/api/well-known.test.ts @@ -72,7 +72,6 @@ describe('.well-known api', () => { expect(newCreatedConnector).toMatchObject({ id, connectorName, - ssoOnly: newOidcSsoConnectorPayload.ssoOnly, logo: newOidcSsoConnectorPayload.branding.logo, darkLogo: newOidcSsoConnectorPayload.branding.darkLogo, }); diff --git a/packages/schemas/alterations/next-1699598903-remove-sso-only-column-in-sso-connectors-table.ts b/packages/schemas/alterations/next-1699598903-remove-sso-only-column-in-sso-connectors-table.ts new file mode 100644 index 000000000..6f0378c18 --- /dev/null +++ b/packages/schemas/alterations/next-1699598903-remove-sso-only-column-in-sso-connectors-table.ts @@ -0,0 +1,18 @@ +import { sql } from 'slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + alter table sso_connectors drop column sso_only; + `); + }, + down: async (pool) => { + await pool.query(sql` + alter table sso_connectors add column sso_only boolean not null default FALSE; + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/types/sso-connector.ts b/packages/schemas/src/types/sso-connector.ts index f38c04c13..25f741f8f 100644 --- a/packages/schemas/src/types/sso-connector.ts +++ b/packages/schemas/src/types/sso-connector.ts @@ -7,7 +7,6 @@ export const ssoConnectorMetadataGuard = z.object({ id: z.string(), connectorName: z.string(), logo: z.string(), - ssoOnly: z.boolean(), darkLogo: z.string().optional(), }); diff --git a/packages/schemas/tables/sso_connectors.sql b/packages/schemas/tables/sso_connectors.sql index 71b2a5dbf..c0c27e2eb 100644 --- a/packages/schemas/tables/sso_connectors.sql +++ b/packages/schemas/tables/sso_connectors.sql @@ -16,8 +16,6 @@ create table sso_connectors ( branding jsonb /* @use SsoBranding */ not null default '{}'::jsonb, /** Determines whether to synchronize the user's profile on each login. */ sync_profile boolean not null default FALSE, - /** Determines whether SSO is the restricted sign-in method for users with the SSO registered email domains */ - sso_only boolean not null default FALSE, /** When the SSO connector was created. */ created_at timestamptz not null default(now()), primary key (id)