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:
parent
5bc649e10d
commit
67be81e6df
4 changed files with 63 additions and 5 deletions
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue