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:
parent
0d83cb1fd6
commit
79eeb3508c
5 changed files with 47 additions and 280 deletions
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
|
|||
samlApplicationConfigs: { insertSamlApplicationConfig },
|
||||
samlApplicationSecrets: {
|
||||
deleteSamlApplicationSecretById,
|
||||
findSamlApplicationSecretsByApplicationId,
|
||||
findSamlApplicationSecretByApplicationIdAndId,
|
||||
updateSamlApplicationSecretStatusByApplicationIdAndSecretId,
|
||||
},
|
||||
|
@ -168,7 +169,24 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
|
|||
} = ctx.guard;
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,9 @@ export const createSamlApplicationSecret = async (id: string, lifeSpanInDays: nu
|
|||
.post(`saml-applications/${id}/secrets`, { json: { lifeSpanInDays } })
|
||||
.json<SamlApplicationSecretResponse>();
|
||||
|
||||
export const getSamlApplicationSecrets = async (id: string) =>
|
||||
authedAdminApi.get(`saml-applications/${id}/secrets`).json<SamlApplicationSecretResponse[]>();
|
||||
|
||||
export const deleteSamlApplicationSecret = async (id: string, secretId: string) =>
|
||||
authedAdminApi.delete(`saml-applications/${id}/secrets/${secretId}`);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
deleteSamlApplicationSecret,
|
||||
createSamlApplicationSecret,
|
||||
updateSamlApplicationSecret,
|
||||
getSamlApplicationSecrets,
|
||||
} from '#src/api/saml-application.js';
|
||||
import { expectRejects } from '#src/helpers/index.js';
|
||||
import { devFeatureTest } from '#src/utils.js';
|
||||
|
@ -114,11 +115,17 @@ describe('SAML application', () => {
|
|||
});
|
||||
|
||||
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(
|
||||
samlApplication.secrets.find((secret) => secret.id === createdSecret.id)
|
||||
samlApplicationSecrets.find((secret) => secret.id === createdSecret.id)
|
||||
).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);
|
||||
|
||||
|
@ -132,14 +139,23 @@ describe('SAML application', () => {
|
|||
});
|
||||
|
||||
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(
|
||||
samlApplication.secrets.find((secret) => secret.id === createdSecret.id)
|
||||
samlApplicationSecrets.find((secret) => secret.id === createdSecret.id)
|
||||
).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);
|
||||
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), {
|
||||
code: 'application.saml.can_not_delete_active_secret',
|
||||
|
@ -147,6 +163,7 @@ describe('SAML application', () => {
|
|||
});
|
||||
|
||||
await updateSamlApplicationSecret(id, createdSecret.id, false);
|
||||
await deleteSamlApplicationSecret(id, createdSecret.id);
|
||||
await deleteSamlApplication(id);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import { type z } from 'zod';
|
|||
|
||||
import { Applications } from '../db-entries/application.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';
|
||||
|
||||
|
@ -37,6 +38,7 @@ export const samlApplicationPatchGuard = applicationPatchGuard
|
|||
|
||||
export type PatchSamlApplication = z.infer<typeof samlApplicationPatchGuard>;
|
||||
|
||||
// Make sure the `privateKey` is not exposed in the response.
|
||||
export const samlApplicationSecretResponseGuard = SamlApplicationSecrets.guard.omit({
|
||||
tenantId: true,
|
||||
applicationId: true,
|
||||
|
|
Loading…
Reference in a new issue