[WIP] Encryption for secure values (#183)

This commit is contained in:
Gregory Schier
2025-04-15 07:18:26 -07:00
committed by GitHub
parent e114a85c39
commit 2e55a1bd6d
208 changed files with 4063 additions and 28698 deletions

View File

@@ -6,9 +6,17 @@ import type { ReactNode } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { useIsEncryptionEnabled } from '../hooks/useIsEncryptionEnabled';
import { useKeyValue } from '../hooks/useKeyValue';
import { useRandomKey } from '../hooks/useRandomKey';
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
import { analyzeTemplate, convertTemplateToSecure } from '../lib/encryption';
import { showPrompt } from '../lib/prompt';
import {
setupOrConfigureEncryption,
withEncryptionEnabled,
} from '../lib/setupOrConfigureEncryption';
import { BadgeButton } from './core/BadgeButton';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { ContextMenu } from './core/Dropdown';
@@ -17,7 +25,8 @@ import { Heading } from './core/Heading';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import type { PairEditorProps } from './core/PairEditor';
import type { PairWithId } from './core/PairEditor';
import { ensurePairId } from './core/PairEditor';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { Separator } from './core/Separator';
import { SplitLayout } from './core/SplitLayout';
@@ -120,16 +129,19 @@ const EnvironmentEditor = function ({
environment: Environment;
className?: string;
}) {
const activeWorkspaceId = activeEnvironment.workspaceId;
const isEncryptionEnabled = useIsEncryptionEnabled();
const valueVisibility = useKeyValue<boolean>({
namespace: 'global',
key: 'environmentValueVisibility',
fallback: true,
key: ['environmentValueVisibility', activeWorkspaceId],
fallback: false,
});
const { allEnvironments } = useEnvironmentsBreakdown();
const handleChange = useCallback<PairEditorProps['onChange']>(
(variables) => patchModel(activeEnvironment, { variables }),
const handleChange = useCallback(
(variables: PairWithId[]) => patchModel(activeEnvironment, { variables }),
[activeEnvironment],
);
const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey();
// Gather a list of env names from other environments, to help the user get them aligned
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
@@ -162,19 +174,50 @@ const EnvironmentEditor = function ({
return name.match(/^[a-z_][a-z0-9_-]*$/i) != null;
}, []);
const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password';
const promptToEncrypt = useMemo(() => {
if (!isEncryptionEnabled) {
return false;
} else {
return !activeEnvironment.variables.every(
(v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure',
);
}
}, [activeEnvironment.variables, isEncryptionEnabled]);
const encryptEnvironment = (environment: Environment) => {
withEncryptionEnabled(async () => {
const encryptedVariables: PairWithId[] = [];
for (const variable of environment.variables) {
const value = variable.value ? await convertTemplateToSecure(variable.value) : '';
encryptedVariables.push(ensurePairId({ ...variable, value }));
}
await handleChange(encryptedVariables);
regenerateForceUpdateKey();
});
};
return (
<VStack space={4} className={classNames(className, 'pl-4')}>
<HStack space={2} className="justify-between">
<Heading className="w-full flex items-center gap-1">
<div>{activeEnvironment?.name}</div>
<IconButton
size="sm"
icon={valueVisibility.value ? 'eye' : 'eye_closed'}
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
onClick={() => {
return valueVisibility.set((v) => !v);
}}
/>
{promptToEncrypt ? (
<BadgeButton color="notice" onClick={() => encryptEnvironment(activeEnvironment)}>
Encrypt All Variables
</BadgeButton>
) : isEncryptionEnabled ? (
<BadgeButton color="secondary" onClick={setupOrConfigureEncryption}>
Encryption Settings
</BadgeButton>
) : (
<IconButton
size="sm"
icon={valueVisibility.value ? 'eye' : 'eye_closed'}
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
onClick={() => valueVisibility.set((v) => !v)}
/>
)}
</Heading>
</HStack>
<div className="h-full pr-2 pb-2">
@@ -184,10 +227,10 @@ const EnvironmentEditor = function ({
nameAutocomplete={nameAutocomplete}
namePlaceholder="VAR_NAME"
nameValidate={validateName}
valueType={valueVisibility.value ? 'text' : 'password'}
valueType={valueType}
valueAutocompleteVariables
valueAutocompleteFunctions
forceUpdateKey={activeEnvironment.id}
forceUpdateKey={`${activeEnvironment.id}::${forceUpdateKey}`}
pairs={activeEnvironment.variables}
onChange={handleChange}
stateKey={`environment.${activeEnvironment.id}`}