From 434bc81deb5b231da25b447a551824f569928df0 Mon Sep 17 00:00:00 2001 From: jake <77554505+brxken128@users.noreply.github.com> Date: Wed, 11 Jan 2023 08:38:45 +0000 Subject: [PATCH] [ENG-329] Crypto dialog refactor (#509) * update backup restoration dialog * restructure MPC dialog * refactor `EncryptFileDialog` * dialog changes Co-authored-by: Brendan Allan --- .../components/dialog/BackupRestoreDialog.tsx | 222 +++++++------ .../components/dialog/DecryptFileDialog.tsx | 141 ++++---- .../components/dialog/EncryptFileDialog.tsx | 152 +++++---- .../dialog/MasterPasswordChangeDialog.tsx | 308 +++++++++--------- 4 files changed, 427 insertions(+), 396 deletions(-) diff --git a/packages/interface/src/components/dialog/BackupRestoreDialog.tsx b/packages/interface/src/components/dialog/BackupRestoreDialog.tsx index 53520192d..f0d947053 100644 --- a/packages/interface/src/components/dialog/BackupRestoreDialog.tsx +++ b/packages/interface/src/components/dialog/BackupRestoreDialog.tsx @@ -2,7 +2,7 @@ import { useLibraryMutation } from '@sd/client'; import { Button, Dialog, Input } from '@sd/ui'; import { Eye, EyeSlash } from 'phosphor-react'; import { ReactNode, useState } from 'react'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { usePlatform } from '../../util/Platform'; import { GenericAlertDialogProps } from './AlertDialog'; @@ -20,6 +20,39 @@ export interface BackupRestorationDialogProps { export const BackupRestoreDialog = (props: BackupRestorationDialogProps) => { const platform = usePlatform(); + + const restoreKeystoreMutation = useLibraryMutation('keys.restoreKeystore', { + onSuccess: (total) => { + setShow((old) => ({ ...old, backupRestoreDialog: false })); + props.setAlertDialogData({ + open: true, + title: 'Import Successful', + description: '', + value: `${total} ${total !== 1 ? 'keys were imported.' : 'key was imported.'}`, + inputBox: false + }); + }, + onError: () => { + setShow((old) => ({ ...old, backupRestoreDialog: false })); + props.setAlertDialogData({ + open: true, + title: 'Import Error', + description: '', + value: 'There was an error while restoring your backup.', + inputBox: false + }); + } + }); + + const [show, setShow] = useState({ + backupRestoreDialog: false, + masterPassword: false, + secretKey: false + }); + + const MPCurrentEyeIcon = show.masterPassword ? EyeSlash : Eye; + const SKCurrentEyeIcon = show.secretKey ? EyeSlash : Eye; + const form = useForm({ defaultValues: { masterPassword: '', @@ -28,124 +61,89 @@ export const BackupRestoreDialog = (props: BackupRestorationDialogProps) => { } }); - const onSubmit: SubmitHandler = (data) => { + const onSubmit = form.handleSubmit((data) => { const sk = data.secretKey || null; if (data.filePath !== '') { - restoreKeystoreMutation.mutate( - { - password: data.masterPassword, - secret_key: sk, - path: data.filePath - }, - { - onSuccess: (total) => { - setShowBackupRestoreDialog(false); - props.setAlertDialogData({ - open: true, - title: 'Import Successful', - description: '', - value: `${total} ${total !== 1 ? 'keys were imported.' : 'key was imported.'}`, - inputBox: false - }); - }, - onError: () => { - setShowBackupRestoreDialog(false); - props.setAlertDialogData({ - open: true, - title: 'Import Error', - description: '', - value: 'There was an error while restoring your backup.', - inputBox: false - }); - } - } - ); + restoreKeystoreMutation.mutate({ + password: data.masterPassword, + secret_key: sk, + path: data.filePath + }); form.reset(); } - }; - - const [showBackupRestoreDialog, setShowBackupRestoreDialog] = useState(false); - const restoreKeystoreMutation = useLibraryMutation('keys.restoreKeystore'); - - const [showMasterPassword, setShowMasterPassword] = useState(false); - const [showSecretKey, setShowSecretKey] = useState(false); - - const MPCurrentEyeIcon = showMasterPassword ? EyeSlash : Eye; - const SKCurrentEyeIcon = showSecretKey ? EyeSlash : Eye; + }); return ( - <> -
- -
- - -
-
- - -
-
- +
+
+ + +
+
+ -
-
-
- + return; + } + platform.openFilePickerDialog().then((result) => { + if (result) form.setValue('filePath', result as string); + }); + }} + > + Select File + + + + ); }; diff --git a/packages/interface/src/components/dialog/DecryptFileDialog.tsx b/packages/interface/src/components/dialog/DecryptFileDialog.tsx index eb5dd0f48..ea9d7365d 100644 --- a/packages/interface/src/components/dialog/DecryptFileDialog.tsx +++ b/packages/interface/src/components/dialog/DecryptFileDialog.tsx @@ -3,6 +3,7 @@ import { useLibraryMutation, useLibraryQuery } from '@sd/client'; import { Button, Dialog, Input, Switch } from '@sd/ui'; import { Eye, EyeSlash, Info } from 'phosphor-react'; import { useState } from 'react'; +import { useForm } from 'react-hook-form'; import { usePlatform } from '../../util/Platform'; import { Tooltip } from '../tooltip/Tooltip'; @@ -16,23 +17,23 @@ interface DecryptDialogProps { setAlertDialogData: (data: GenericAlertDialogProps) => void; } +type FormValues = { + type: 'password' | 'key'; + outputPath: string; + password: string; + saveToKeyManager: boolean; +}; + export const DecryptFileDialog = (props: DecryptDialogProps) => { const platform = usePlatform(); - const { location_id, path_id } = props; - const decryptFile = useLibraryMutation('files.decryptFiles'); - const [outputPath, setOutputpath] = useState(''); - const [password, setPassword] = useState(''); - const [saveToKeyManager, setSaveToKeyManager] = useState(true); - const [showPassword, setShowPassword] = useState(false); - const PasswordCurrentEyeIcon = showPassword ? EyeSlash : Eye; const mountedUuids = useLibraryQuery(['keys.listMounted'], { onSuccess: (data) => { hasMountedKeys = data.length > 0 ? true : false; if (!hasMountedKeys) { - setDecryptType('password'); + form.setValue('type', 'password'); } else { - setDecryptType('key'); + form.setValue('type', 'key'); } } }); @@ -40,10 +41,63 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => { let hasMountedKeys = mountedUuids.data !== undefined && mountedUuids.data.length > 0 ? true : false; - const [decryptType, setDecryptType] = useState(hasMountedKeys ? 'key' : 'password'); + const decryptFile = useLibraryMutation('files.decryptFiles', { + onSuccess: () => { + props.setAlertDialogData({ + open: true, + title: 'Info', + value: + 'The decryption job has started successfully. You may track the progress in the job overview panel.', + inputBox: false, + description: '' + }); + }, + onError: () => { + props.setAlertDialogData({ + open: true, + title: 'Error', + value: 'The decryption job failed to start.', + inputBox: false, + description: '' + }); + } + }); + + const [show, setShow] = useState({ password: false }); + + const PasswordCurrentEyeIcon = show.password ? EyeSlash : Eye; + + const form = useForm({ + defaultValues: { + type: hasMountedKeys ? 'key' : 'password', + outputPath: '', + password: '', + saveToKeyManager: true + } + }); + + const onSubmit = form.handleSubmit((data) => { + const output = data.outputPath !== '' ? data.outputPath : null; + const pw = data.type === 'password' ? data.password : null; + const save = data.type === 'password' ? data.saveToKeyManager : null; + + props.setOpen(false); + + props.location_id && + props.path_id && + decryptFile.mutate({ + location_id: props.location_id, + path_id: props.path_id, + output_path: output, + password: pw, + save_to_library: save + }); + + form.reset(); + }); return ( - <> +
{ description="Leave the output file blank for the default." loading={decryptFile.isLoading} ctaLabel="Decrypt" - ctaAction={() => { - const output = outputPath !== '' ? outputPath : null; - const pw = decryptType === 'password' ? password : null; - const save = decryptType === 'password' ? saveToKeyManager : null; - - props.setOpen(false); - - location_id && - path_id && - decryptFile.mutate( - { - location_id, - path_id, - output_path: output, - password: pw, - save_to_library: save - }, - { - onSuccess: () => { - props.setAlertDialogData({ - open: true, - title: 'Info', - value: - 'The decryption job has started successfully. You may track the progress in the job overview panel.', - inputBox: false, - description: '' - }); - }, - onError: () => { - props.setAlertDialogData({ - open: true, - title: 'Error', - value: 'The decryption job failed to start.', - inputBox: false, - description: '' - }); - } - } - ); - }} > - + form.setValue('type', e)} + className="mt-2" + > Key Type
@@ -117,19 +135,18 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
- {decryptType === 'password' && ( + {form.watch('type') === 'password' && ( <>
setPassword(e.target.value)} - value={password} - type={showPassword ? 'text' : 'password'} + {...form.register('password', { required: false })} + type={show.password ? 'text' : 'password'} required />
Save to Key Manager @@ -161,7 +178,7 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
- +
); }; diff --git a/packages/interface/src/components/dialog/EncryptFileDialog.tsx b/packages/interface/src/components/dialog/EncryptFileDialog.tsx index 87c23ed49..ea424e752 100644 --- a/packages/interface/src/components/dialog/EncryptFileDialog.tsx +++ b/packages/interface/src/components/dialog/EncryptFileDialog.tsx @@ -1,6 +1,7 @@ import { Algorithm, useLibraryMutation, useLibraryQuery } from '@sd/client'; import { Button, Dialog, Select, SelectOption } from '@sd/ui'; import { useState } from 'react'; +import { useForm } from 'react-hook-form'; import { getHashingAlgorithmString } from '../../screens/settings/library/KeysSetting'; import { usePlatform } from '../../util/Platform'; @@ -16,19 +17,25 @@ interface EncryptDialogProps { setAlertDialogData: (data: GenericAlertDialogProps) => void; } +type FormValues = { + key: string; + encryptionAlgo: string; + hashingAlgo: string; + metadata: boolean; + previewMedia: boolean; + outputPath: string; +}; + export const EncryptFileDialog = (props: EncryptDialogProps) => { - const { location_id, path_id } = props; const platform = usePlatform(); - // the selected key will be random, we should prioritise the default - const [key, setKey] = useState(''); - - // decided against react-hook-form, as it doesn't allow us to work with select boxes and such - const [metadata, setMetadata] = useState(false); - const [previewMedia, setPreviewMedia] = useState(false); - const [encryptionAlgo, setEncryptionAlgo] = useState('XChaCha20Poly1305'); - const [hashingAlgo, setHashingAlgo] = useState(''); - const [outputPath, setOutputpath] = useState(''); + const UpdateKey = (uuid: string) => { + form.setValue('key', uuid); + const hashAlg = keys.data?.find((key) => { + return key.uuid === uuid; + })?.hashing_algorithm; + hashAlg && form.setValue('hashingAlgo', getHashingAlgorithmString(hashAlg)); + }; const keys = useLibraryQuery(['keys.list']); const mountedUuids = useLibraryQuery(['keys.listMounted'], { @@ -37,18 +44,60 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => { } }); - const UpdateKey = (uuid: string) => { - setKey(uuid); - const hashAlg = keys.data?.find((key) => { - return key.uuid === uuid; - })?.hashing_algorithm; - hashAlg && setHashingAlgo(getHashingAlgorithmString(hashAlg)); - }; + const encryptFile = useLibraryMutation('files.encryptFiles', { + onSuccess: () => { + props.setAlertDialogData({ + open: true, + title: 'Success', + value: + 'The encryption job has started successfully. You may track the progress in the job overview panel.', + inputBox: false, + description: '' + }); + }, + onError: () => { + props.setAlertDialogData({ + open: true, + title: 'Error', + value: 'The encryption job failed to start.', + inputBox: false, + description: '' + }); + } + }); - const encryptFile = useLibraryMutation('files.encryptFiles'); + const form = useForm({ + defaultValues: { + key: '', + encryptionAlgo: 'XChaCha20Poly1305', + hashingAlgo: 'Argon2id-s', + metadata: false, + previewMedia: false, + outputPath: '' + } + }); + + const onSubmit = form.handleSubmit((data) => { + const output = data.outputPath !== '' ? data.outputPath : null; + props.setOpen(false); + + props.location_id && + props.path_id && + encryptFile.mutate({ + algorithm: data.encryptionAlgo as Algorithm, + key_uuid: data.key, + location_id: props.location_id, + path_id: props.path_id, + metadata: data.metadata, + preview_media: data.previewMedia, + output_path: output + }); + + form.reset(); + }); return ( - <> +
{ description="Configure your encryption settings. Leave the output file blank for the default." loading={encryptFile.isLoading} ctaLabel="Encrypt" - ctaAction={() => { - const output = outputPath !== '' ? outputPath : null; - props.setOpen(false); - - location_id && - path_id && - encryptFile.mutate( - { - algorithm: encryptionAlgo as Algorithm, - key_uuid: key, - location_id, - path_id, - metadata, - preview_media: previewMedia, - output_path: output - }, - { - onSuccess: () => { - props.setAlertDialogData({ - open: true, - title: 'Success', - value: - 'The encryption job has started successfully. You may track the progress in the job overview panel.', - inputBox: false, - description: '' - }); - }, - onError: () => { - props.setAlertDialogData({ - open: true, - title: 'Error', - value: 'The encryption job failed to start.', - inputBox: false, - description: '' - }); - } - } - ); - }} >
Key setEncryptionAlgo(e)}> + @@ -154,7 +168,7 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => { className="mt-2 text-gray-400/80" onChange={() => {}} disabled - value={hashingAlgo} + value={form.watch('hashingAlgo')} > Argon2id (standard) Argon2id (hardened) @@ -169,14 +183,20 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
Metadata - setMetadata(e.target.checked)} /> + form.setValue('metadata', e.target.checked)} + />
Preview Media - setPreviewMedia(e.target.checked)} /> + form.setValue('previewMedia', e.target.checked)} + />
- +
); }; diff --git a/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx b/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx index 9e3c51788..1bbae31f3 100644 --- a/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx +++ b/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx @@ -24,18 +24,6 @@ type FormValues = { }; export const MasterPasswordChangeDialog = (props: MasterPasswordChangeDialogProps) => { - const { trigger } = props; - - const form = useForm({ - defaultValues: { - masterPassword: '', - masterPassword2: '', - secretKey: '', - encryptionAlgo: 'XChaCha20Poly1305', - hashingAlgo: 'Argon2id-s' - } - }); - const changeMasterPassword = useLibraryMutation('keys.changeMasterPassword', { onSuccess: () => { setShow((old) => ({ ...old, masterPasswordDialog: false })); @@ -71,6 +59,16 @@ export const MasterPasswordChangeDialog = (props: MasterPasswordChangeDialogProp const MP2CurrentEyeIcon = show.masterPassword2 ? EyeSlash : Eye; const SKCurrentEyeIcon = show.secretKey ? EyeSlash : Eye; + const form = useForm({ + defaultValues: { + masterPassword: '', + masterPassword2: '', + secretKey: '', + encryptionAlgo: 'XChaCha20Poly1305', + hashingAlgo: 'Argon2id-s' + } + }); + const onSubmit = form.handleSubmit((data) => { if (data.masterPassword !== data.masterPassword2) { props.setAlertDialogData({ @@ -96,153 +94,151 @@ export const MasterPasswordChangeDialog = (props: MasterPasswordChangeDialogProp }); return ( - <> -
- { - setShow((old) => ({ ...old, masterPasswordDialog: e })); - }} - title="Change Master Password" - description="Select a new master password for your key manager. Leave the key secret blank to disable it." - ctaDanger={true} - loading={changeMasterPassword.isLoading} - ctaLabel="Change" - trigger={trigger} - > -
- - - - -
-
- - -
+ + { + setShow((old) => ({ ...old, masterPasswordDialog: e })); + }} + title="Change Master Password" + description="Select a new master password for your key manager. Leave the key secret blank to disable it." + ctaDanger={true} + loading={changeMasterPassword.isLoading} + ctaLabel="Change" + trigger={props.trigger} + > +
+ + + + +
+
+ + +
-
- - - - -
+
+ + + + +
- + -
-
- Encryption - -
-
- Hashing - -
+
+
+ Encryption +
-
- - +
+ Hashing + +
+ +
+ ); };