import { enableEncryption, revealWorkspaceKey, setWorkspaceKey } from '@yaakapp-internal/crypto'; import type { WorkspaceMeta } from '@yaakapp-internal/models'; import classNames from 'classnames'; import { useAtomValue } from 'jotai'; import { useEffect, useState } from 'react'; import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace'; import { createFastMutation } from '../hooks/useFastMutation'; import { useStateWithDeps } from '../hooks/useStateWithDeps'; import { CopyIconButton } from './CopyIconButton'; import { Banner } from './core/Banner'; import type { ButtonProps } from './core/Button'; import { Button } from './core/Button'; import { IconButton } from './core/IconButton'; import { IconTooltip } from './core/IconTooltip'; import { Label } from './core/Label'; import { PlainInput } from './core/PlainInput'; import { HStack, VStack } from './core/Stacks'; import { EncryptionHelp } from './EncryptionHelp'; interface Props { size?: ButtonProps['size']; expanded?: boolean; onDone?: () => void; onEnabledEncryption?: () => void; } export function WorkspaceEncryptionSetting({ size, expanded, onDone, onEnabledEncryption }: Props) { const [justEnabledEncryption, setJustEnabledEncryption] = useState(false); const [error, setError] = useState(null); const workspace = useAtomValue(activeWorkspaceAtom); const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom); const [key, setKey] = useState<{ key: string | null; error: string | null } | null>(null); useEffect(() => { if (workspaceMeta == null) { return; } if (workspaceMeta?.encryptionKey == null) { setKey({ key: null, error: null }); return; } revealWorkspaceKey(workspaceMeta.workspaceId).then( (key) => { setKey({ key, error: null }); }, (err) => { setKey({ key: null, error: `${err}` }); }, ); }, [workspaceMeta, workspaceMeta?.encryptionKey]); if (key == null || workspace == null || workspaceMeta == null) { return null; } // Prompt for key if it doesn't exist or could not be decrypted if ( key.error != null || (workspace.encryptionKeyChallenge && workspaceMeta.encryptionKey == null) ) { return ( { onDone?.(); onEnabledEncryption?.(); }} /> ); } // Show the key if it exists if (workspaceMeta.encryptionKey && key.key != null) { const keyRevealer = ( ); return ( {justEnabledEncryption && ( {helpAfterEncryption} )} {keyRevealer} {onDone && ( )} ); } // Show button to enable encryption return (
{error && ( {error} )} {expanded ? ( ) : ( )}
); } const setWorkspaceKeyMut = createFastMutation({ mutationKey: ['set-workspace-key'], mutationFn: setWorkspaceKey, }); function EnterWorkspaceKey({ workspaceMeta, onEnabled, error, }: { workspaceMeta: WorkspaceMeta; onEnabled?: () => void; error?: string | null; }) { const [key, setKey] = useState(''); return ( {error ? ( {error} ) : ( This workspace contains encrypted values but no key is configured. Please enter the workspace key to access the encrypted data. )} { e.preventDefault(); setWorkspaceKeyMut.mutate( { workspaceId: workspaceMeta.workspaceId, key: key.trim(), }, { onSuccess: onEnabled }, ); }} > ); } function KeyRevealer({ defaultShow = false, disableLabel = false, encryptionKey, }: { defaultShow?: boolean; disableLabel?: boolean; encryptionKey: string; }) { const [show, setShow] = useStateWithDeps(defaultShow, [defaultShow]); return (
{!disableLabel && ( Workspace encryption key{' '} )} {encryptionKey && } {encryptionKey && } setShow((v) => !v)} />
); } function HighlightedKey({ keyText, show }: { keyText: string; show: boolean }) { return ( {show ? ( keyText.split('').map((c, i) => { return ( {c} ); }) ) : (
•••••••••••••••••••••
)}
); } const helpAfterEncryption = (

The following key is used for encryption operations within this workspace. It is stored securely using your OS keychain, but it is recommended to back it up. If you share this workspace with others, you'll need to send them this key to access any encrypted values.

);