Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Using global private environment to save secrets[INS-4715] #8233

Open
wants to merge 41 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7c0e2af
1.add external vault context menu
cwangsmv Oct 31, 2024
f1973d2
1.Add cloud credential model
cwangsmv Nov 11, 2024
cc38d40
1.fix main process integration issue
cwangsmv Nov 12, 2024
10099be
1.adjust layout for upgrade notice
cwangsmv Nov 13, 2024
c7b970f
1.add logic to handle vault secret items(vault environment)
cwangsmv Nov 13, 2024
361c185
1.add modal to confirm export private environment when user exports g…
cwangsmv Nov 14, 2024
4092d5d
1.fix aws secret manager tag
cwangsmv Nov 15, 2024
50dff22
1.use hook to get plan detail
cwangsmv Nov 15, 2024
0b55a50
1.remove unnecessary style color
cwangsmv Nov 18, 2024
3ba47b6
1.fix lint issue
cwangsmv Nov 18, 2024
8c86fbf
1.save work
cwangsmv Nov 21, 2024
0e3c02c
1.ui changes
cwangsmv Nov 26, 2024
85adcd2
1.add logic to handle vault key reset and input request
cwangsmv Nov 27, 2024
6a6678d
1.add encrypt/decrypt and remove secret function
cwangsmv Nov 27, 2024
ac1f9be
1.move removeAllSecrets function to modal
cwangsmv Nov 27, 2024
c00a4c0
1.basic integration with srp api
cwangsmv Dec 2, 2024
8d23a7b
1.add new utils function
cwangsmv Dec 3, 2024
096cd4a
1.add modal for secrets environment variable without vault key hint
cwangsmv Dec 3, 2024
7415db5
1.integration with sse event of reset vault key
cwangsmv Dec 4, 2024
23c6c22
1.support create vault key conflict condition
cwangsmv Dec 4, 2024
5cccad7
1.integrate with sse event change
cwangsmv Dec 5, 2024
4feb20d
1.fix some minor issues
cwangsmv Dec 5, 2024
fbf45a9
1.fix error handling
cwangsmv Dec 9, 2024
941df45
remove aws secret key related changes
cwangsmv Jan 6, 2025
bc61a6e
1.remove aws related code
cwangsmv Jan 6, 2025
73cbc5c
1.remove useless codes
cwangsmv Jan 6, 2025
d61ad11
1.add enableVaultInScripts settings to allow using vault in script
cwangsmv Jan 7, 2025
3c77c78
1.add error handle for get vault salt from server
cwangsmv Jan 7, 2025
10d138a
1.Add insomnia.vault to insomnia script
cwangsmv Jan 8, 2025
3f66590
1.fix log issue
cwangsmv Jan 8, 2025
7e6253d
1.Hide vault key panel when user is not logged in
cwangsmv Jan 8, 2025
ce370ce
1.remove keytar and use electron safestorage instead
cwangsmv Jan 9, 2025
0020b01
Do not allow set method in vault script
cwangsmv Jan 9, 2025
4a6e7af
1.rename local vault key file for cross-os support
cwangsmv Jan 10, 2025
5ccad31
unify vault secret key
cwangsmv Jan 10, 2025
dd8e7c2
1.fix issue from comment
cwangsmv Jan 13, 2025
fe13c86
1.add support for legacy environment with vault as environment key
cwangsmv Jan 13, 2025
6b5f3f2
1.fix issue
cwangsmv Jan 13, 2025
8956138
1.avoid duplicate rendering
cwangsmv Jan 13, 2025
daea7ce
1.fix naming
cwangsmv Jan 13, 2025
14c2e92
1.fix issues from comment
cwangsmv Jan 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions packages/insomnia-sdk/src/objects/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,38 @@ export class Variables {
return this.localVars.toObject();
};
}

export class Vault extends Environment {

constructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean) {
super(name, jsonObject);
return new Proxy(this, {
// throw error on get or set method call if enableVaultInScripts is false
get: (target, prop, receiver) => {
if (!enableVaultInScripts) {
throw new Error('Vault is disabled in script');
}
return Reflect.get(target, prop, receiver);
},
set: (target, prop, value, receiver) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may not support this?
Another irrelevant minor observation is, when environment.set is called in script, open the environment modal, it will not be shown in the key-value mode until switching to the json mode and back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, do not allow set method in vault script.
For the second issue found, I've created a ticket to fix the bug.

if (!enableVaultInScripts) {
throw new Error('Vault is disabled in script');
}
return Reflect.set(target, prop, value, receiver);
},
});
}

unset = () => {
throw new Error('Vault can not be unset in script');
};

clear = () => {
throw new Error('Vault can not be cleared in script');
};

set = () => {
throw new Error('Vault can not be set in script');
};

}
9 changes: 8 additions & 1 deletion packages/insomnia-sdk/src/objects/insomnia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { filterClientCertificates } from 'insomnia/src/network/certificate';

import { toPreRequestAuth } from './auth';
import { CookieObject } from './cookies';
import { Environment, Variables } from './environments';
import { Environment, Variables, Vault } from './environments';
import { Execution } from './execution';
import type { RequestContext } from './interfaces';
import { transformToSdkProxyOptions } from './proxy-configs';
Expand All @@ -28,6 +28,7 @@ export class InsomniaObject {
public info: RequestInfo;
public response?: ScriptResponse;
public execution: Execution;
public vault?: Vault;

private clientCertificates: ClientCertificate[];
private _expect = expect;
Expand Down Expand Up @@ -55,6 +56,7 @@ export class InsomniaObject {
requestInfo: RequestInfo;
execution: Execution;
response?: ScriptResponse;
vault?: Vault;
},
) {
this.globals = rawObj.globals;
Expand All @@ -66,6 +68,7 @@ export class InsomniaObject {
this.cookies = rawObj.cookies;
this.response = rawObj.response;
this.execution = rawObj.execution;
this.vault = rawObj.vault;

this.info = rawObj.requestInfo;
this.request = rawObj.request;
Expand Down Expand Up @@ -150,6 +153,9 @@ export async function initInsomniaObject(
new Environment(rawObj.iterationData.name, rawObj.iterationData.data) : new Environment('iterationData', {});
const localVariables = rawObj.transientVariables ?
new Environment(rawObj.transientVariables.name, rawObj.transientVariables.data) : new Environment('transientVariables', {});
const enableVaultInScripts = rawObj.settings?.enableVaultInScripts || false;
const vault = rawObj.vault ?
new Vault('vault', rawObj.vault, enableVaultInScripts) : new Vault('vault', {}, enableVaultInScripts);
const cookies = new CookieObject(rawObj.cookieJar);
// TODO: update follows when post-request script and iterationData are introduced
const requestInfo = new RequestInfo({
Expand Down Expand Up @@ -233,6 +239,7 @@ export async function initInsomniaObject(
environment,
baseEnvironment,
iterationData,
vault,
variables,
request,
settings: rawObj.settings,
Expand Down
1 change: 1 addition & 0 deletions packages/insomnia-sdk/src/objects/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface RequestContext {
timelinePath: string;
environment: IEnvironment;
baseEnvironment: IEnvironment;
vault?: IEnvironment;
collectionVariables?: object;
globals?: object;
iterationData?: Omit<IEnvironment, 'id'>;
Expand Down
1 change: 1 addition & 0 deletions packages/insomnia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@getinsomnia/api-client": "0.0.4",
"@getinsomnia/srp-js": "1.0.0-alpha.1",
"@sentry/electron": "^5.1.0",
"@stoplight/spectral-core": "^1.18.3",
"@stoplight/spectral-formats": "^1.6.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia/src/account/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ async function _unsetSessionData() {
symmetricKey: {} as JsonWebKey,
publicKey: {} as JsonWebKey,
encPrivateKey: {} as crypt.AESMessage,
vaultSalt: '',
vaultKey: '',
});
}

Expand Down
25 changes: 20 additions & 5 deletions packages/insomnia/src/common/export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import YAML from 'yaml';

import { isApiSpec } from '../models/api-spec';
import { isCookieJar } from '../models/cookie-jar';
import { type Environment, isEnvironment } from '../models/environment';
import { type Environment, isEnvironment, maskVaultEnvironmentData } from '../models/environment';
import { isGrpcRequest } from '../models/grpc-request';
import * as requestOperations from '../models/helpers/request-operations';
import { type BaseModel, environment } from '../models/index';
Expand Down Expand Up @@ -493,7 +493,7 @@ export const exportMockServerToFile = async (workspace: Workspace) => {
});
};

const exportGlobalEnvironment = async (workspace: Workspace, selectedFormat: 'json' | 'yaml') => {
const exportGlobalEnvironment = async (workspace: Workspace, selectedFormat: 'json' | 'yaml', shouldExportPrivateEnvironments: boolean) => {
const data: Insomnia4Data = {
_type: 'export',
__export_format: EXPORT_FORMAT,
Expand All @@ -503,11 +503,16 @@ const exportGlobalEnvironment = async (workspace: Workspace, selectedFormat: 'js
};

const baseEnvironment = await models.environment.getOrCreateForParentId(workspace._id);
const subEnvironments = await models.environment.findByParentId(baseEnvironment._id);
const subEnvironments = (await models.environment.findByParentId(baseEnvironment._id))
.filter(subEnv => shouldExportPrivateEnvironments || !subEnv.isPrivate);

data.resources.push({ ...workspace, _type: 'workspace' });
data.resources.push({ ...baseEnvironment, _type: 'environment' });
subEnvironments.map(environment => data.resources.push({ ...environment, _type: 'environment' }));
subEnvironments.map(environment => {
// mask vault environment varibale if necessary
const maskedEnvironment = maskVaultEnvironmentData(environment);
data.resources.push({ ...maskedEnvironment, _type: 'environment' });
});

if (selectedFormat === 'yaml') {
return YAML.stringify(data);
Expand All @@ -529,15 +534,25 @@ export const exportGlobalEnvironmentToFile = async (workspace: Workspace) => {
invariant(selectedFormat, 'expected selected format to be defined');
invariant(selectedFormat === 'json' || selectedFormat === 'yaml', 'unexpected selected format');
window.localStorage.setItem('insomnia.lastExportFormat', selectedFormat);
// Modal to confirm whether to export private environment or not if necessary
const baseEnvironment = await models.environment.getOrCreateForParentId(workspace._id);
const subEnvironments = await models.environment.findByParentId(baseEnvironment._id);
const showPrivateEnvironmentPrompt = subEnvironments.some(subEnv => subEnv.isPrivate);
let shouldExportPrivateEnvironments = false;
if (showPrivateEnvironmentPrompt) {
shouldExportPrivateEnvironments = await showExportPrivateEnvironmentsModal();
}

const fileName = await showSaveExportedFileDialog({
exportedFileNamePrefix: workspace.name,
selectedFormat,
});
if (!fileName) {
return;
}

try {
const stringifiedExport = await exportGlobalEnvironment(workspace, selectedFormat);
const stringifiedExport = await exportGlobalEnvironment(workspace, selectedFormat, shouldExportPrivateEnvironments);
writeExportedFileToFileSystem(fileName, stringifiedExport, err => err && console.warn('Export failed', err));
window.main.trackSegmentEvent({ event: SegmentEvent.dataExport, properties: { type: selectedFormat, scope: 'environment' } });
} catch (err) {
Expand Down
8 changes: 4 additions & 4 deletions packages/insomnia/src/common/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { database as db } from './database';

export const KEEP_ON_ERROR = 'keep';
export const THROW_ON_ERROR = 'throw';
export type RenderPurpose = 'send' | 'general' | 'no-render';
export type RenderPurpose = 'send' | 'general' | 'preview' | 'script' | 'no-render';
export const RENDER_PURPOSE_SEND: RenderPurpose = 'send';
export const RENDER_PURPOSE_GENERAL: RenderPurpose = 'general';
export const RENDER_PURPOSE_NO_RENDER: RenderPurpose = 'no-render';
Expand Down Expand Up @@ -212,7 +212,7 @@ export async function buildRenderContext(
}

// Render the context with itself to fill in the rest.
const finalRenderContext = renderContext;
const finalRenderContext = await templatingUtils.maskOrDecryptContextIfNecessary(renderContext as Record<string, any> & BaseRenderContext);

const keys = _getOrderedEnvironmentKeys(finalRenderContext);

Expand Down Expand Up @@ -372,7 +372,7 @@ interface BaseRenderContextOptions {
ignoreUndefinedEnvVariable?: boolean;
}

interface RenderContextOptions extends BaseRenderContextOptions, Partial<RenderRequest<Request | GrpcRequest | WebSocketRequest>> {
export interface RenderContextOptions extends BaseRenderContextOptions, Partial<RenderRequest<Request | GrpcRequest | WebSocketRequest>> {
ancestors?: RenderContextAncestor[];
}
export async function getRenderContext(
Expand Down Expand Up @@ -533,7 +533,7 @@ export async function getRenderContext(
interface BaseRenderContext {
getMeta: () => {};
getKeysContext: () => {};
getPurpose: () => string | undefined;
getPurpose: () => RenderPurpose | undefined;
getExtraInfo: (key: string) => string | null;
getEnvironmentId: () => string | undefined;
getGlobalEnvironmentId: () => string | undefined;
Expand Down
4 changes: 4 additions & 0 deletions packages/insomnia/src/common/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,8 @@ export interface Settings {
useBulkParametersEditor: boolean;
validateAuthSSL: boolean;
validateSSL: boolean;
// vault related settings
saveVaultKeyLocally: boolean;
vaultSecretCacheDuration: number;
cwangsmv marked this conversation as resolved.
Show resolved Hide resolved
enableVaultInScripts: boolean;
}
2 changes: 2 additions & 0 deletions packages/insomnia/src/main.development.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { backupIfNewerVersionAvailable } from './main/backup';
import { ipcMainOn, ipcMainOnce, registerElectronHandlers } from './main/ipc/electron';
import { registergRPCHandlers } from './main/ipc/grpc';
import { registerMainHandlers } from './main/ipc/main';
import { registerSecretStorageHandlers } from './main/ipc/secretStorage';
import { registerCurlHandlers } from './main/network/curl';
import { registerWebSocketHandlers } from './main/network/websocket';
import { watchProxySettings } from './main/proxy';
Expand Down Expand Up @@ -64,6 +65,7 @@ app.on('ready', async () => {
registergRPCHandlers();
registerWebSocketHandlers();
registerCurlHandlers();
registerSecretStorageHandlers();

/**
* There's no option that prevents Electron from fetching spellcheck dictionaries from Chromium's CDN and passing a non-resolving URL is the only known way to prevent it from fetching.
Expand Down
14 changes: 11 additions & 3 deletions packages/insomnia/src/main/ipc/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ export type HandleChannels =
| 'webSocket.open'
| 'webSocket.readyState'
| 'writeFile'
| 'extractJsonFileFromPostmanDataDumpArchive';
| 'extractJsonFileFromPostmanDataDumpArchive'
| 'secretStorage.setSecret'
| 'secretStorage.getSecret'
| 'secretStorage.deleteSecret'
| 'secretStorage.encryptString'
| 'secretStorage.decryptString';

export const ipcMainHandle = (
channel: HandleChannels,
Expand Down Expand Up @@ -162,6 +167,7 @@ export function registerElectronHandlers() {
.sort((a, b) => fnOrString(a.templateTag.displayName).localeCompare(fnOrString(b.templateTag.displayName)))
.map(l => {
const actions = l.templateTag.args?.[0];
const needsEnterprisePlan = l.templateTag.needsEnterprisePlan || false;
const additionalArgs = l.templateTag.args?.slice(1);
const hasSubmenu = actions?.options?.length;
return {
Expand All @@ -170,16 +176,18 @@ export function registerElectronHandlers() {
{
click: () => {
const tag = `{% ${l.templateTag.name} ${l.templateTag.args?.map(getTemplateValue).join(', ')} %}`;
event.sender.send('context-menu-command', { key, tag });
const displayName = l.templateTag.displayName;
event.sender.send('context-menu-command', { key, tag, needsEnterprisePlan, displayName });
},
} :
{
submenu: actions?.options?.map(action => ({
label: fnOrString(action.displayName),
click: () => {
const additionalTagFields = additionalArgs.length ? ', ' + additionalArgs.map(getTemplateValue).join(', ') : '';
const displayName = action.displayName;
const tag = `{% ${l.templateTag.name} '${action.value}'${additionalTagFields} %}`;
event.sender.send('context-menu-command', { key, tag });
event.sender.send('context-menu-command', { key, tag, needsEnterprisePlan, displayName });
},
})),
}),
Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia/src/main/ipc/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { WebSocketBridgeAPI } from '../network/websocket';
import { ipcMainHandle, ipcMainOn, ipcMainOnce, type RendererOnChannels } from './electron';
import extractPostmanDataDumpHandler from './extractPostmanDataDump';
import type { gRPCBridgeAPI } from './grpc';
import type { secretStorageBridgeAPI } from './secretStorage';

export interface RendererToMainBridgeAPI {
loginStateChange: () => void;
Expand All @@ -37,6 +38,7 @@ export interface RendererToMainBridgeAPI {
webSocket: WebSocketBridgeAPI;
grpc: gRPCBridgeAPI;
curl: CurlBridgeAPI;
secretStorage: secretStorageBridgeAPI;
trackSegmentEvent: (options: { event: string; properties?: Record<string, unknown> }) => void;
trackPageView: (options: { name: string }) => void;
showContextMenu: (options: { key: string; nunjucksTag?: { template: string; range: MarkerRange } }) => void;
Expand Down
Loading
Loading