0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(core): redesign get sso connectors endpoint (#6454)

* refactor(core): redesign get sso connectors endpoint

redesign get sso conenctors endpoint

* chore(core): fix import format

fix import format
This commit is contained in:
simeng-li 2024-08-16 11:06:35 +08:00 committed by GitHub
parent d2220f1205
commit 50e430880d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 115 additions and 72 deletions

View file

@ -0,0 +1,37 @@
{
"paths": {
"/api/experience/sso-connectors": {
"get": {
"tags": ["Dev feature"],
"summary": "Get all the enabled SSO connectors by the given email's domain",
"description": "Extract the email domain from the provided email address. Returns all the enabled SSO connectors that match the email domain.",
"parameters": [
{
"name": "email",
"in": "query",
"description": "The email address to find the enabled SSO connectors."
}
],
"responses": {
"200": {
"description": "The enabled SSO connectors have been successfully retrieved.",
"content": {
"application/json": {
"schema": {
"properties": {
"connectorIds": {
"description": "The list of enabled SSO connectorIds. Returns an empty array if no enabled SSO connectors are found."
}
}
}
}
}
},
"400": {
"description": "The email address is invalid, can not extract a valid domain from it."
}
}
}
}
}
}

View file

@ -0,0 +1,48 @@
import type Router from 'koa-router';
import { z } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import { type WithInteractionDetailsContext } from '#src/middleware/koa-interaction-details.js';
import type TenantContext from '#src/tenants/TenantContext.js';
import assertThat from '#src/utils/assert-that.js';
import { SignInExperienceValidator } from '../classes/libraries/sign-in-experience-validator.js';
import { experienceRoutes } from '../const.js';
export default function experienceAnonymousRoutes<T extends WithInteractionDetailsContext>(
router: Router<unknown, T>,
tenantContext: TenantContext
) {
const { libraries, queries } = tenantContext;
router.get(
`${experienceRoutes.prefix}/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;
assertThat(
email.split('@')[1],
new RequestError({ code: 'guard.invalid_input', status: 400, email })
);
const signInExperienceValidator = new SignInExperienceValidator(libraries, queries);
const connectors = await signInExperienceValidator.getEnabledSsoConnectorsByEmail(email);
ctx.body = {
connectorIds: connectors.map(({ id }) => id),
};
return next();
}
);
}

View file

@ -21,6 +21,7 @@ import assertThat from '#src/utils/assert-that.js';
import { type AnonymousRouter, type RouterInitArgs } from '../types.js';
import experienceAnonymousRoutes from './anonymous-routes/index.js';
import ExperienceInteraction from './classes/experience-interaction.js';
import { experienceRoutes } from './const.js';
import { koaExperienceInteractionHooks } from './middleware/koa-experience-interaction-hooks.js';
@ -187,4 +188,5 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
newPasswordIdentityVerificationRoutes(experienceRouter, tenant);
profileRoutes(experienceRouter, tenant);
experienceAnonymousRoutes(experienceRouter, tenant);
}

View file

@ -13,6 +13,22 @@ export type WithExperienceInteractionContext<
experienceInteraction: ExperienceInteraction;
};
/**
* White-listed endpoints that does not require an validation and initialization of `ExperienceInteraction`.
*/
const whiteListedEndpoint = [
// PUT /experience: New ExperienceInteraction instance supposed to be created for this request.
{
method: 'PUT',
path: `${experienceRoutes.prefix}`,
},
// GET /experience/sso-connectors: Fetch available SSO connectors for a given email, no interaction needed.
{
method: 'GET',
path: `${experienceRoutes.prefix}/sso-connectors`,
},
];
/**
* @overview This middleware initializes the `ExperienceInteraction` for the current request.
* The `ExperienceInteraction` instance is used to manage all the data related to the current interaction.
@ -33,9 +49,10 @@ export default function koaExperienceInteraction<
request: { method, path },
} = ctx;
// Should not retrieve the interaction details for the PUT /experience request.
// New ExperienceInteraction instance supposed to be created for this request.
if (method === 'PUT' && path === `${experienceRoutes.prefix}`) {
// Skip initializing `ExperienceInteraction` for white-listed endpoints.
if (
whiteListedEndpoint.some((endpoint) => endpoint.method === method && endpoint.path === path)
) {
return next();
}

View file

@ -95,39 +95,6 @@
}
}
}
},
"/api/experience/verification/sso/connectors": {
"get": {
"tags": ["Dev feature"],
"summary": "Get all the enabled SSO connectors by the given email's domain",
"description": "Extract the email domain from the provided email address. Returns all the enabled SSO connectors that match the email domain.",
"parameters": [
{
"name": "email",
"in": "query",
"description": "The email address to find the enabled SSO connectors."
}
],
"responses": {
"200": {
"description": "The enabled SSO connectors have been successfully retrieved.",
"content": {
"application/json": {
"schema": {
"properties": {
"connectorIds": {
"description": "The list of enabled SSO connectorIds. Returns an empty array if no enabled SSO connectors are found."
}
}
}
}
}
},
"400": {
"description": "The email address is invalid, can not extract a valid domain from it."
}
}
}
}
}
}

View file

@ -128,36 +128,4 @@ export default function enterpriseSsoVerificationRoutes<
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

@ -154,7 +154,7 @@ export class ExperienceClient extends MockClient {
public async getAvailableSsoConnectors(email: string) {
return api
.get(`${experienceRoutes.verification}/sso/connectors`, {
.get(`${experienceRoutes.prefix}/sso-connectors`, {
headers: { cookie: this.interactionCookie },
searchParams: { email },
})

View file

@ -3,6 +3,7 @@ import { ConnectorType } from '@logto/connector-kit';
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 { ExperienceClient } from '#src/client/experience/index.js';
import { initExperienceClient } from '#src/helpers/client.js';
import { clearConnectorsByTypes, setSocialConnector } from '#src/helpers/connector.js';
import {
@ -216,7 +217,8 @@ devFeatureTest.describe('enterprise sso verification', () => {
});
it('should get sso connectors with given email properly', async () => {
const client = await initExperienceClient();
const client = new ExperienceClient();
await client.initSession();
const response = await client.getAvailableSsoConnectors('bar@' + domain);
@ -225,7 +227,8 @@ devFeatureTest.describe('enterprise sso verification', () => {
});
it('should return empty array if no sso connectors found', async () => {
const client = await initExperienceClient();
const client = new ExperienceClient();
await client.initSession();
const response = await client.getAvailableSsoConnectors('bar@invalid.com');
@ -237,7 +240,8 @@ devFeatureTest.describe('enterprise sso verification', () => {
singleSignOnEnabled: false,
});
const client = await initExperienceClient();
const client = new ExperienceClient();
await client.initSession();
const response = await client.getAvailableSsoConnectors('bar@' + domain);