mirror of
https://github.com/Kong/insomnia.git
synced 2026-08-04 11:52:33 -04:00
first pass
This commit is contained in:
@@ -5,7 +5,7 @@ import { models, services } from '~/insomnia-data';
|
||||
|
||||
import { AI_PLUGIN_NAME, LLM_BACKENDS } from '../common/constants';
|
||||
import { database } from '../common/database';
|
||||
import * as crypt from './crypt';
|
||||
import type { AESMessage } from './crypt';
|
||||
|
||||
export interface SessionData {
|
||||
accountId: string;
|
||||
@@ -15,7 +15,7 @@ export interface SessionData {
|
||||
lastName: string;
|
||||
symmetricKey: JsonWebKey;
|
||||
publicKey: JsonWebKey;
|
||||
encPrivateKey: crypt.AESMessage;
|
||||
encPrivateKey: AESMessage;
|
||||
}
|
||||
|
||||
/** Creates a session from a sessionId and derived symmetric key. */
|
||||
@@ -28,7 +28,8 @@ export async function absorbKey(sessionId: string, key: string) {
|
||||
]);
|
||||
const { public_key: publicKey, enc_private_key: encPrivateKey, enc_symmetric_key: encSymmetricKey } = keys;
|
||||
const { email, id: accountId, first_name: firstName, last_name: lastName } = profile;
|
||||
const symmetricKeyStr = crypt.decryptAES(key, JSON.parse(encSymmetricKey));
|
||||
const { decryptAES } = await import('./crypt');
|
||||
const symmetricKeyStr = decryptAES(key, JSON.parse(encSymmetricKey));
|
||||
|
||||
// Store the information for later
|
||||
await setSessionData(
|
||||
@@ -58,7 +59,8 @@ export async function getPrivateKey() {
|
||||
throw new Error("Can't get private key: session is missing keys.");
|
||||
}
|
||||
|
||||
const privateKeyStr = crypt.decryptAES(symmetricKey, encPrivateKey);
|
||||
const { decryptAES } = await import('./crypt');
|
||||
const privateKeyStr = decryptAES(symmetricKey, encPrivateKey);
|
||||
return JSON.parse(privateKeyStr) as JsonWebKey;
|
||||
}
|
||||
|
||||
@@ -105,7 +107,7 @@ export async function setSessionData(
|
||||
email: string,
|
||||
symmetricKey: JsonWebKey,
|
||||
publicKey: JsonWebKey,
|
||||
encPrivateKey: crypt.AESMessage,
|
||||
encPrivateKey: AESMessage,
|
||||
) {
|
||||
const sessionData: SessionData = {
|
||||
id,
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import clone from 'clone';
|
||||
import type * as Har from 'har-format';
|
||||
import { Cookie as ToughCookie } from 'tough-cookie';
|
||||
|
||||
import type { BaseModel, Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
import { applyRequestHooks } from '~/network/network-adapter';
|
||||
|
||||
import * as plugins from '../plugins';
|
||||
import * as pluginApp from '../plugins/context/app';
|
||||
import * as pluginRequest from '../plugins/context/request';
|
||||
import * as pluginStore from '../plugins/context/store';
|
||||
import { RenderError } from '../templating/render-error';
|
||||
import type { RenderedRequest } from '../templating/types';
|
||||
import { parseGraphQLReqeustBody } from '../utils/graph-ql';
|
||||
@@ -264,25 +260,7 @@ async function _applyRequestPluginHooks(
|
||||
renderedRequest: RenderedRequest,
|
||||
renderedContext: Record<string, any>,
|
||||
): Promise<RenderedRequest> {
|
||||
let newRenderedRequest = renderedRequest;
|
||||
|
||||
for (const { plugin, hook } of await plugins.getRequestHooks()) {
|
||||
newRenderedRequest = clone(newRenderedRequest);
|
||||
const context = {
|
||||
...(pluginApp.init() as Record<string, any>),
|
||||
...(pluginRequest.init(newRenderedRequest, renderedContext) as Record<string, any>),
|
||||
...(pluginStore.init(plugin) as Record<string, any>),
|
||||
};
|
||||
|
||||
try {
|
||||
await hook(context);
|
||||
} catch (err) {
|
||||
err.plugin = plugin;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return newRenderedRequest;
|
||||
return applyRequestHooks(renderedRequest, renderedContext);
|
||||
}
|
||||
|
||||
export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequest, addContentLength = false) {
|
||||
|
||||
@@ -8,12 +8,12 @@ import { HydratedRouter } from 'react-router/dom';
|
||||
|
||||
import { insomniaFetch } from '~/common/insomnia-fetch';
|
||||
import { initDatabase, initServices, services } from '~/insomnia-data';
|
||||
import { plugins } from '~/plugins/renderer-bridge';
|
||||
import { database as clientDatabase } from '~/ui/database.client';
|
||||
import { clearOAuthWindowSessionId } from '~/ui/spawn-oauth-window';
|
||||
|
||||
import { migrateFromLocalStorage, type SessionData, setSessionData, setVaultSessionData } from './account/session';
|
||||
import { getInsomniaSession, getInsomniaVaultKey, getInsomniaVaultSalt, getSkipOnboarding } from './common/constants';
|
||||
import { init as initPlugins } from './plugins';
|
||||
import { applyColorScheme } from './plugins/misc';
|
||||
import { registerSyncMergeConflictListener } from './sync/vcs/insomnia-sync';
|
||||
import { HtmlElementWrapper } from './ui/components/html-element-wrapper';
|
||||
@@ -40,7 +40,7 @@ delete window._dataServices;
|
||||
|
||||
configureFetch(options => insomniaFetch({ ...options, onDeepLink: (uri: string) => window.main.openDeepLink(uri) }));
|
||||
|
||||
await initPlugins();
|
||||
await plugins.reloadPlugins();
|
||||
|
||||
await migrateFromLocalStorage();
|
||||
registerSyncMergeConflictListener();
|
||||
|
||||
@@ -201,7 +201,7 @@ export function createWindow(): ElectronBrowserWindow {
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'entry.preload.min.js'),
|
||||
zoomFactor: getZoomFactor(),
|
||||
nodeIntegration: true,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false, // must remain false to ensure the nunjucks web worker sandbox does not have access to Node.js APIs
|
||||
webviewTag: true,
|
||||
// TODO: enable context isolation
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import Color from 'color';
|
||||
|
||||
import type { ThemeSettings } from '~/insomnia-data';
|
||||
import { getAppDefaultTheme } from '~/insomnia-data/common';
|
||||
import { getAppDefaultTheme } from '../common/constants';
|
||||
import type { SerializableTheme } from './bridge-types';
|
||||
import { plugins } from './renderer-bridge';
|
||||
|
||||
import type { Theme } from './index';
|
||||
import { type ColorScheme, getThemes } from './index';
|
||||
export type ColorScheme = 'default' | 'light' | 'dark';
|
||||
|
||||
export type HexColor = `#${string}`;
|
||||
export type RGBColor = `rgb(${string})`;
|
||||
@@ -331,7 +332,7 @@ export async function setTheme(themeName: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
const themes: Theme[] = await getThemes();
|
||||
const themes: SerializableTheme[] = await plugins.getThemes();
|
||||
let selectedTheme = themes.find(t => t.theme.name === themeName);
|
||||
|
||||
if (!selectedTheme) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { PluginBridgeMetrics, PluginsBridgeAPI } from './bridge-types';
|
||||
import { invokePluginMethod } from './invoke-method';
|
||||
|
||||
// Phase 1a rollback switch: set INSOMNIA_ENABLE_PLUGIN_BRIDGE=false to fall
|
||||
// back to running plugins directly in the renderer (legacy behaviour).
|
||||
@@ -7,15 +6,17 @@ import { invokePluginMethod } from './invoke-method';
|
||||
// plugin-system deps it pulls in don't inflate the preload.
|
||||
const bridgeEnabled = process.env.INSOMNIA_ENABLE_PLUGIN_BRIDGE !== 'false';
|
||||
|
||||
function call<M extends keyof Omit<PluginsBridgeAPI, 'getBridgeMetrics'>>(
|
||||
async function call<M extends keyof Omit<PluginsBridgeAPI, 'getBridgeMetrics'>>(
|
||||
method: M,
|
||||
args?: Parameters<PluginsBridgeAPI[M]>[0],
|
||||
): ReturnType<PluginsBridgeAPI[M]> {
|
||||
): Promise<Awaited<ReturnType<PluginsBridgeAPI[M]>>> {
|
||||
if (bridgeEnabled) {
|
||||
const fn = (window.main.plugins[method] as (...a: any[]) => any);
|
||||
return fn(args) as ReturnType<PluginsBridgeAPI[M]>;
|
||||
return fn(args) as Promise<Awaited<ReturnType<PluginsBridgeAPI[M]>>>;
|
||||
}
|
||||
return invokePluginMethod(method as any, args) as ReturnType<PluginsBridgeAPI[M]>;
|
||||
|
||||
const { invokePluginMethod } = await import('./invoke-method');
|
||||
return invokePluginMethod(method as any, args) as Promise<Awaited<ReturnType<PluginsBridgeAPI[M]>>>;
|
||||
}
|
||||
|
||||
const emptyBridgeMetrics: PluginBridgeMetrics = {
|
||||
|
||||
@@ -568,7 +568,7 @@ const Root = () => {
|
||||
// gracefully handle open org in app from browser
|
||||
const userSession = await services.userSession.get();
|
||||
if (!userSession.id || userSession.id === '') {
|
||||
const url = new URL(getLoginUrl());
|
||||
const url = new URL(await getLoginUrl());
|
||||
window.main.openInBrowser(url.toString());
|
||||
window.localStorage.setItem('specificOrgRedirectAfterAuthorize', params.organizationId);
|
||||
return navigate(href('/auth/authorize'));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getVault } from 'insomnia-api';
|
||||
import { Fragment } from 'react';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { Button, Heading } from 'react-aria-components';
|
||||
import { href, redirect, useFetchers, useNavigate } from 'react-router';
|
||||
|
||||
@@ -75,7 +75,12 @@ export const useAuthorizeActionFetcher = createFetcherSubmitHook(
|
||||
);
|
||||
|
||||
const Component = () => {
|
||||
const url = getLoginUrl();
|
||||
const [url, setUrl] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
void getLoginUrl().then(setUrl);
|
||||
}, []);
|
||||
|
||||
const copyUrl = () => {
|
||||
window.clipboard.writeText(url);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ const GoogleIcon = (props: React.ReactSVGElement['props']) => {
|
||||
export async function clientAction({ request }: Route.ClientActionArgs) {
|
||||
const data = await request.formData();
|
||||
const provider = data.get('provider');
|
||||
const url = new URL(getLoginUrl());
|
||||
const url = new URL(await getLoginUrl());
|
||||
|
||||
if (typeof provider === 'string' && provider) {
|
||||
url.searchParams.set('provider', provider);
|
||||
|
||||
@@ -1,33 +1,52 @@
|
||||
import * as session from '../account/session';
|
||||
import { getAppWebsiteBaseURL, getInsomniaPublicKey, getInsomniaSecretKey } from '../common/constants';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import { keyPair, open } from '../utils/sealedbox';
|
||||
|
||||
interface AuthBox {
|
||||
token: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
const sessionKeyPair = keyPair();
|
||||
encodeBase64(sessionKeyPair.publicKey).then(res => {
|
||||
try {
|
||||
window.localStorage.setItem('insomnia.publicKey', getInsomniaPublicKey() || res);
|
||||
} catch {
|
||||
console.error('Failed to store public key in localStorage.');
|
||||
}
|
||||
});
|
||||
encodeBase64(sessionKeyPair.secretKey).then(res => {
|
||||
try {
|
||||
window.localStorage.setItem('insomnia.secretKey', getInsomniaSecretKey() || res);
|
||||
} catch {
|
||||
console.error('Failed to store secret key in localStorage.');
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Keypair used for the login handshake.
|
||||
* This keypair can be re-used for the entire session.
|
||||
*/
|
||||
|
||||
interface SessionKeyPair {
|
||||
publicKey: Uint8Array;
|
||||
secretKey: Uint8Array;
|
||||
}
|
||||
|
||||
let sessionKeyPairPromise: Promise<SessionKeyPair> | null = null;
|
||||
|
||||
async function getSessionKeyPair() {
|
||||
if (!sessionKeyPairPromise) {
|
||||
sessionKeyPairPromise = (async () => {
|
||||
const { keyPair } = await import('../utils/sealedbox');
|
||||
const sessionKeyPair = keyPair();
|
||||
|
||||
encodeBase64(sessionKeyPair.publicKey).then(res => {
|
||||
try {
|
||||
window.localStorage.setItem('insomnia.publicKey', getInsomniaPublicKey() || res);
|
||||
} catch {
|
||||
console.error('Failed to store public key in localStorage.');
|
||||
}
|
||||
});
|
||||
encodeBase64(sessionKeyPair.secretKey).then(res => {
|
||||
try {
|
||||
window.localStorage.setItem('insomnia.secretKey', getInsomniaSecretKey() || res);
|
||||
} catch {
|
||||
console.error('Failed to store secret key in localStorage.');
|
||||
}
|
||||
});
|
||||
|
||||
return sessionKeyPair;
|
||||
})();
|
||||
}
|
||||
|
||||
return sessionKeyPairPromise;
|
||||
}
|
||||
|
||||
export async function decodeBase64(base64: string): Promise<Uint8Array> {
|
||||
try {
|
||||
let uri = 'data:application/octet-binary;base64,';
|
||||
@@ -65,9 +84,11 @@ export async function encodeBase64(data: Uint8Array): Promise<string> {
|
||||
|
||||
export async function submitAuthCode(code: string) {
|
||||
try {
|
||||
await getSessionKeyPair();
|
||||
const rawBox = await decodeBase64(code.trim());
|
||||
const publicKey = await decodeBase64(window.localStorage.getItem('insomnia.publicKey') || '');
|
||||
const secretKey = await decodeBase64(window.localStorage.getItem('insomnia.secretKey') || '');
|
||||
const { open } = await import('../utils/sealedbox');
|
||||
const boxData = open(rawBox, publicKey, secretKey);
|
||||
invariant(boxData, 'Invalid authentication code.');
|
||||
|
||||
@@ -80,7 +101,8 @@ export async function submitAuthCode(code: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getLoginUrl() {
|
||||
export async function getLoginUrl() {
|
||||
await getSessionKeyPair();
|
||||
const publicKey = window.localStorage.getItem('insomnia.publicKey');
|
||||
if (!publicKey) {
|
||||
console.log('[auth] No public key found');
|
||||
|
||||
@@ -68,9 +68,9 @@ export const OrganizationSelect = ({
|
||||
<div className="my-1 border-t border-(--hl-sm)" />
|
||||
<Button
|
||||
className="flex h-(--line-height-xs) w-full items-center gap-2 bg-transparent px-(--padding-md) whitespace-nowrap text-(--color-font) transition-colors hover:bg-(--hl-sm) focus:bg-(--hl-xs) focus:outline-hidden"
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
setIsOpen(false);
|
||||
window.main.openInBrowser(getLoginUrl());
|
||||
window.main.openInBrowser(await getLoginUrl());
|
||||
}}
|
||||
>
|
||||
<Icon icon="sign-in-alt" />
|
||||
@@ -78,11 +78,11 @@ export const OrganizationSelect = ({
|
||||
</Button>
|
||||
<Button
|
||||
className="flex h-(--line-height-xs) w-full items-center gap-2 bg-transparent px-(--padding-md) whitespace-nowrap text-(--color-font) transition-colors hover:bg-(--hl-sm) focus:bg-(--hl-xs) focus:outline-hidden"
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
setIsOpen(false);
|
||||
// If user is in the scratchpad workspace redirect them to the login page
|
||||
if (isScratchpadWorkspace) {
|
||||
return window.main.openInBrowser(getLoginUrl());
|
||||
return window.main.openInBrowser(await getLoginUrl());
|
||||
}
|
||||
|
||||
if (!currentPlan) {
|
||||
|
||||
Reference in New Issue
Block a user