Skip to content

Commit

Permalink
1.initial check-in for separate vault key ui changes
Browse files Browse the repository at this point in the history
  • Loading branch information
cwangsmv committed Jan 14, 2025
1 parent 65fd66b commit 7354277
Show file tree
Hide file tree
Showing 23 changed files with 921 additions and 37 deletions.
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.

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
3 changes: 3 additions & 0 deletions packages/insomnia/src/common/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,7 @@ export interface Settings {
useBulkParametersEditor: boolean;
validateAuthSSL: boolean;
validateSSL: boolean;
// vault related settings
saveVaultKeyLocally: boolean;
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/secret-storage';
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
7 changes: 6 additions & 1 deletion 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
3 changes: 2 additions & 1 deletion packages/insomnia/src/main/ipc/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +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 './secret-storage';
export interface RendererToMainBridgeAPI {
loginStateChange: () => void;
openInBrowser: (url: string) => void;
Expand All @@ -37,6 +37,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
77 changes: 77 additions & 0 deletions packages/insomnia/src/main/ipc/secret-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { safeStorage } from 'electron';

import LocalStorage from '../local-storage';
import { initLocalStorage } from '../window-utils';
import { ipcMainHandle } from './electron';

export interface secretStorageBridgeAPI {
setSecret: typeof setSecret;
getSecret: typeof getSecret;
deleteSecret: typeof deleteSecret;
encryptString: (raw: string) => Promise<string>;
decryptString: (cipherText: string) => Promise<string>;
}

export function registerSecretStorageHandlers() {
ipcMainHandle('secretStorage.setSecret', (_, key, secret) => setSecret(key, secret));
ipcMainHandle('secretStorage.getSecret', (_, key) => getSecret(key));
ipcMainHandle('secretStorage.deleteSecret', (_, key) => deleteSecret(key));
ipcMainHandle('secretStorage.encryptString', (_, raw) => encryptString(raw));
ipcMainHandle('secretStorage.decryptString', (_, raw) => decryptString(raw));
}

let localStorage: LocalStorage | null = null;

const getLocalStorage = () => {
if (!localStorage) {
localStorage = initLocalStorage();
}
return localStorage;
};

const setSecret = async (key: string, secret: string) => {
try {
const secretStorage = getLocalStorage();
const encrypted = encryptString(secret);
secretStorage.setItem(key, encrypted);
} catch (error) {
console.error(`Can not save secret ${error.toString()}`);
return Promise.reject(error);
}
};

const getSecret = async (key: string) => {
try {
const secretStorage = getLocalStorage();
const encrypted = secretStorage.getItem(key, '');
return encrypted === '' ? null : decryptString(encrypted);
} catch (error) {
console.error(`Can not get secret ${error.toString()}`);
return Promise.reject(null);
}
};

const deleteSecret = async (key: string) => {
try {
const secretStorage = getLocalStorage();
secretStorage.deleteItem(key);
} catch (error) {
console.error(`Can not delele secret ${error.toString()}`);
return Promise.reject(error);
}
};

const encryptString = (raw: string) => {
if (safeStorage.isEncryptionAvailable()) {
return safeStorage.encryptString(raw).toString('hex');
}
return raw;
};

const decryptString = (cipherText: string) => {
const buffer = Buffer.from(cipherText, 'hex');
if (safeStorage.isEncryptionAvailable()) {
return safeStorage.decryptString(buffer);
}
return cipherText;
};
15 changes: 15 additions & 0 deletions packages/insomnia/src/main/local-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ class LocalStorage {
}
}

deleteItem(key: string) {
clearTimeout(this._timeouts[key]);
delete this._buffer[key];

const path = this._getKeyPath(key);

try {
fs.unlinkSync(path);
} catch (error) {
if (error.code !== 'ENOENT') {
console.error(`[localstorage] Failed to delete item from LocalStorage: ${error}`);
}
}
}

_flush() {
const keys = Object.keys(this._buffer);

Expand Down
7 changes: 4 additions & 3 deletions packages/insomnia/src/main/window-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface Bounds {
}

export function init() {
initLocalStorage();
localStorage = initLocalStorage();
}
const stopAndWaitForHiddenBrowserWindow = async (runningHiddenBrowserWindow: BrowserWindow) => {
return await new Promise<void>(resolve => {
Expand Down Expand Up @@ -800,9 +800,10 @@ export const setZoom = (transformer: (current: number) => number) => () => {
localStorage?.setItem('zoomFactor', actual);
};

function initLocalStorage() {
export function initLocalStorage() {
const localStoragePath = path.join(process.env['INSOMNIA_DATA_PATH'] || app.getPath('userData'), 'localStorage');
localStorage = new LocalStorage(localStoragePath);
const localStorage = new LocalStorage(localStoragePath);
return localStorage;
}

export function createWindowsAndReturnMain({ firstLaunch }: { firstLaunch?: boolean } = {}) {
Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia/src/models/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export function init(): BaseSettings {
useBulkParametersEditor: false,
validateAuthSSL: true,
validateSSL: true,
saveVaultKeyLocally: true,
enableVaultInScripts: false,
};
}

Expand Down
4 changes: 4 additions & 0 deletions packages/insomnia/src/models/user-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface BaseUserSession {
symmetricKey: JsonWebKey;
publicKey: JsonWebKey;
encPrivateKey: AESMessage;
vaultSalt?: string;
vaultKey?: string;
};

export interface HashedUserSession {
Expand All @@ -34,6 +36,8 @@ export function init(): BaseUserSession {
symmetricKey: {} as JsonWebKey,
publicKey: {} as JsonWebKey,
encPrivateKey: {} as AESMessage,
vaultKey: '',
vaultSalt: '',
};
}

Expand Down
11 changes: 11 additions & 0 deletions packages/insomnia/src/preload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { contextBridge, ipcRenderer, webUtils as _webUtils } from 'electron';

import type { gRPCBridgeAPI } from './main/ipc/grpc';
import type { secretStorageBridgeAPI } from './main/ipc/secret-storage';
import type { CurlBridgeAPI } from './main/network/curl';
import type { WebSocketBridgeAPI } from './main/network/websocket';
import { invariant } from './utils/invariant';
Expand Down Expand Up @@ -40,6 +41,15 @@ const grpc: gRPCBridgeAPI = {
loadMethods: options => ipcRenderer.invoke('grpc.loadMethods', options),
loadMethodsFromReflection: options => ipcRenderer.invoke('grpc.loadMethodsFromReflection', options),
};

const secretStorage: secretStorageBridgeAPI = {
setSecret: (key, secret) => ipcRenderer.invoke('secretStorage.setSecret', key, secret),
getSecret: key => ipcRenderer.invoke('secretStorage.getSecret', key),
deleteSecret: key => ipcRenderer.invoke('secretStorage.deleteSecret', key),
encryptString: raw => ipcRenderer.invoke('secretStorage.encryptString', raw),
decryptString: cipherText => ipcRenderer.invoke('secretStorage.decryptString', cipherText),
};

const main: Window['main'] = {
startExecution: options => ipcRenderer.send('startExecution', options),
addExecutionStep: options => ipcRenderer.send('addExecutionStep', options),
Expand Down Expand Up @@ -67,6 +77,7 @@ const main: Window['main'] = {
webSocket,
grpc,
curl,
secretStorage,
trackSegmentEvent: options => ipcRenderer.send('trackSegmentEvent', options),
trackPageView: options => ipcRenderer.send('trackPageView', options),
showContextMenu: options => ipcRenderer.send('show-context-menu', options),
Expand Down
31 changes: 22 additions & 9 deletions packages/insomnia/src/ui/components/base/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import React, { type FC, useCallback, useState } from 'react';
import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
import { useInterval } from 'react-use';

import { Button, type ButtonProps } from '../themed-button';

export interface CopyBtnHanlde {
copy: () => void;
}
interface Props extends ButtonProps {
confirmMessage?: string;
content: string;
title?: string;
}

export const CopyButton: FC<Props> = ({
children,
confirmMessage,
content,
title,
...buttonProps
}) => {
export const CopyButton = forwardRef<CopyBtnHanlde, Props>((props, ref) => {
const {
children,
confirmMessage,
content,
title,
...buttonProps
} = props;
const [showConfirmation, setshowConfirmation] = useState(false);
const onClick = useCallback(async (event: React.MouseEvent) => {
event.preventDefault();
Expand All @@ -31,6 +35,15 @@ export const CopyButton: FC<Props> = ({
setshowConfirmation(false);
}, 2000);

useImperativeHandle(ref, () => ({
copy: () => {
if (content) {
window.clipboard.writeText(content);
setshowConfirmation(true);
}
},
}), [content]);

const confirm = typeof confirmMessage === 'string' ? confirmMessage : 'Copied';
return (
<Button
Expand All @@ -47,4 +60,4 @@ export const CopyButton: FC<Props> = ({
)}
</Button>
);
};
});
Loading

0 comments on commit 7354277

Please sign in to comment.