diff --git a/packages/core/src/queries/logto-config.ts b/packages/core/src/queries/logto-config.ts index ff0bf3544..4d051bce6 100644 --- a/packages/core/src/queries/logto-config.ts +++ b/packages/core/src/queries/logto-config.ts @@ -16,6 +16,8 @@ import { z } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; +import { DeletionError } from '#src/errors/SlonikError/index.js'; + const { table, fields } = convertToIdentifiers(LogtoConfigs); export const createLogtoConfigQueries = (pool: CommonQueryMethods) => { @@ -85,6 +87,19 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => { return z.object({ value: jwtCustomizerConfigGuard[key] }).parse(rows[0]); }; + const deleteJwtCustomizer = async (key: T) => { + const { rowCount } = await pool.query( + sql` + delete from ${table} + where ${fields.key} = ${key} + ` + ); + + if (rowCount < 1) { + throw new DeletionError(LogtoConfigs.table, key); + } + }; + return { getAdminConsoleConfig, updateAdminConsoleConfig, @@ -93,5 +108,6 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => { updateOidcConfigsByKey, upsertJwtCustomizer, getJwtCustomizer, + deleteJwtCustomizer, }; }; diff --git a/packages/core/src/routes/logto-config.openapi.json b/packages/core/src/routes/logto-config.openapi.json index a24d14736..983f3cab7 100644 --- a/packages/core/src/routes/logto-config.openapi.json +++ b/packages/core/src/routes/logto-config.openapi.json @@ -187,6 +187,25 @@ "description": "The JWT customizer does not exist." } } + }, + "delete": { + "summary": "Delete JWT customizer", + "description": "Delete the JWT customizer for the given token type.", + "parameters": [ + { + "in": "path", + "name": "tokenType", + "description": "The token type to delete the JWT customizer for." + } + ], + "responses": { + "204": { + "description": "The JWT customizer was deleted successfully." + }, + "404": { + "description": "The JWT customizer does not exist." + } + } } } } diff --git a/packages/core/src/routes/logto-config.test.ts b/packages/core/src/routes/logto-config.test.ts index 9ccc9ad68..d9b146931 100644 --- a/packages/core/src/routes/logto-config.test.ts +++ b/packages/core/src/routes/logto-config.test.ts @@ -54,6 +54,7 @@ const logtoConfigQueries = { }), updateOidcConfigsByKey: jest.fn(), getRowsByKeys: jest.fn(async () => mockLogtoConfigRows), + deleteJwtCustomizer: jest.fn(), }; const logtoConfigLibraries = { @@ -284,4 +285,12 @@ describe('configs routes', () => { expect(response.status).toEqual(200); expect(response.body).toEqual(mockJwtCustomizerConfigForAccessToken.value); }); + + it('DELETE /configs/jwt-customizer/:tokenType should delete the record', async () => { + const response = await routeRequester.delete('/configs/jwt-customizer/client-credentials'); + expect(logtoConfigQueries.deleteJwtCustomizer).toHaveBeenCalledWith( + LogtoJwtTokenKey.ClientCredentials + ); + expect(response.status).toEqual(204); + }); }); diff --git a/packages/core/src/routes/logto-config.ts b/packages/core/src/routes/logto-config.ts index b4d4ab9ea..bb258949a 100644 --- a/packages/core/src/routes/logto-config.ts +++ b/packages/core/src/routes/logto-config.ts @@ -93,6 +93,7 @@ export default function logtoConfigRoutes( updateAdminConsoleConfig, updateOidcConfigsByKey, getJwtCustomizer, + deleteJwtCustomizer, } = queries.logtoConfigs; const { getOidcConfigs } = logtoConfigs; >>>>>>> 8086c9bc6 (feat(core): add GET /configs/jwt-customizer API) @@ -275,4 +276,23 @@ export default function logtoConfigRoutes( return next(); } ); + + router.delete( + '/configs/jwt-customizer/:tokenType', + koaGuard({ + params: z.object({ + tokenType: z.nativeEnum(LogtoJwtTokenKeyType), + }), + status: [204, 404], + }), + async (ctx, next) => { + const { + params: { tokenType }, + } = ctx.guard; + + await deleteJwtCustomizer(getLogtoJwtTokenKey(tokenType)); + ctx.status = 204; + return next(); + } + ); } diff --git a/packages/integration-tests/src/api/logto-config.ts b/packages/integration-tests/src/api/logto-config.ts index 29deba26c..44fe3981f 100644 --- a/packages/integration-tests/src/api/logto-config.ts +++ b/packages/integration-tests/src/api/logto-config.ts @@ -50,3 +50,6 @@ export const getJwtCustomizer = async (keyType: LogtoJwtTokenKeyType) => authedAdminApi .get(`configs/jwt-customizer/${keyType}`) .json(); + +export const deleteJwtCustomizer = async (keyType: LogtoJwtTokenKeyType) => + authedAdminApi.delete(`configs/jwt-customizer/${keyType}`); diff --git a/packages/integration-tests/src/tests/api/logto-config.test.ts b/packages/integration-tests/src/tests/api/logto-config.test.ts index d1b066e2e..261f1332c 100644 --- a/packages/integration-tests/src/tests/api/logto-config.test.ts +++ b/packages/integration-tests/src/tests/api/logto-config.test.ts @@ -3,6 +3,7 @@ import { type AdminConsoleData, LogtoOidcConfigKeyType, } from '@logto/schemas'; +import { HTTPError } from 'got'; import { deleteOidcKey, @@ -12,6 +13,7 @@ import { updateAdminConsoleConfig, upsertJwtCustomizer, getJwtCustomizer, + deleteJwtCustomizer, } from '#src/api/index.js'; import { expectRejects } from '#src/helpers/index.js'; @@ -126,7 +128,7 @@ describe('admin console sign-in experience', () => { expect(privateKeys2[1]?.id).toBe(privateKeys[0]?.id); }); - it('should successfully POST and GET a JWT customizer (access token)', async () => { + it('should successfully POST/GET/DELETE a JWT customizer (access token)', async () => { const accessTokenJwtCustomizerPayload = { script: '', envVars: {}, @@ -138,11 +140,25 @@ describe('admin console sign-in experience', () => { }, }; + const accessToken = await upsertJwtCustomizer('access-token', accessTokenJwtCustomizerPayload); + /** + * The 'access-token' JWT customizer has not been created, as a result: + * - GET request should fail with a 404 error + * - DELETE request should fail with a 404 error + */ await expectRejects(getJwtCustomizer('access-token'), { code: 'entity.not_exists', statusCode: 404, }); - const accessToken = await upsertJwtCustomizer('access-token', accessTokenJwtCustomizerPayload); + const response = await deleteJwtCustomizer('access-token').catch( + (error: unknown) => error + ); + expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true); + + const accessToken = await insertJwtCustomizer( + 'access-token', + accessTokenJwtCustomizerPayload + ); expect(accessToken).toMatchObject(accessTokenJwtCustomizerPayload); const newAccessTokenJwtCustomizerPayload = { ...accessTokenJwtCustomizerPayload, @@ -156,21 +172,44 @@ describe('admin console sign-in experience', () => { await expect(getJwtCustomizer('access-token')).resolves.toMatchObject( newAccessTokenJwtCustomizerPayload ); + + /** + * The 'access-token' JWT customizer has been created, as a result: + * - DELETE request should succeed + * - GET request should fail with a 404 error after the successful DELETE request + */ + await expect(deleteJwtCustomizer('access-token')).resolves.not.toThrow(); + await expectRejects(getJwtCustomizer('access-token'), { + code: 'entity.not_found', + statusCode: 404, + }); }); - it('should successfully POST and GET a JWT customizer (client credentials)', async () => { + it('should successfully POST/GET/DELETE a JWT customizer (client credentials)', async () => { const clientCredentialsJwtCustomizerPayload = { script: '', envVars: {}, contextSample: {}, }; + const clientCredentials = await upsertJwtCustomizer( + 'client-credentials', + /** + * The 'client-credentials' JWT customizer has not been created, as a result: + * - GET request should fail with a 404 error + * - DELETE request should fail with a 404 error + */ await expectRejects(getJwtCustomizer('client-credentials'), { code: 'entity.not_exists', statusCode: 404, }); - const clientCredentials = await upsertJwtCustomizer( - 'client-credentials', + const response = await deleteJwtCustomizer(LogtoJwtTokenKeyType.ClientCredentials).catch( + (error: unknown) => error + ); + expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true); + + const clientCredentials = await insertJwtCustomizer( + LogtoJwtTokenKeyType.ClientCredentials, clientCredentialsJwtCustomizerPayload ); expect(clientCredentials).toMatchObject(clientCredentialsJwtCustomizerPayload); @@ -186,5 +225,16 @@ describe('admin console sign-in experience', () => { await expect(getJwtCustomizer('client-credentials')).resolves.toMatchObject( newClientCredentialsJwtCustomizerPayload ); + + /** + * The 'client-credentials' JWT customizer has been created, as a result: + * - DELETE request should succeed + * - GET request should fail with a 404 error after the successful DELETE request + */ + await deleteJwtCustomizer(LogtoJwtTokenKeyType.ClientCredentials); + await expectRejects(getJwtCustomizer(LogtoJwtTokenKeyType.ClientCredentials), { + code: 'entity.not_found', + statusCode: 404, + }); }); });