0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(core): add get available sso connectors endpoint (#6224)

feat(core): implement get sso connectors
implement get sso connectors endpoint
This commit is contained in:
simeng-li 2024-07-15 18:32:42 +08:00 committed by GitHub
parent c5897b3893
commit f94fb519f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 17 deletions

View file

@ -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,
}
)
);

View file

@ -100,4 +100,36 @@ export default function enterpriseSsoVerificationRoutes<T extends WithLogContext
return next();
}
);
router.get(
`${experienceRoutes.verification}/sso/connectors`,
koaGuard({
query: z.object({
email: z.string().email(),
}),
status: [200, 400],
response: z.object({
connectorIds: z.string().array(),
}),
}),
async (ctx, next) => {
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();
}
);
}

View file

@ -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`, {

View file

@ -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);
});
});
});