mirror of
https://github.com/Kong/insomnia.git
synced 2026-08-02 19:02:29 -04:00
fix(sandbox): migrate caCertificate/clientCertificate off the services.invoke gateway (Phase 1a)
Registers a named services.<serviceName>.<methodName> IPC handler for each of the 10 caCertificate/clientCertificate pairs instead of routing them through the generic reflection-based services.invoke dispatcher. The renderer proxy (preload bridge and the non-isolated fallback) now consults a shared migrated-pairs list to pick the named channel for these pairs while everything else keeps using the legacy gateway, with a test cross-checking the list against what main/ipc actually registers so the two can't drift apart. A parity test proves each named handler forwards the same args to the same services.* call the old dispatch made. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import type { GenerateMcpSamplingResponseFunction } from '~/common/plugins/types
|
||||
import type { RenderedRequest } from '~/common/templating/types';
|
||||
import { invariant } from '~/common/utils/invariant';
|
||||
import { invokeWithNormalizedError } from '~/main/ipc/invoke';
|
||||
import { resolveServicesInvokeChannel } from '~/main/ipc/migrated-services-invoke-pairs';
|
||||
import type { LLMBackend, LLMConfig, LLMConfigServiceAPI } from '~/main/llm-config-service';
|
||||
import type { PluginInvokeMethod } from '~/plugins/invoke-method';
|
||||
import { isUserAbortResolveMergeConflictError, UserAbortResolveMergeConflictError } from '~/sync/vcs/errors';
|
||||
@@ -552,8 +553,12 @@ if (process.contextIsolated) {
|
||||
// function and rebuild the services Proxy in the isolated renderer world.
|
||||
contextBridge.exposeInMainWorld(
|
||||
'_dataServicesInvoke',
|
||||
(serviceName: string, methodName: string, ...args: unknown[]) =>
|
||||
invokeWithNormalizedError('services.invoke', serviceName, methodName, ...args),
|
||||
(serviceName: string, methodName: string, ...args: unknown[]) => {
|
||||
const channel = resolveServicesInvokeChannel(serviceName, methodName);
|
||||
return channel === 'services.invoke'
|
||||
? invokeWithNormalizedError('services.invoke', serviceName, methodName, ...args)
|
||||
: invokeWithNormalizedError(channel, ...args);
|
||||
},
|
||||
);
|
||||
contextBridge.exposeInMainWorld('env', env);
|
||||
} else {
|
||||
|
||||
@@ -6,16 +6,16 @@ exports[`describeServicesInvokeSurface (real repo) > matches the current service
|
||||
"apiSpec.getOrCreateForParentId: services.invoke (1 call site)",
|
||||
"apiSpec.update: services.invoke (1 call site)",
|
||||
"apiSpec.updateOrCreateForParentId: services.invoke (1 call site)",
|
||||
"caCertificate.create: services.invoke (1 call site)",
|
||||
"caCertificate.getById: services.invoke (1 call site)",
|
||||
"caCertificate.getByParentId: services.invoke (4 call sites)",
|
||||
"caCertificate.removeWhere: services.invoke (1 call site)",
|
||||
"caCertificate.update: services.invoke (1 call site)",
|
||||
"clientCertificate.create: services.invoke (1 call site)",
|
||||
"clientCertificate.findByParentId: services.invoke (5 call sites)",
|
||||
"clientCertificate.getById: services.invoke (2 call sites)",
|
||||
"clientCertificate.remove: services.invoke (1 call site)",
|
||||
"clientCertificate.update: services.invoke (1 call site)",
|
||||
"caCertificate.create: named handler (1 call site)",
|
||||
"caCertificate.getById: named handler (1 call site)",
|
||||
"caCertificate.getByParentId: named handler (4 call sites)",
|
||||
"caCertificate.removeWhere: named handler (1 call site)",
|
||||
"caCertificate.update: named handler (1 call site)",
|
||||
"clientCertificate.create: named handler (1 call site)",
|
||||
"clientCertificate.findByParentId: named handler (5 call sites)",
|
||||
"clientCertificate.getById: named handler (2 call sites)",
|
||||
"clientCertificate.remove: named handler (1 call site)",
|
||||
"clientCertificate.update: named handler (1 call site)",
|
||||
"cloudCredential.all: services.invoke (3 call sites)",
|
||||
"cloudCredential.create: services.invoke (1 call site)",
|
||||
"cloudCredential.getById: services.invoke (4 call sites)",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as handlers from '../services-invoke-migrated-handlers';
|
||||
|
||||
vi.mock('insomnia-data', () => ({
|
||||
services: {
|
||||
caCertificate: { create: vi.fn(), getById: vi.fn(), getByParentId: vi.fn(), removeWhere: vi.fn(), update: vi.fn() },
|
||||
clientCertificate: { create: vi.fn(), findByParentId: vi.fn(), getById: vi.fn(), remove: vi.fn(), update: vi.fn() },
|
||||
},
|
||||
}));
|
||||
|
||||
// Reference behavior: exactly what the old services.invoke reflection dispatch did for a
|
||||
// (serviceName, methodName) pair — `services[serviceName][methodName](...args)`. Every migrated
|
||||
// named handler below is checked against this, not just against its own naming convention.
|
||||
const legacyDispatch = async (serviceName: string, methodName: string, ...args: unknown[]) => {
|
||||
const { services } = await import('insomnia-data');
|
||||
const service = (services as unknown as Record<string, Record<string, (...a: unknown[]) => unknown>>)[serviceName];
|
||||
return service[methodName](...args);
|
||||
};
|
||||
|
||||
const CASES: { handlerName: keyof typeof handlers; serviceName: string; methodName: string; args: unknown[] }[] = [
|
||||
{ handlerName: 'caCertificateCreate', serviceName: 'caCertificate', methodName: 'create', args: [{ parentId: 'w1' }] },
|
||||
{ handlerName: 'caCertificateGetById', serviceName: 'caCertificate', methodName: 'getById', args: ['cert1'] },
|
||||
{ handlerName: 'caCertificateGetByParentId', serviceName: 'caCertificate', methodName: 'getByParentId', args: ['w1'] },
|
||||
{ handlerName: 'caCertificateRemoveWhere', serviceName: 'caCertificate', methodName: 'removeWhere', args: ['w1'] },
|
||||
{ handlerName: 'caCertificateUpdate', serviceName: 'caCertificate', methodName: 'update', args: [{ _id: 'cert1' }, { path: '/new.pem' }] },
|
||||
{ handlerName: 'clientCertificateCreate', serviceName: 'clientCertificate', methodName: 'create', args: [{ parentId: 'w1' }] },
|
||||
{ handlerName: 'clientCertificateFindByParentId', serviceName: 'clientCertificate', methodName: 'findByParentId', args: ['w1'] },
|
||||
{ handlerName: 'clientCertificateGetById', serviceName: 'clientCertificate', methodName: 'getById', args: ['cert1'] },
|
||||
{ handlerName: 'clientCertificateRemove', serviceName: 'clientCertificate', methodName: 'remove', args: [{ _id: 'cert1' }] },
|
||||
{ handlerName: 'clientCertificateUpdate', serviceName: 'clientCertificate', methodName: 'update', args: [{ _id: 'cert1' }, { host: 'localhost' }] },
|
||||
];
|
||||
|
||||
describe.each(CASES)('$handlerName', ({ handlerName, serviceName, methodName, args }) => {
|
||||
it('forwards to the same services.* call, with the same args and result, as the old services.invoke dispatch', async () => {
|
||||
const { services } = await import('insomnia-data');
|
||||
const service = (services as unknown as Record<string, Record<string, ReturnType<typeof vi.fn>>>)[serviceName];
|
||||
service[methodName].mockReset().mockResolvedValue({ sentinel: `${handlerName}-result` });
|
||||
|
||||
const legacyResult = await legacyDispatch(serviceName, methodName, ...args);
|
||||
const namedHandlerResult = await (handlers[handlerName] as (...a: unknown[]) => unknown)({}, ...args);
|
||||
|
||||
expect(namedHandlerResult).toEqual(legacyResult);
|
||||
expect(service[methodName]).toHaveBeenCalledWith(...args);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import path from 'node:path';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { MIGRATED_SERVICES_INVOKE_PAIRS } from '../migrated-services-invoke-pairs';
|
||||
import {
|
||||
describeServicesInvokeSurface,
|
||||
findPairsMissingNamedHandler,
|
||||
@@ -96,4 +97,14 @@ describe('describeServicesInvokeSurface (real repo)', () => {
|
||||
expect(entries.length).toBeGreaterThan(0);
|
||||
expect(formatServicesInvokeSurfaceEntries(entries)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// Keeps the renderer-transport allowlist (migrated-services-invoke-pairs.ts) from drifting out of
|
||||
// sync with what main/ipc/*.ts actually registers: a pair the proxy would route to a named channel
|
||||
// that doesn't exist would hard-fail every call; a pair with a named handler the proxy doesn't know
|
||||
// about would silently keep using the generic gateway.
|
||||
it('has a named handler for every pair the renderer proxy is told to route there, and vice versa', () => {
|
||||
const entries = describeServicesInvokeSurface();
|
||||
const detectedMigrated = new Set(entries.filter(e => e.hasNamedHandler).map(e => e.pair));
|
||||
expect(detectedMigrated).toEqual(new Set(MIGRATED_SERVICES_INVOKE_PAIRS));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,6 +43,16 @@ export type HandleChannels =
|
||||
| 'curlRequest'
|
||||
| 'database.caCertificate.create'
|
||||
| 'services.invoke'
|
||||
| 'services.caCertificate.create'
|
||||
| 'services.caCertificate.getById'
|
||||
| 'services.caCertificate.getByParentId'
|
||||
| 'services.caCertificate.removeWhere'
|
||||
| 'services.caCertificate.update'
|
||||
| 'services.clientCertificate.create'
|
||||
| 'services.clientCertificate.findByParentId'
|
||||
| 'services.clientCertificate.getById'
|
||||
| 'services.clientCertificate.remove'
|
||||
| 'services.clientCertificate.update'
|
||||
| 'extractJsonFileFromPostmanDataDumpArchive'
|
||||
| 'generateCommitsFromDiff'
|
||||
| 'generateMockRouteDataFromSpec'
|
||||
|
||||
@@ -44,6 +44,18 @@ import {
|
||||
exportWorkspacesHAR,
|
||||
} from '~/main/har';
|
||||
import { convert } from '~/main/importers/convert';
|
||||
import {
|
||||
caCertificateCreate,
|
||||
caCertificateGetById,
|
||||
caCertificateGetByParentId,
|
||||
caCertificateRemoveWhere,
|
||||
caCertificateUpdate,
|
||||
clientCertificateCreate,
|
||||
clientCertificateFindByParentId,
|
||||
clientCertificateGetById,
|
||||
clientCertificateRemove,
|
||||
clientCertificateUpdate,
|
||||
} from '~/main/ipc/services-invoke-migrated-handlers';
|
||||
import { getCurrentConfig, type LLMConfigServiceAPI } from '~/main/llm-config-service';
|
||||
import { multipartBufferToArray, type Part } from '~/main/multipart-buffer-to-array';
|
||||
import { insecureReadFile, insecureReadFileWithEncoding, isPathAllowed, secureReadFile } from '~/main/secure-read-file';
|
||||
@@ -396,6 +408,16 @@ export function registerMainHandlers() {
|
||||
ipcMainHandle('database.caCertificate.create', async (_, options: { parentId: string; path: string }) => {
|
||||
return services.caCertificate.create(options);
|
||||
});
|
||||
ipcMainHandle('services.caCertificate.create', caCertificateCreate);
|
||||
ipcMainHandle('services.caCertificate.getById', caCertificateGetById);
|
||||
ipcMainHandle('services.caCertificate.getByParentId', caCertificateGetByParentId);
|
||||
ipcMainHandle('services.caCertificate.removeWhere', caCertificateRemoveWhere);
|
||||
ipcMainHandle('services.caCertificate.update', caCertificateUpdate);
|
||||
ipcMainHandle('services.clientCertificate.create', clientCertificateCreate);
|
||||
ipcMainHandle('services.clientCertificate.findByParentId', clientCertificateFindByParentId);
|
||||
ipcMainHandle('services.clientCertificate.getById', clientCertificateGetById);
|
||||
ipcMainHandle('services.clientCertificate.remove', clientCertificateRemove);
|
||||
ipcMainHandle('services.clientCertificate.update', clientCertificateUpdate);
|
||||
ipcMainHandle('createPlugin', async (_, options: { pluginName: string; mainJs: string }) => {
|
||||
return createPlugin(options.pluginName, options.mainJs);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Single source of truth for which (serviceName, methodName) pairs have their own
|
||||
// `services.<serviceName>.<methodName>` IPC handler instead of routing through the generic
|
||||
// `services.invoke` gateway (see services-invoke-surface.ts, SERVICES-INVOKE-MIGRATION-PLAN.md).
|
||||
// Consulted by both the preload bridge and the non-isolated renderer proxy fallback so the two
|
||||
// transports can never pick a different channel for the same pair. No Node-only imports here — this
|
||||
// module is bundled into the renderer as well as the main process.
|
||||
export const MIGRATED_SERVICES_INVOKE_PAIRS: ReadonlySet<string> = new Set<string>([
|
||||
'caCertificate.create',
|
||||
'caCertificate.getById',
|
||||
'caCertificate.getByParentId',
|
||||
'caCertificate.removeWhere',
|
||||
'caCertificate.update',
|
||||
'clientCertificate.create',
|
||||
'clientCertificate.findByParentId',
|
||||
'clientCertificate.getById',
|
||||
'clientCertificate.remove',
|
||||
'clientCertificate.update',
|
||||
]);
|
||||
|
||||
/** The IPC channel a `services.<serviceName>.<methodName>` call should use: the named channel once migrated, else the legacy generic gateway. */
|
||||
export const resolveServicesInvokeChannel = (serviceName: string, methodName: string): string =>
|
||||
MIGRATED_SERVICES_INVOKE_PAIRS.has(`${serviceName}.${methodName}`)
|
||||
? `services.${serviceName}.${methodName}`
|
||||
: 'services.invoke';
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { CaCertificate, ClientCertificate } from 'insomnia-data';
|
||||
import { services } from 'insomnia-data';
|
||||
|
||||
// Named per-pair handlers for services.invoke pairs migrated off the generic reflection-based
|
||||
// gateway (see services-invoke-surface.ts, SERVICES-INVOKE-MIGRATION-PLAN.md). Each forwards to the
|
||||
// exact same services.* call the generic dispatch made for that pair, with the same arguments — main.ts
|
||||
// registers each of these under the literal channel name `services.<serviceName>.<methodName>`.
|
||||
|
||||
export const caCertificateCreate = (_: unknown, patch: Partial<CaCertificate> = {}) => services.caCertificate.create(patch);
|
||||
export const caCertificateGetById = (_: unknown, id: string) => services.caCertificate.getById(id);
|
||||
export const caCertificateGetByParentId = (_: unknown, parentId: string) => services.caCertificate.getByParentId(parentId);
|
||||
export const caCertificateRemoveWhere = (_: unknown, parentId: string) => services.caCertificate.removeWhere(parentId);
|
||||
export const caCertificateUpdate = (_: unknown, cert: CaCertificate, patch: Partial<CaCertificate> = {}) => services.caCertificate.update(cert, patch);
|
||||
|
||||
export const clientCertificateCreate = (_: unknown, patch: Partial<ClientCertificate> = {}) => services.clientCertificate.create(patch);
|
||||
export const clientCertificateFindByParentId = (_: unknown, parentId: string) => services.clientCertificate.findByParentId(parentId);
|
||||
export const clientCertificateGetById = (_: unknown, id: string) => services.clientCertificate.getById(id);
|
||||
export const clientCertificateRemove = (_: unknown, cert: ClientCertificate) => services.clientCertificate.remove(cert);
|
||||
export const clientCertificateUpdate = (_: unknown, cert: ClientCertificate, patch: Partial<ClientCertificate> = {}) => services.clientCertificate.update(cert, patch);
|
||||
@@ -1,6 +1,10 @@
|
||||
import { invokeWithNormalizedError } from '~/main/ipc/invoke';
|
||||
import { resolveServicesInvokeChannel } from '~/main/ipc/migrated-services-invoke-pairs';
|
||||
import { createServicesProxy } from '~/ui/services-proxy';
|
||||
|
||||
export const servicesProxy = createServicesProxy((serviceName, methodName, ...args) =>
|
||||
invokeWithNormalizedError<unknown>('services.invoke', serviceName, methodName, ...args),
|
||||
);
|
||||
export const servicesProxy = createServicesProxy((serviceName, methodName, ...args) => {
|
||||
const channel = resolveServicesInvokeChannel(serviceName, methodName);
|
||||
return channel === 'services.invoke'
|
||||
? invokeWithNormalizedError<unknown>('services.invoke', serviceName, methodName, ...args)
|
||||
: invokeWithNormalizedError<unknown>(channel, ...args);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user