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

feat(core): delete protected app (#5191)

This commit is contained in:
wangsijie 2024-01-11 15:30:42 +08:00 committed by GitHub
parent 5bc649e10d
commit 67be81e6df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 5 deletions

View file

@ -2,10 +2,30 @@ import { EnvSet } from '#src/env-set/index.js';
import type Queries from '#src/tenants/Queries.js'; import type Queries from '#src/tenants/Queries.js';
import SystemContext from '#src/tenants/SystemContext.js'; import SystemContext from '#src/tenants/SystemContext.js';
import assertThat from '#src/utils/assert-that.js'; import assertThat from '#src/utils/assert-that.js';
import { updateProtectedAppSiteConfigs } from '#src/utils/cloudflare/index.js'; import {
deleteProtectedAppSiteConfigs,
updateProtectedAppSiteConfigs,
} from '#src/utils/cloudflare/index.js';
export type ProtectedAppLibrary = ReturnType<typeof createProtectedAppLibrary>; export type ProtectedAppLibrary = ReturnType<typeof createProtectedAppLibrary>;
const getProviderConfig = async () => {
const { protectedAppConfigProviderConfig } = SystemContext.shared;
assertThat(protectedAppConfigProviderConfig, 'application.protected_app_not_configured');
return protectedAppConfigProviderConfig;
};
const deleteRemoteAppConfigs = async (host: string): Promise<void> => {
if (EnvSet.values.isIntegrationTest) {
return;
}
const protectedAppConfigProviderConfig = await getProviderConfig();
await deleteProtectedAppSiteConfigs(protectedAppConfigProviderConfig, host);
};
export const createProtectedAppLibrary = (queries: Queries) => { export const createProtectedAppLibrary = (queries: Queries) => {
const { const {
applications: { findApplicationById }, applications: { findApplicationById },
@ -17,8 +37,7 @@ export const createProtectedAppLibrary = (queries: Queries) => {
return; return;
} }
const { protectedAppConfigProviderConfig } = SystemContext.shared; const protectedAppConfigProviderConfig = await getProviderConfig();
assertThat(protectedAppConfigProviderConfig, 'application.protected_app_not_configured');
const { protectedAppMetadata, id, secret } = await findApplicationById(applicationId); const { protectedAppMetadata, id, secret } = await findApplicationById(applicationId);
if (!protectedAppMetadata) { if (!protectedAppMetadata) {
@ -41,5 +60,6 @@ export const createProtectedAppLibrary = (queries: Queries) => {
return { return {
syncAppConfigsToRemote, syncAppConfigsToRemote,
deleteRemoteAppConfigs,
}; };
}; };

View file

@ -12,6 +12,7 @@ const { jest } = import.meta;
const findApplicationById = jest.fn(async () => mockApplication); const findApplicationById = jest.fn(async () => mockApplication);
const deleteApplicationById = jest.fn(); const deleteApplicationById = jest.fn();
const syncAppConfigsToRemote = jest.fn(); const syncAppConfigsToRemote = jest.fn();
const deleteRemoteAppConfigs = jest.fn();
const updateApplicationById = jest.fn( const updateApplicationById = jest.fn(
async (_, data: Partial<CreateApplication>): Promise<Application> => ({ async (_, data: Partial<CreateApplication>): Promise<Application> => ({
...mockApplication, ...mockApplication,
@ -43,7 +44,10 @@ const tenantContext = new MockTenant(
}, },
}, },
undefined, undefined,
{ quota: createMockQuotaLibrary(), protectedApps: { syncAppConfigsToRemote } } {
quota: createMockQuotaLibrary(),
protectedApps: { syncAppConfigsToRemote, deleteRemoteAppConfigs },
}
); );
const { createRequester } = await import('#src/utils/test-utils.js'); const { createRequester } = await import('#src/utils/test-utils.js');
@ -64,6 +68,7 @@ describe('application route', () => {
afterEach(() => { afterEach(() => {
updateApplicationById.mockClear(); updateApplicationById.mockClear();
syncAppConfigsToRemote.mockClear(); syncAppConfigsToRemote.mockClear();
deleteRemoteAppConfigs.mockClear();
}); });
const applicationRequest = createRequester({ authedRoutes: applicationRoutes, tenantContext }); const applicationRequest = createRequester({ authedRoutes: applicationRoutes, tenantContext });
@ -303,11 +308,15 @@ describe('application route', () => {
); );
}); });
it('DELETE /applications/:applicationId', async () => { it('DELETE /applications/:applicationId for protected app', async () => {
findApplicationById.mockResolvedValueOnce(mockProtectedApplication);
await expect(applicationRequest.delete('/applications/foo')).resolves.toHaveProperty( await expect(applicationRequest.delete('/applications/foo')).resolves.toHaveProperty(
'status', 'status',
204 204
); );
expect(deleteRemoteAppConfigs).toHaveBeenCalledWith(
mockProtectedApplication.protectedAppMetadata?.host
);
}); });
it('DELETE /applications/:applicationId should throw if application not found', async () => { it('DELETE /applications/:applicationId should throw if application not found', async () => {

View file

@ -306,6 +306,10 @@ export default function applicationRoutes<T extends AuthedRouter>(
}), }),
async (ctx, next) => { async (ctx, next) => {
const { id } = ctx.guard.params; const { id } = ctx.guard.params;
const { type, protectedAppMetadata } = await findApplicationById(id);
if (type === ApplicationType.Protected && protectedAppMetadata) {
await protectedApps.deleteRemoteAppConfigs(protectedAppMetadata.host);
}
// Note: will need delete cascade when application is joint with other tables // Note: will need delete cascade when application is joint with other tables
await deleteApplicationById(id); await deleteApplicationById(id);
ctx.status = 204; ctx.status = 204;

View file

@ -54,3 +54,28 @@ export const updateProtectedAppSiteConfigs = async (
handleResponse(response); handleResponse(response);
}; };
export const deleteProtectedAppSiteConfigs = async (
auth: ProtectedAppConfigProviderData,
host: string
) => {
const response = await got.delete(
new URL(
path.join(
baseUrl.pathname,
`/accounts/${auth.accountIdentifier}/storage/kv/namespaces/${
auth.namespaceIdentifier
}/values/${encodeURIComponent(`${auth.keyName}:${host}`)}`
),
baseUrl
),
{
headers: {
Authorization: `Bearer ${auth.apiToken}`,
},
throwHttpErrors: false,
}
);
handleResponse(response);
};