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:
parent
d2220f1205
commit
50e430880d
8 changed files with 115 additions and 72 deletions
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
})
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue