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

chore: fix code

This commit is contained in:
Darcy Ye 2024-11-28 12:52:52 +08:00
parent 0d83cb1fd6
commit 79eeb3508c
No known key found for this signature in database
GPG key ID: B46F4C07EDEFC610
5 changed files with 47 additions and 280 deletions

View file

@ -1,273 +0,0 @@
{
"tags": [
{
"name": "SAML applications",
"description": "SAML applications enable Single Sign-On (SSO) integration between Logto (acting as Identity Provider/IdP) and third-party Service Providers (SP) using the SAML 2.0 protocol. These endpoints allow you to manage SAML application configurations."
},
{
"name": "Dev feature"
}
],
"paths": {
"/api/saml-applications": {
"post": {
"summary": "Create SAML application",
"description": "Create a new SAML application with the given configuration. This will create both the application entity and its SAML-specific configurations.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"type": "string",
"description": "The name of the SAML application."
},
"description": {
"type": "string",
"description": "The description of the SAML application."
},
"customData": {
"type": "object",
"description": "Custom data for the application."
},
"config": {
"type": "object",
"properties": {
"attributeMapping": {
"type": "object",
"description": "Mapping of SAML attributes to Logto user properties."
},
"entityId": {
"type": "string",
"description": "Service provider's entityId."
},
"acsUrl": {
"type": "object",
"description": "Service provider assertion consumer service URL configuration."
}
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "The SAML application was created successfully."
},
"400": {
"description": "Invalid request body or SAML configuration."
}
}
}
},
"/api/saml-applications/{id}": {
"get": {
"summary": "Get SAML application",
"description": "Get a SAML application by ID. This will return both the application entity and its SAML-specific configurations.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the SAML application to retrieve."
}
],
"responses": {
"200": {
"description": "The SAML application was retrieved successfully."
},
"400": {
"description": "Invalid application ID, the application is not a SAML application."
},
"404": {
"description": "The SAML application was not found."
}
}
},
"patch": {
"summary": "Update SAML application",
"description": "Update a SAML application by ID. This will update both the application entity and its SAML-specific configurations.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the SAML application to update."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"type": "string",
"description": "The name of the SAML application."
},
"description": {
"type": "string",
"description": "The description of the SAML application."
},
"customData": {
"type": "object",
"description": "Custom data for the application."
},
"config": {
"type": "object",
"properties": {
"attributeMapping": {
"type": "object",
"description": "Mapping of SAML attributes to Logto user properties."
},
"entityId": {
"type": "string",
"description": "Service provider's entityId."
},
"acsUrl": {
"type": "object",
"description": "Service provider assertion consumer service URL configuration."
}
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "The SAML application was updated successfully."
},
"400": {
"description": "Invalid application ID or request body."
},
"404": {
"description": "The SAML application was not found."
},
"422": {
"description": "Invalid SAML configuration."
}
}
},
"delete": {
"summary": "Delete SAML application",
"description": "Delete a SAML application by ID. This will remove both the application entity and its SAML-specific configurations.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the SAML application to delete."
}
],
"responses": {
"204": {
"description": "The SAML application was deleted successfully."
},
"400": {
"description": "Invalid application ID, the application is not a SAML application."
},
"404": {
"description": "The SAML application was not found."
}
}
}
},
"/api/saml-applications/{id}/secrets": {
"post": {
"summary": "Create SAML application secret",
"description": "Create a new secret for the specified SAML application.",
"parameters": [],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"properties": {
"lifeSpanInDays": {
"type": "number",
"description": "The lifespan of the secret in days."
}
}
}
}
}
},
"responses": {
"201": {
"description": "The SAML application secret was created successfully."
},
"400": {
"description": "Invalid application ID or request body."
},
"404": {
"description": "The SAML application was not found."
}
}
}
},
"/api/saml-applications/{id}/secrets/{secretId}": {
"delete": {
"summary": "Delete SAML application secret",
"description": "Delete a secret from the specified SAML application. Active secrets cannot be deleted.",
"parameters": [],
"responses": {
"204": {
"description": "The SAML application secret was deleted successfully."
},
"400": {
"description": "Invalid application ID or secret ID, or attempting to delete an active secret."
},
"404": {
"description": "The SAML application or secret was not found."
}
}
},
"patch": {
"summary": "Update SAML application secret status",
"description": "Update the active status of a SAML application secret.",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"active": {
"required": true,
"type": "boolean",
"description": "The new active status of the secret."
}
}
}
}
}
},
"responses": {
"200": {
"description": "The SAML application secret was updated successfully."
},
"400": {
"description": "Invalid application ID, secret ID or request body."
},
"404": {
"description": "The SAML application or secret was not found."
}
}
}
}
}
}

View file

@ -27,6 +27,7 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
samlApplicationConfigs: { insertSamlApplicationConfig }, samlApplicationConfigs: { insertSamlApplicationConfig },
samlApplicationSecrets: { samlApplicationSecrets: {
deleteSamlApplicationSecretById, deleteSamlApplicationSecretById,
findSamlApplicationSecretsByApplicationId,
findSamlApplicationSecretByApplicationIdAndId, findSamlApplicationSecretByApplicationIdAndId,
updateSamlApplicationSecretStatusByApplicationIdAndSecretId, updateSamlApplicationSecretStatusByApplicationIdAndSecretId,
}, },
@ -168,7 +169,24 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
} = ctx.guard; } = ctx.guard;
ctx.status = 201; ctx.status = 201;
ctx.body = await createNewSamlApplicationSecretForApplication(id, lifeSpanInDays); ctx.body = await createSamlApplicationSecret(id, lifeSpanInDays);
return next();
}
);
router.get(
'/saml-applications/:id/secrets',
koaGuard({
params: z.object({ id: z.string() }),
response: samlApplicationSecretResponseGuard.array(),
status: [200, 400, 404],
}),
async (ctx, next) => {
const { id } = ctx.guard.params;
ctx.status = 200;
ctx.body = await findSamlApplicationSecretsByApplicationId(id);
return next(); return next();
} }

View file

@ -33,6 +33,9 @@ export const createSamlApplicationSecret = async (id: string, lifeSpanInDays: nu
.post(`saml-applications/${id}/secrets`, { json: { lifeSpanInDays } }) .post(`saml-applications/${id}/secrets`, { json: { lifeSpanInDays } })
.json<SamlApplicationSecretResponse>(); .json<SamlApplicationSecretResponse>();
export const getSamlApplicationSecrets = async (id: string) =>
authedAdminApi.get(`saml-applications/${id}/secrets`).json<SamlApplicationSecretResponse[]>();
export const deleteSamlApplicationSecret = async (id: string, secretId: string) => export const deleteSamlApplicationSecret = async (id: string, secretId: string) =>
authedAdminApi.delete(`saml-applications/${id}/secrets/${secretId}`); authedAdminApi.delete(`saml-applications/${id}/secrets/${secretId}`);

View file

@ -9,6 +9,7 @@ import {
deleteSamlApplicationSecret, deleteSamlApplicationSecret,
createSamlApplicationSecret, createSamlApplicationSecret,
updateSamlApplicationSecret, updateSamlApplicationSecret,
getSamlApplicationSecrets,
} from '#src/api/saml-application.js'; } from '#src/api/saml-application.js';
import { expectRejects } from '#src/helpers/index.js'; import { expectRejects } from '#src/helpers/index.js';
import { devFeatureTest } from '#src/utils.js'; import { devFeatureTest } from '#src/utils.js';
@ -114,11 +115,17 @@ describe('SAML application', () => {
}); });
const createdSecret = await createSamlApplicationSecret(id, 30); const createdSecret = await createSamlApplicationSecret(id, 30);
const samlApplication = await getSamlApplication(id);
expect(samlApplication.secrets.length).toBe(2); // @ts-expect-error - Make sure the `privateKey` is not exposed in the response.
expect(createdSecret.privateKey).toBeUndefined();
const samlApplicationSecrets = await getSamlApplicationSecrets(id);
expect(samlApplicationSecrets.length).toBe(2);
expect( expect(
samlApplication.secrets.find((secret) => secret.id === createdSecret.id) samlApplicationSecrets.find((secret) => secret.id === createdSecret.id)
).not.toBeUndefined(); ).not.toBeUndefined();
// @ts-expect-error - Make sure the `privateKey` is not exposed in the response.
expect(samlApplicationSecrets.every((secret) => secret.privateKey === undefined)).toBe(true);
await deleteSamlApplicationSecret(id, createdSecret.id); await deleteSamlApplicationSecret(id, createdSecret.id);
@ -132,14 +139,23 @@ describe('SAML application', () => {
}); });
const createdSecret = await createSamlApplicationSecret(id, 30); const createdSecret = await createSamlApplicationSecret(id, 30);
const samlApplication = await getSamlApplication(id);
expect(samlApplication.secrets.length).toBe(2); // @ts-expect-error - Make sure the `privateKey` is not exposed in the response.
expect(createdSecret.privateKey).toBeUndefined();
const samlApplicationSecrets = await getSamlApplicationSecrets(id);
expect(samlApplicationSecrets.length).toBe(2);
expect( expect(
samlApplication.secrets.find((secret) => secret.id === createdSecret.id) samlApplicationSecrets.find((secret) => secret.id === createdSecret.id)
).not.toBeUndefined(); ).not.toBeUndefined();
// @ts-expect-error - Make sure the `privateKey` is not exposed in the response.
expect(samlApplicationSecrets.every((secret) => secret.privateKey === undefined)).toBe(true);
const updatedSecret = await updateSamlApplicationSecret(id, createdSecret.id, true); const updatedSecret = await updateSamlApplicationSecret(id, createdSecret.id, true);
expect(updatedSecret.active).toBe(true); expect(updatedSecret.active).toBe(true);
// @ts-expect-error - Make sure the `privateKey` is not exposed in the response.
expect(updatedSecret.privateKey).toBeUndefined();
await expectRejects(deleteSamlApplicationSecret(id, createdSecret.id), { await expectRejects(deleteSamlApplicationSecret(id, createdSecret.id), {
code: 'application.saml.can_not_delete_active_secret', code: 'application.saml.can_not_delete_active_secret',
@ -147,6 +163,7 @@ describe('SAML application', () => {
}); });
await updateSamlApplicationSecret(id, createdSecret.id, false); await updateSamlApplicationSecret(id, createdSecret.id, false);
await deleteSamlApplicationSecret(id, createdSecret.id);
await deleteSamlApplication(id); await deleteSamlApplication(id);
}); });
}); });

View file

@ -2,6 +2,7 @@ import { type z } from 'zod';
import { Applications } from '../db-entries/application.js'; import { Applications } from '../db-entries/application.js';
import { SamlApplicationConfigs } from '../db-entries/saml-application-config.js'; import { SamlApplicationConfigs } from '../db-entries/saml-application-config.js';
import { SamlApplicationSecrets } from '../db-entries/saml-application-secret.js';
import { applicationCreateGuard, applicationPatchGuard } from './application.js'; import { applicationCreateGuard, applicationPatchGuard } from './application.js';
@ -37,6 +38,7 @@ export const samlApplicationPatchGuard = applicationPatchGuard
export type PatchSamlApplication = z.infer<typeof samlApplicationPatchGuard>; export type PatchSamlApplication = z.infer<typeof samlApplicationPatchGuard>;
// Make sure the `privateKey` is not exposed in the response.
export const samlApplicationSecretResponseGuard = SamlApplicationSecrets.guard.omit({ export const samlApplicationSecretResponseGuard = SamlApplicationSecrets.guard.omit({
tenantId: true, tenantId: true,
applicationId: true, applicationId: true,