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:
parent
c5897b3893
commit
f94fb519f4
4 changed files with 106 additions and 17 deletions
|
@ -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,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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`, {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue