mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-08 08:22:53 +00:00
[ENG-329] Crypto dialog refactor (#509)
* update backup restoration dialog * restructure MPC dialog * refactor `EncryptFileDialog` * dialog changes Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
parent
50b788f9e6
commit
434bc81deb
|
@ -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<FormValues>({
|
||||
defaultValues: {
|
||||
masterPassword: '',
|
||||
|
@ -28,124 +61,89 @@ export const BackupRestoreDialog = (props: BackupRestorationDialogProps) => {
|
|||
}
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<FormValues> = (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 (
|
||||
<>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Dialog
|
||||
open={showBackupRestoreDialog}
|
||||
setOpen={setShowBackupRestoreDialog}
|
||||
title="Restore Keys"
|
||||
description="Restore keys from a backup."
|
||||
loading={restoreKeystoreMutation.isLoading}
|
||||
ctaLabel="Restore"
|
||||
trigger={props.trigger}
|
||||
>
|
||||
<div className="relative flex flex-grow mt-3 mb-2">
|
||||
<Input
|
||||
className="flex-grow !py-0.5"
|
||||
placeholder="Master Password"
|
||||
required
|
||||
type={showMasterPassword ? 'text' : 'password'}
|
||||
{...form.register('masterPassword', { required: true })}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShowMasterPassword(!showMasterPassword)}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<MPCurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-3">
|
||||
<Input
|
||||
className="flex-grow !py-0.5"
|
||||
placeholder="Secret Key"
|
||||
{...form.register('secretKey', { required: false })}
|
||||
type={showSecretKey ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShowSecretKey(!showSecretKey)}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<SKCurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={form.watch('filePath') !== '' ? 'accent' : 'gray'}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!platform.openFilePickerDialog) {
|
||||
// TODO: Support opening locations on web
|
||||
props.setAlertDialogData({
|
||||
open: true,
|
||||
title: 'Error',
|
||||
description: '',
|
||||
value: "System dialogs aren't supported on this platform.",
|
||||
inputBox: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
platform.openFilePickerDialog().then((result) => {
|
||||
if (result) form.setValue('filePath', result as string);
|
||||
<form onSubmit={onSubmit}>
|
||||
<Dialog
|
||||
open={show.backupRestoreDialog}
|
||||
setOpen={(e) => setShow((old) => ({ ...old, backupRestoreDialog: e }))}
|
||||
title="Restore Keys"
|
||||
description="Restore keys from a backup."
|
||||
loading={restoreKeystoreMutation.isLoading}
|
||||
ctaLabel="Restore"
|
||||
trigger={props.trigger}
|
||||
>
|
||||
<div className="relative flex flex-grow mt-3 mb-2">
|
||||
<Input
|
||||
className="flex-grow !py-0.5"
|
||||
placeholder="Master Password"
|
||||
required
|
||||
type={show.masterPassword ? 'text' : 'password'}
|
||||
{...form.register('masterPassword', { required: true })}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, masterPassword: !old.masterPassword }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<MPCurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-3">
|
||||
<Input
|
||||
className="flex-grow !py-0.5"
|
||||
placeholder="Secret Key"
|
||||
{...form.register('secretKey', { required: false })}
|
||||
type={show.secretKey ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, secretKey: !old.secretKey }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<SKCurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={form.watch('filePath') !== '' ? 'accent' : 'gray'}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!platform.openFilePickerDialog) {
|
||||
// TODO: Support opening locations on web
|
||||
props.setAlertDialogData({
|
||||
open: true,
|
||||
title: 'Error',
|
||||
description: '',
|
||||
value: "System dialogs aren't supported on this platform.",
|
||||
inputBox: false
|
||||
});
|
||||
}}
|
||||
>
|
||||
Select File
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
</form>
|
||||
</>
|
||||
return;
|
||||
}
|
||||
platform.openFilePickerDialog().then((result) => {
|
||||
if (result) form.setValue('filePath', result as string);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Select File
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<FormValues>({
|
||||
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 (
|
||||
<>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Dialog
|
||||
open={props.open}
|
||||
setOpen={props.setOpen}
|
||||
|
@ -51,48 +105,12 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
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: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<RadioGroup value={decryptType} onChange={setDecryptType} className="mt-2">
|
||||
<RadioGroup
|
||||
value={form.watch('type')}
|
||||
onChange={(e: 'key' | 'password') => form.setValue('type', e)}
|
||||
className="mt-2"
|
||||
>
|
||||
<span className="text-xs font-bold">Key Type</span>
|
||||
<div className="flex flex-row gap-2 mt-2">
|
||||
<RadioGroup.Option disabled={!hasMountedKeys} value="key">
|
||||
|
@ -117,19 +135,18 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
{decryptType === 'password' && (
|
||||
{form.watch('type') === 'password' && (
|
||||
<>
|
||||
<div className="relative flex flex-grow mt-3 mb-2">
|
||||
<Input
|
||||
className={`flex-grow w-max !py-0.5`}
|
||||
placeholder="Password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
value={password}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
{...form.register('password', { required: false })}
|
||||
type={show.password ? 'text' : 'password'}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
onClick={() => setShow((old) => ({ ...old, password: !old.password }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
|
@ -143,8 +160,8 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
<Switch
|
||||
className="bg-app-selected"
|
||||
size="sm"
|
||||
checked={saveToKeyManager}
|
||||
onCheckedChange={setSaveToKeyManager}
|
||||
checked={form.watch('saveToKeyManager')}
|
||||
onCheckedChange={(e) => form.setValue('saveToKeyManager', e)}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-3 text-xs font-medium mt-0.5">Save to Key Manager</span>
|
||||
|
@ -161,7 +178,7 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
|
||||
<Button
|
||||
size="sm"
|
||||
variant={outputPath !== '' ? 'accent' : 'gray'}
|
||||
variant={form.watch('outputPath') !== '' ? 'accent' : 'gray'}
|
||||
className="h-[23px] text-xs leading-3 mt-2"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
@ -178,7 +195,7 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
return;
|
||||
}
|
||||
platform.saveFilePickerDialog().then((result) => {
|
||||
if (result) setOutputpath(result as string);
|
||||
if (result) form.setValue('outputPath', result as string);
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -187,6 +204,6 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<FormValues>({
|
||||
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 (
|
||||
<>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Dialog
|
||||
open={props.open}
|
||||
setOpen={props.setOpen}
|
||||
|
@ -56,52 +105,13 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
|
|||
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: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className="grid w-full grid-cols-2 gap-4 mt-4 mb-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold">Key</span>
|
||||
<Select
|
||||
className="mt-2"
|
||||
value={key}
|
||||
value={form.watch('key')}
|
||||
onChange={(e) => {
|
||||
UpdateKey(e);
|
||||
}}
|
||||
|
@ -114,7 +124,7 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
|
|||
|
||||
<Button
|
||||
size="sm"
|
||||
variant={outputPath !== '' ? 'accent' : 'gray'}
|
||||
variant={form.watch('outputPath') !== '' ? 'accent' : 'gray'}
|
||||
className="h-[23px] text-xs leading-3 mt-2"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
@ -131,7 +141,7 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
|
|||
return;
|
||||
}
|
||||
platform.saveFilePickerDialog().then((result) => {
|
||||
if (result) setOutputpath(result as string);
|
||||
if (result) form.setValue('outputPath', result as string);
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -143,7 +153,11 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
|
|||
<div className="grid w-full grid-cols-2 gap-4 mt-4 mb-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold">Encryption</span>
|
||||
<Select className="mt-2" value={encryptionAlgo} onChange={(e) => setEncryptionAlgo(e)}>
|
||||
<Select
|
||||
className="mt-2"
|
||||
value={form.watch('encryptionAlgo')}
|
||||
onChange={(e) => form.setValue('encryptionAlgo', e)}
|
||||
>
|
||||
<SelectOption value="XChaCha20Poly1305">XChaCha20-Poly1305</SelectOption>
|
||||
<SelectOption value="Aes256Gcm">AES-256-GCM</SelectOption>
|
||||
</Select>
|
||||
|
@ -154,7 +168,7 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
|
|||
className="mt-2 text-gray-400/80"
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
value={hashingAlgo}
|
||||
value={form.watch('hashingAlgo')}
|
||||
>
|
||||
<SelectOption value="Argon2id-s">Argon2id (standard)</SelectOption>
|
||||
<SelectOption value="Argon2id-h">Argon2id (hardened)</SelectOption>
|
||||
|
@ -169,14 +183,20 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
|
|||
<div className="grid w-full grid-cols-2 gap-4 mt-4 mb-3">
|
||||
<div className="flex">
|
||||
<span className="text-sm font-bold mr-3 ml-0.5 mt-0.5">Metadata</span>
|
||||
<Checkbox checked={metadata} onChange={(e) => setMetadata(e.target.checked)} />
|
||||
<Checkbox
|
||||
checked={form.watch('metadata')}
|
||||
onChange={(e) => form.setValue('metadata', e.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-sm font-bold mr-3 ml-0.5 mt-0.5">Preview Media</span>
|
||||
<Checkbox checked={previewMedia} onChange={(e) => setPreviewMedia(e.target.checked)} />
|
||||
<Checkbox
|
||||
checked={form.watch('previewMedia')}
|
||||
onChange={(e) => form.setValue('previewMedia', e.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,18 +24,6 @@ type FormValues = {
|
|||
};
|
||||
|
||||
export const MasterPasswordChangeDialog = (props: MasterPasswordChangeDialogProps) => {
|
||||
const { trigger } = props;
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
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<FormValues>({
|
||||
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 (
|
||||
<>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Dialog
|
||||
open={show.masterPasswordDialog}
|
||||
setOpen={(e) => {
|
||||
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}
|
||||
>
|
||||
<div className="relative flex flex-grow mt-3 mb-2">
|
||||
<Input
|
||||
className={`flex-grow w-max !py-0.5`}
|
||||
placeholder="New password"
|
||||
required
|
||||
{...form.register('masterPassword', { required: true })}
|
||||
type={show.masterPassword ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const password = generatePassword(32);
|
||||
form.setValue('masterPassword', password);
|
||||
form.setValue('masterPassword2', password);
|
||||
setShow((old) => ({
|
||||
...old,
|
||||
masterPassword: true,
|
||||
masterPassword2: true
|
||||
}));
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[65px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<ArrowsClockwise className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(form.watch('masterPassword') as string);
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[35px] top-[5px]"
|
||||
>
|
||||
<Clipboard className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, masterPassword: !old.masterPassword }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<MP1CurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-2">
|
||||
<Input
|
||||
className={`flex-grow !py-0.5}`}
|
||||
placeholder="New password (again)"
|
||||
required
|
||||
{...form.register('masterPassword2', { required: true })}
|
||||
type={show.masterPassword2 ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, masterPassword2: !old.masterPassword2 }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<MP2CurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Dialog
|
||||
open={show.masterPasswordDialog}
|
||||
setOpen={(e) => {
|
||||
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}
|
||||
>
|
||||
<div className="relative flex flex-grow mt-3 mb-2">
|
||||
<Input
|
||||
className={`flex-grow w-max !py-0.5`}
|
||||
placeholder="New password"
|
||||
required
|
||||
{...form.register('masterPassword', { required: true })}
|
||||
type={show.masterPassword ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const password = generatePassword(32);
|
||||
form.setValue('masterPassword', password);
|
||||
form.setValue('masterPassword2', password);
|
||||
setShow((old) => ({
|
||||
...old,
|
||||
masterPassword: true,
|
||||
masterPassword2: true
|
||||
}));
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[65px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<ArrowsClockwise className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(form.watch('masterPassword') as string);
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[35px] top-[5px]"
|
||||
>
|
||||
<Clipboard className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, masterPassword: !old.masterPassword }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<MP1CurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-2">
|
||||
<Input
|
||||
className={`flex-grow !py-0.5}`}
|
||||
placeholder="New password (again)"
|
||||
required
|
||||
{...form.register('masterPassword2', { required: true })}
|
||||
type={show.masterPassword2 ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, masterPassword2: !old.masterPassword2 }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<MP2CurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="relative flex flex-grow mb-2">
|
||||
<Input
|
||||
className={`flex-grow !py-0.5}`}
|
||||
placeholder="Key secret"
|
||||
{...form.register('secretKey', { required: false })}
|
||||
type={show.secretKey ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
// onClick={() => setmasterPassword2(!masterPassword2)}
|
||||
onClick={() => {
|
||||
form.setValue('secretKey', cryptoRandomString({ length: 24 }));
|
||||
setShow((old) => ({ ...old, secretKey: true }));
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[65px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<ArrowsClockwise className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(form.watch('secretKey') as string);
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[35px] top-[5px]"
|
||||
>
|
||||
<Clipboard className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, secretKey: !old.secretKey }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<SKCurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative flex flex-grow mb-2">
|
||||
<Input
|
||||
className={`flex-grow !py-0.5}`}
|
||||
placeholder="Key secret"
|
||||
{...form.register('secretKey', { required: false })}
|
||||
type={show.secretKey ? 'text' : 'password'}
|
||||
/>
|
||||
<Button
|
||||
// onClick={() => setmasterPassword2(!masterPassword2)}
|
||||
onClick={() => {
|
||||
form.setValue('secretKey', cryptoRandomString({ length: 24 }));
|
||||
setShow((old) => ({ ...old, secretKey: true }));
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[65px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<ArrowsClockwise className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(form.watch('secretKey') as string);
|
||||
}}
|
||||
size="icon"
|
||||
className="border-none absolute right-[35px] top-[5px]"
|
||||
>
|
||||
<Clipboard className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShow((old) => ({ ...old, secretKey: !old.secretKey }))}
|
||||
size="icon"
|
||||
className="border-none absolute right-[5px] top-[5px]"
|
||||
type="button"
|
||||
>
|
||||
<SKCurrentEyeIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<PasswordMeter password={form.watch('masterPassword')} />
|
||||
<PasswordMeter password={form.watch('masterPassword')} />
|
||||
|
||||
<div className="grid w-full grid-cols-2 gap-4 mt-4 mb-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold">Encryption</span>
|
||||
<Select
|
||||
className="mt-2"
|
||||
value={form.watch('encryptionAlgo')}
|
||||
onChange={(e) => form.setValue('encryptionAlgo', e)}
|
||||
>
|
||||
<SelectOption value="XChaCha20Poly1305">XChaCha20-Poly1305</SelectOption>
|
||||
<SelectOption value="Aes256Gcm">AES-256-GCM</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold">Hashing</span>
|
||||
<Select
|
||||
className="mt-2"
|
||||
value={form.watch('hashingAlgo')}
|
||||
onChange={(e) => form.setValue('hashingAlgo', e)}
|
||||
>
|
||||
<SelectOption value="Argon2id-s">Argon2id (standard)</SelectOption>
|
||||
<SelectOption value="Argon2id-h">Argon2id (hardened)</SelectOption>
|
||||
<SelectOption value="Argon2id-p">Argon2id (paranoid)</SelectOption>
|
||||
<SelectOption value="BalloonBlake3-s">BLAKE3-Balloon (standard)</SelectOption>
|
||||
<SelectOption value="BalloonBlake3-h">BLAKE3-Balloon (hardened)</SelectOption>
|
||||
<SelectOption value="BalloonBlake3-p">BLAKE3-Balloon (paranoid)</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid w-full grid-cols-2 gap-4 mt-4 mb-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold">Encryption</span>
|
||||
<Select
|
||||
className="mt-2"
|
||||
value={form.watch('encryptionAlgo')}
|
||||
onChange={(e) => form.setValue('encryptionAlgo', e)}
|
||||
>
|
||||
<SelectOption value="XChaCha20Poly1305">XChaCha20-Poly1305</SelectOption>
|
||||
<SelectOption value="Aes256Gcm">AES-256-GCM</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
</Dialog>
|
||||
</form>
|
||||
</>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold">Hashing</span>
|
||||
<Select
|
||||
className="mt-2"
|
||||
value={form.watch('hashingAlgo')}
|
||||
onChange={(e) => form.setValue('hashingAlgo', e)}
|
||||
>
|
||||
<SelectOption value="Argon2id-s">Argon2id (standard)</SelectOption>
|
||||
<SelectOption value="Argon2id-h">Argon2id (hardened)</SelectOption>
|
||||
<SelectOption value="Argon2id-p">Argon2id (paranoid)</SelectOption>
|
||||
<SelectOption value="BalloonBlake3-s">BLAKE3-Balloon (standard)</SelectOption>
|
||||
<SelectOption value="BalloonBlake3-h">BLAKE3-Balloon (hardened)</SelectOption>
|
||||
<SelectOption value="BalloonBlake3-p">BLAKE3-Balloon (paranoid)</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue