0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

Merge pull request #5467 from logto-io/yemq-log-8285-add-DELETE-configs-jwt-customizer-API

feat(core): add DELETE /configs/jwt-customizer API
This commit is contained in:
Darcy Ye 2024-03-07 15:24:02 +08:00 committed by GitHub
commit a00badc891
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 98 additions and 23 deletions

View file

@ -1,5 +1,5 @@
import {
jwtCustomizerConfigGuard,
type jwtCustomizerConfigGuard,
LogtoTenantConfigKey,
LogtoConfigs,
type AdminConsoleData,
@ -12,9 +12,9 @@ import {
import { convertToIdentifiers } from '@logto/shared';
import type { CommonQueryMethods } from 'slonik';
import { sql } from 'slonik';
import { z } from 'zod';
import { type z } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import { DeletionError } from '#src/errors/SlonikError/index.js';
const { table, fields } = convertToIdentifiers(LogtoConfigs);
@ -45,6 +45,17 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => {
where ${fields.key} in (${sql.join(keys, sql`,`)})
`);
const deleteRowByKey = async (key: LogtoConfigKey) => {
const { rowCount } = await pool.query(sql`
delete from ${table}
where ${fields.key}=${key}
`);
if (rowCount < 1) {
throw new DeletionError(LogtoConfigs.table, key);
}
};
const updateOidcConfigsByKey = async (key: LogtoOidcConfigKey, value: OidcConfigKey[]) =>
pool.query(sql`
update ${table}
@ -69,21 +80,7 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => {
`
);
const getJwtCustomizer = async <T extends LogtoJwtTokenKey>(key: T) => {
const { rows } = await getRowsByKeys([key]);
// If the record does not exist (`rows` is empty)
if (rows.length === 0) {
throw new RequestError({
code: 'entity.not_exists',
name: table,
id: key,
status: 404,
});
}
return z.object({ value: jwtCustomizerConfigGuard[key] }).parse(rows[0]);
};
const deleteJwtCustomizer = async <T extends LogtoJwtTokenKey>(key: T) => deleteRowByKey(key);
return {
getAdminConsoleConfig,
@ -92,6 +89,6 @@ export const createLogtoConfigQueries = (pool: CommonQueryMethods) => {
getRowsByKeys,
updateOidcConfigsByKey,
upsertJwtCustomizer,
getJwtCustomizer,
deleteJwtCustomizer,
};
};

View file

@ -168,6 +168,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": "tokenTypePath",
"description": "The token type path to delete the JWT customizer for."
}
],
"responses": {
"204": {
"description": "The JWT customizer was deleted successfully."
},
"404": {
"description": "The JWT customizer does not exist."
}
}
}
}
}

View file

@ -54,6 +54,7 @@ const logtoConfigQueries = {
}),
updateOidcConfigsByKey: jest.fn(),
getRowsByKeys: jest.fn(async () => mockLogtoConfigRows),
deleteJwtCustomizer: jest.fn(),
};
const logtoConfigLibraries = {
@ -275,4 +276,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);
});
});

View file

@ -81,8 +81,13 @@ const getRedactedOidcKeyResponse = async (
export default function logtoConfigRoutes<T extends AuthedRouter>(
...[router, { queries, logtoConfigs, invalidateCache }]: RouterInitArgs<T>
) {
const { getAdminConsoleConfig, getRowsByKeys, updateAdminConsoleConfig, updateOidcConfigsByKey } =
queries.logtoConfigs;
const {
getAdminConsoleConfig,
getRowsByKeys,
updateAdminConsoleConfig,
updateOidcConfigsByKey,
deleteJwtCustomizer,
} = queries.logtoConfigs;
const { getOidcConfigs, upsertJwtCustomizer, getJwtCustomizer } = logtoConfigs;
router.get(
@ -263,4 +268,27 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
return next();
}
);
router.delete(
'/configs/jwt-customizer/:tokenTypePath',
koaGuard({
params: z.object({
tokenTypePath: z.nativeEnum(LogtoJwtTokenPath),
}),
status: [204, 404],
}),
async (ctx, next) => {
const {
params: { tokenTypePath },
} = ctx.guard;
await deleteJwtCustomizer(
tokenTypePath === LogtoJwtTokenPath.AccessToken
? LogtoJwtTokenKey.AccessToken
: LogtoJwtTokenKey.ClientCredentials
);
ctx.status = 204;
return next();
}
);
}

View file

@ -45,3 +45,6 @@ export const getJwtCustomizer = async (keyTypePath: 'access-token' | 'client-cre
authedAdminApi
.get(`configs/jwt-customizer/${keyTypePath}`)
.json<JwtCustomizerAccessToken | JwtCustomizerClientCredentials>();
export const deleteJwtCustomizer = async (keyTypePath: 'access-token' | 'client-credentials') =>
authedAdminApi.delete(`configs/jwt-customizer/${keyTypePath}`);

View file

@ -12,6 +12,7 @@ import {
updateAdminConsoleConfig,
upsertJwtCustomizer,
getJwtCustomizer,
deleteJwtCustomizer,
} from '#src/api/index.js';
import { expectRejects } from '#src/helpers/index.js';
@ -126,7 +127,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 PUT/GET/DELETE a JWT customizer (access token)', async () => {
const accessTokenJwtCustomizerPayload = {
script: '',
envVars: {},
@ -142,6 +143,10 @@ describe('admin console sign-in experience', () => {
code: 'entity.not_exists',
statusCode: 404,
});
await expectRejects(deleteJwtCustomizer('access-token'), {
code: 'entity.not_found',
statusCode: 404,
});
const accessToken = await upsertJwtCustomizer('access-token', accessTokenJwtCustomizerPayload);
expect(accessToken).toMatchObject(accessTokenJwtCustomizerPayload);
const newAccessTokenJwtCustomizerPayload = {
@ -156,9 +161,14 @@ describe('admin console sign-in experience', () => {
await expect(getJwtCustomizer('access-token')).resolves.toMatchObject(
newAccessTokenJwtCustomizerPayload
);
await expect(deleteJwtCustomizer('access-token')).resolves.not.toThrow();
await expectRejects(getJwtCustomizer('access-token'), {
code: 'entity.not_exists',
statusCode: 404,
});
});
it('should successfully POST and GET a JWT customizer (client credentials)', async () => {
it('should successfully PUT/GET/DELETE a JWT customizer (client credentials)', async () => {
const clientCredentialsJwtCustomizerPayload = {
script: '',
envVars: {},
@ -169,6 +179,10 @@ describe('admin console sign-in experience', () => {
code: 'entity.not_exists',
statusCode: 404,
});
await expectRejects(deleteJwtCustomizer('client-credentials'), {
code: 'entity.not_found',
statusCode: 404,
});
const clientCredentials = await upsertJwtCustomizer(
'client-credentials',
clientCredentialsJwtCustomizerPayload
@ -186,5 +200,10 @@ describe('admin console sign-in experience', () => {
await expect(getJwtCustomizer('client-credentials')).resolves.toMatchObject(
newClientCredentialsJwtCustomizerPayload
);
await expect(deleteJwtCustomizer('client-credentials')).resolves.not.toThrow();
await expectRejects(getJwtCustomizer('client-credentials'), {
code: 'entity.not_exists',
statusCode: 404,
});
});
});