0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

feat(core): undeploy worker scripts when jwt customizer is deleted (#5685)

undeloy work scripts when the jwt customizer is deleted
This commit is contained in:
simeng-li 2024-04-12 11:05:11 +08:00 committed by GitHub
parent 9b3d4ef75b
commit 543931aa88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 68 additions and 15 deletions

View file

@ -2,6 +2,8 @@ import { GlobalValues } from '@logto/shared';
import { createMockUtils } from '@logto/shared/esm';
import nock from 'nock';
import { mockLogtoConfigsLibrary } from '#src/test-utils/mock-libraries.js';
import { type LogtoConfigLibrary } from './logto-config.js';
const { jest } = import.meta;
@ -28,17 +30,12 @@ await mockEsmWithActual('#src/env-set/index.js', () => ({
const { createCloudConnectionLibrary } = await import('./cloud-connection.js');
const logtoConfigs: LogtoConfigLibrary = {
...mockLogtoConfigsLibrary,
getCloudConnectionData: jest.fn().mockResolvedValue({
appId: 'appId',
appSecret: 'appSecret',
resource: 'resource',
}),
getOidcConfigs: jest.fn(),
upsertJwtCustomizer: jest.fn(),
getJwtCustomizer: jest.fn(),
getJwtCustomizers: jest.fn(),
updateJwtCustomizer: jest.fn(),
deployJwtCustomizerScript: jest.fn(),
};
describe('getAccessToken()', () => {

View file

@ -8,6 +8,7 @@ import {
jwtCustomizerConfigGuard,
logtoOidcConfigGuard,
} from '@logto/schemas';
import { assert } from '@silverhand/essentials';
import chalk from 'chalk';
import { ZodError, z } from 'zod';
@ -173,6 +174,36 @@ export const createLogtoConfigLibrary = ({
});
};
const undeployJwtCustomizerScript = async <T extends LogtoJwtTokenKey>(
cloudConnection: CloudConnectionLibrary,
key: T
) => {
const [client, jwtCustomizers] = await Promise.all([
cloudConnection.getClient(),
getJwtCustomizers(),
]);
assert(jwtCustomizers[key], new RequestError({ code: 'entity.not_exists', key }));
// Undeploy the worker directly if the only JWT customizer is being deleted.
if (Object.entries(jwtCustomizers).length === 1) {
await client.delete(`/api/services/custom-jwt/worker`);
return;
}
// Remove the JWT customizer script from the existing JWT customizer scripts and redeploy.
const customizerScriptsFromDatabase = getJwtCustomizerScripts(jwtCustomizers);
await client.put(`/api/services/custom-jwt/worker`, {
body: {
production: {
...customizerScriptsFromDatabase,
[key]: undefined,
},
},
});
};
return {
getOidcConfigs,
getCloudConnectionData,
@ -181,5 +212,6 @@ export const createLogtoConfigLibrary = ({
getJwtCustomizers,
updateJwtCustomizer,
deployJwtCustomizerScript,
undeployJwtCustomizerScript,
};
};

View file

@ -11,6 +11,7 @@ import {
} from '#src/__mocks__/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { ssoConnectorFactories } from '#src/sso/index.js';
import { mockLogtoConfigsLibrary } from '#src/test-utils/mock-libraries.js';
import { createCloudConnectionLibrary } from '../cloud-connection.js';
import { createConnectorLibrary } from '../connector.js';
@ -51,17 +52,12 @@ const connectorLibrary = createConnectorLibrary(queries, {
getClient: jest.fn(),
});
const cloudConnection = createCloudConnectionLibrary({
...mockLogtoConfigsLibrary,
getCloudConnectionData: jest.fn().mockResolvedValue({
appId: 'appId',
appSecret: 'appSecret',
resource: 'resource',
}),
getOidcConfigs: jest.fn(),
upsertJwtCustomizer: jest.fn(),
getJwtCustomizer: jest.fn(),
getJwtCustomizers: jest.fn(),
updateJwtCustomizer: jest.fn(),
deployJwtCustomizerScript: jest.fn(),
});
const getLogtoConnectors = jest.spyOn(connectorLibrary, 'getLogtoConnectors');

View file

@ -24,6 +24,7 @@ const logtoConfigLibraries = {
getJwtCustomizers: jest.fn(),
updateJwtCustomizer: jest.fn(),
deployJwtCustomizerScript: jest.fn(),
undeployJwtCustomizerScript: jest.fn(),
};
const settingRoutes = await pickDefault(import('./index.js'));
@ -126,6 +127,10 @@ describe('configs JWT customizer routes', () => {
it('DELETE /configs/jwt-customizer/:tokenType should delete the record', async () => {
const response = await routeRequester.delete('/configs/jwt-customizer/client-credentials');
expect(logtoConfigLibraries.undeployJwtCustomizerScript).toHaveBeenCalledWith(
tenantContext.cloudConnection,
LogtoJwtTokenKey.ClientCredentials
);
expect(logtoConfigQueries.deleteJwtCustomizer).toHaveBeenCalledWith(
LogtoJwtTokenKey.ClientCredentials
);

View file

@ -40,6 +40,7 @@ export default function logtoConfigJwtCustomizerRoutes<T extends AuthedRouter>(
getJwtCustomizers,
updateJwtCustomizer,
deployJwtCustomizerScript,
undeployJwtCustomizerScript,
} = logtoConfigs;
router.put(
@ -177,15 +178,23 @@ export default function logtoConfigJwtCustomizerRoutes<T extends AuthedRouter>(
status: [204, 404],
}),
async (ctx, next) => {
const { isIntegrationTest } = EnvSet.values;
const {
params: { tokenTypePath },
} = ctx.guard;
await deleteJwtCustomizer(
const tokenKey =
tokenTypePath === LogtoJwtTokenKeyType.AccessToken
? LogtoJwtTokenKey.AccessToken
: LogtoJwtTokenKey.ClientCredentials
);
: LogtoJwtTokenKey.ClientCredentials;
// Undeploy the script first to avoid the case where the JWT customizer was deleted from DB but worker script not updated successfully.
if (!isIntegrationTest) {
await undeployJwtCustomizerScript(cloudConnection, tokenKey);
}
await deleteJwtCustomizer(tokenKey);
ctx.status = 204;
return next();
}

View file

@ -0,0 +1,14 @@
import { type LogtoConfigLibrary } from '#src/libraries/logto-config.js';
const { jest } = import.meta;
export const mockLogtoConfigsLibrary: LogtoConfigLibrary = {
getCloudConnectionData: jest.fn(),
getOidcConfigs: jest.fn(),
upsertJwtCustomizer: jest.fn(),
getJwtCustomizer: jest.fn(),
getJwtCustomizers: jest.fn(),
updateJwtCustomizer: jest.fn(),
deployJwtCustomizerScript: jest.fn(),
undeployJwtCustomizerScript: jest.fn(),
};