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 },
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}`);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue