[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:
jake 2023-01-11 08:38:45 +00:00 committed by GitHub
parent 50b788f9e6
commit 434bc81deb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 427 additions and 396 deletions

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};