mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: add SAML app secret related APIs
This commit is contained in:
parent
c211dd1d38
commit
0d83cb1fd6
8 changed files with 469 additions and 1 deletions
|
@ -2,6 +2,7 @@ import {
|
||||||
ApplicationType,
|
ApplicationType,
|
||||||
type SamlApplicationResponse,
|
type SamlApplicationResponse,
|
||||||
type PatchSamlApplication,
|
type PatchSamlApplication,
|
||||||
|
type SamlApplicationSecret,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { generateStandardId } from '@logto/shared';
|
import { generateStandardId } from '@logto/shared';
|
||||||
import { removeUndefinedKeys } from '@silverhand/essentials';
|
import { removeUndefinedKeys } from '@silverhand/essentials';
|
||||||
|
@ -26,7 +27,7 @@ export const createSamlApplicationsLibrary = (queries: Queries) => {
|
||||||
applicationId: string,
|
applicationId: string,
|
||||||
// Set certificate life span to 1 year by default.
|
// Set certificate life span to 1 year by default.
|
||||||
lifeSpanInDays = 365
|
lifeSpanInDays = 365
|
||||||
) => {
|
): Promise<SamlApplicationSecret> => {
|
||||||
const { privateKey, certificate, notAfter } = await generateKeyPairAndCertificate(
|
const { privateKey, certificate, notAfter } = await generateKeyPairAndCertificate(
|
||||||
lifeSpanInDays
|
lifeSpanInDays
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,6 +20,13 @@ export const createSamlApplicationSecretsQueries = (pool: CommonQueryMethods) =>
|
||||||
where ${fields.applicationId}=${applicationId}
|
where ${fields.applicationId}=${applicationId}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const findSamlApplicationSecretByApplicationIdAndId = async (applicationId: string, id: string) =>
|
||||||
|
pool.one<SamlApplicationSecret>(sql`
|
||||||
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
from ${table}
|
||||||
|
where ${fields.applicationId} = ${applicationId} and ${fields.id} = ${id}
|
||||||
|
`);
|
||||||
|
|
||||||
const deleteSamlApplicationSecretById = async (id: string) => {
|
const deleteSamlApplicationSecretById = async (id: string) => {
|
||||||
const { rowCount } = await pool.query(sql`
|
const { rowCount } = await pool.query(sql`
|
||||||
delete from ${table}
|
delete from ${table}
|
||||||
|
@ -31,9 +38,38 @@ export const createSamlApplicationSecretsQueries = (pool: CommonQueryMethods) =>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateSamlApplicationSecretStatusByApplicationIdAndSecretId = async (
|
||||||
|
applicationId: string,
|
||||||
|
secretId: string,
|
||||||
|
active: boolean
|
||||||
|
) => {
|
||||||
|
return pool.transaction(async (transaction) => {
|
||||||
|
if (active) {
|
||||||
|
// If activating this secret, first deactivate all other secrets of the same application
|
||||||
|
await transaction.query(sql`
|
||||||
|
update ${table}
|
||||||
|
set ${fields.active} = false
|
||||||
|
where ${fields.applicationId} = ${applicationId}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the status of the specified secret
|
||||||
|
const updatedSecret = await transaction.one<SamlApplicationSecret>(sql`
|
||||||
|
update ${table}
|
||||||
|
set ${fields.active} = ${active}
|
||||||
|
where ${fields.id} = ${secretId} and ${fields.applicationId} = ${applicationId}
|
||||||
|
returning ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
`);
|
||||||
|
|
||||||
|
return updatedSecret;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
insertSamlApplicationSecret,
|
insertSamlApplicationSecret,
|
||||||
findSamlApplicationSecretsByApplicationId,
|
findSamlApplicationSecretsByApplicationId,
|
||||||
|
findSamlApplicationSecretByApplicationIdAndId,
|
||||||
deleteSamlApplicationSecretById,
|
deleteSamlApplicationSecretById,
|
||||||
|
updateSamlApplicationSecretStatusByApplicationIdAndSecretId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
273
packages/core/src/saml-applications/routes/index.openapi.json
Normal file
273
packages/core/src/saml-applications/routes/index.openapi.json
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
{
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ import {
|
||||||
samlApplicationCreateGuard,
|
samlApplicationCreateGuard,
|
||||||
samlApplicationPatchGuard,
|
samlApplicationPatchGuard,
|
||||||
samlApplicationResponseGuard,
|
samlApplicationResponseGuard,
|
||||||
|
samlApplicationSecretResponseGuard,
|
||||||
|
SamlApplicationSecrets,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { generateStandardId } from '@logto/shared';
|
import { generateStandardId } from '@logto/shared';
|
||||||
import { removeUndefinedKeys } from '@silverhand/essentials';
|
import { removeUndefinedKeys } from '@silverhand/essentials';
|
||||||
|
@ -23,6 +25,11 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
|
||||||
const {
|
const {
|
||||||
applications: { insertApplication, findApplicationById, deleteApplicationById },
|
applications: { insertApplication, findApplicationById, deleteApplicationById },
|
||||||
samlApplicationConfigs: { insertSamlApplicationConfig },
|
samlApplicationConfigs: { insertSamlApplicationConfig },
|
||||||
|
samlApplicationSecrets: {
|
||||||
|
deleteSamlApplicationSecretById,
|
||||||
|
findSamlApplicationSecretByApplicationIdAndId,
|
||||||
|
updateSamlApplicationSecretStatusByApplicationIdAndSecretId,
|
||||||
|
},
|
||||||
} = queries;
|
} = queries;
|
||||||
const {
|
const {
|
||||||
samlApplications: {
|
samlApplications: {
|
||||||
|
@ -145,4 +152,86 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/saml-applications/:id/secrets',
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ id: z.string() }),
|
||||||
|
body: z.object({ lifeSpanInDays: z.number().optional() }),
|
||||||
|
response: samlApplicationSecretResponseGuard,
|
||||||
|
status: [201, 400, 404],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const {
|
||||||
|
body: { lifeSpanInDays },
|
||||||
|
params: { id },
|
||||||
|
} = ctx.guard;
|
||||||
|
|
||||||
|
ctx.status = 201;
|
||||||
|
ctx.body = await createNewSamlApplicationSecretForApplication(id, lifeSpanInDays);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/saml-applications/:id/secrets/:secretId',
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ id: z.string(), secretId: z.string() }),
|
||||||
|
status: [204, 400, 404],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, secretId } = ctx.guard.params;
|
||||||
|
|
||||||
|
// Although we can directly find the SAML app secret by `secretId` here, to prevent deleting a secret that does not belong to the current application, we will first verify through the application ID and secret ID.
|
||||||
|
const samlApplicationSecret = await findSamlApplicationSecretByApplicationIdAndId(
|
||||||
|
id,
|
||||||
|
secretId
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(!samlApplicationSecret.active, 'application.saml.can_not_delete_active_secret');
|
||||||
|
|
||||||
|
await deleteSamlApplicationSecretById(secretId);
|
||||||
|
|
||||||
|
ctx.status = 204;
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
'/saml-applications/:id/secrets/:secretId',
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ id: z.string(), secretId: z.string() }),
|
||||||
|
body: SamlApplicationSecrets.createGuard.pick({
|
||||||
|
active: true,
|
||||||
|
}),
|
||||||
|
response: samlApplicationSecretResponseGuard,
|
||||||
|
status: [200, 400, 404],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, secretId } = ctx.guard.params;
|
||||||
|
const { active } = ctx.guard.body;
|
||||||
|
|
||||||
|
const originalSamlApplicationSecret = await findSamlApplicationSecretByApplicationIdAndId(
|
||||||
|
id,
|
||||||
|
secretId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (originalSamlApplicationSecret.active === active) {
|
||||||
|
ctx.status = 200;
|
||||||
|
ctx.body = originalSamlApplicationSecret;
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSamlApplicationSecret =
|
||||||
|
await updateSamlApplicationSecretStatusByApplicationIdAndSecretId(id, secretId, active);
|
||||||
|
|
||||||
|
ctx.status = 200;
|
||||||
|
ctx.body = updatedSamlApplicationSecret;
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
type SamlApplicationResponse,
|
type SamlApplicationResponse,
|
||||||
type CreateSamlApplication,
|
type CreateSamlApplication,
|
||||||
type PatchSamlApplication,
|
type PatchSamlApplication,
|
||||||
|
type SamlApplicationSecretResponse,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
|
|
||||||
import { authedAdminApi } from './api.js';
|
import { authedAdminApi } from './api.js';
|
||||||
|
@ -26,3 +27,16 @@ export const updateSamlApplication = async (
|
||||||
|
|
||||||
export const getSamlApplication = async (id: string) =>
|
export const getSamlApplication = async (id: string) =>
|
||||||
authedAdminApi.get(`saml-applications/${id}`).json<SamlApplicationResponse>();
|
authedAdminApi.get(`saml-applications/${id}`).json<SamlApplicationResponse>();
|
||||||
|
|
||||||
|
export const createSamlApplicationSecret = async (id: string, lifeSpanInDays: number) =>
|
||||||
|
authedAdminApi
|
||||||
|
.post(`saml-applications/${id}/secrets`, { json: { lifeSpanInDays } })
|
||||||
|
.json<SamlApplicationSecretResponse>();
|
||||||
|
|
||||||
|
export const deleteSamlApplicationSecret = async (id: string, secretId: string) =>
|
||||||
|
authedAdminApi.delete(`saml-applications/${id}/secrets/${secretId}`);
|
||||||
|
|
||||||
|
export const updateSamlApplicationSecret = async (id: string, secretId: string, active: boolean) =>
|
||||||
|
authedAdminApi
|
||||||
|
.patch(`saml-applications/${id}/secrets/${secretId}`, { json: { active } })
|
||||||
|
.json<SamlApplicationSecretResponse>();
|
||||||
|
|
|
@ -6,6 +6,9 @@ import {
|
||||||
deleteSamlApplication,
|
deleteSamlApplication,
|
||||||
updateSamlApplication,
|
updateSamlApplication,
|
||||||
getSamlApplication,
|
getSamlApplication,
|
||||||
|
deleteSamlApplicationSecret,
|
||||||
|
createSamlApplicationSecret,
|
||||||
|
updateSamlApplicationSecret,
|
||||||
} 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';
|
||||||
|
@ -103,4 +106,47 @@ describe('SAML application', () => {
|
||||||
});
|
});
|
||||||
await deleteApplication(application.id);
|
await deleteApplication(application.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to create and delete SAML application secrets', async () => {
|
||||||
|
const { id } = await createSamlApplication({
|
||||||
|
name: 'test',
|
||||||
|
description: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdSecret = await createSamlApplicationSecret(id, 30);
|
||||||
|
const samlApplication = await getSamlApplication(id);
|
||||||
|
expect(samlApplication.secrets.length).toBe(2);
|
||||||
|
expect(
|
||||||
|
samlApplication.secrets.find((secret) => secret.id === createdSecret.id)
|
||||||
|
).not.toBeUndefined();
|
||||||
|
|
||||||
|
await deleteSamlApplicationSecret(id, createdSecret.id);
|
||||||
|
|
||||||
|
await deleteSamlApplication(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to update SAML application secret status and can not delete active secret', async () => {
|
||||||
|
const { id } = await createSamlApplication({
|
||||||
|
name: 'test',
|
||||||
|
description: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdSecret = await createSamlApplicationSecret(id, 30);
|
||||||
|
const samlApplication = await getSamlApplication(id);
|
||||||
|
expect(samlApplication.secrets.length).toBe(2);
|
||||||
|
expect(
|
||||||
|
samlApplication.secrets.find((secret) => secret.id === createdSecret.id)
|
||||||
|
).not.toBeUndefined();
|
||||||
|
|
||||||
|
const updatedSecret = await updateSamlApplicationSecret(id, createdSecret.id, true);
|
||||||
|
expect(updatedSecret.active).toBe(true);
|
||||||
|
|
||||||
|
await expectRejects(deleteSamlApplicationSecret(id, createdSecret.id), {
|
||||||
|
code: 'application.saml.can_not_delete_active_secret',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateSamlApplicationSecret(id, createdSecret.id, false);
|
||||||
|
await deleteSamlApplication(id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,6 +25,7 @@ const application = {
|
||||||
saml_application_only: 'The API is only available for SAML applications.',
|
saml_application_only: 'The API is only available for SAML applications.',
|
||||||
acs_url_binding_not_supported:
|
acs_url_binding_not_supported:
|
||||||
'Only HTTP-POST binding is supported for receiving SAML assertions.',
|
'Only HTTP-POST binding is supported for receiving SAML assertions.',
|
||||||
|
can_not_delete_active_secret: 'Can not delete the active secret.',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,14 @@ export const samlApplicationPatchGuard = applicationPatchGuard
|
||||||
|
|
||||||
export type PatchSamlApplication = z.infer<typeof samlApplicationPatchGuard>;
|
export type PatchSamlApplication = z.infer<typeof samlApplicationPatchGuard>;
|
||||||
|
|
||||||
|
export const samlApplicationSecretResponseGuard = SamlApplicationSecrets.guard.omit({
|
||||||
|
tenantId: true,
|
||||||
|
applicationId: true,
|
||||||
|
privateKey: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SamlApplicationSecretResponse = z.infer<typeof samlApplicationSecretResponseGuard>;
|
||||||
|
|
||||||
export const samlApplicationResponseGuard = Applications.guard.merge(
|
export const samlApplicationResponseGuard = Applications.guard.merge(
|
||||||
// Partial to allow the optional fields to be omitted in the response.
|
// Partial to allow the optional fields to be omitted in the response.
|
||||||
// When starting to create a SAML application, SAML configuration is optional, which can lead to the absence of SAML configuration.
|
// When starting to create a SAML application, SAML configuration is optional, which can lead to the absence of SAML configuration.
|
||||||
|
|
Loading…
Reference in a new issue