From 79d97f2ba7077d36d8d05aaa7796699f83ab69bf Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 15 Nov 2021 12:02:59 +0000 Subject: [PATCH] Desktop: Allow showing passwords in Master Password dialog --- .eslintignore | 3 ++ .gitignore | 3 ++ .../gui/MasterPasswordDialog/Dialog.tsx | 30 +++++++++++------- .../gui/PasswordInput/PasswordInput.tsx | 31 +++++++++++++++++++ .../app-desktop/gui/PasswordInput/style.scss | 19 ++++++++++++ packages/app-desktop/main.scss | 14 ++++++--- packages/app-desktop/style.scss | 1 + 7 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 packages/app-desktop/gui/PasswordInput/PasswordInput.tsx create mode 100644 packages/app-desktop/gui/PasswordInput/style.scss diff --git a/.eslintignore b/.eslintignore index 36a2c3fcb..95acf5fc9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -534,6 +534,9 @@ packages/app-desktop/gui/NoteToolbar/NoteToolbar.js.map packages/app-desktop/gui/OneDriveLoginScreen.d.ts packages/app-desktop/gui/OneDriveLoginScreen.js packages/app-desktop/gui/OneDriveLoginScreen.js.map +packages/app-desktop/gui/PasswordInput/PasswordInput.d.ts +packages/app-desktop/gui/PasswordInput/PasswordInput.js +packages/app-desktop/gui/PasswordInput/PasswordInput.js.map packages/app-desktop/gui/ResizableLayout/MoveButtons.d.ts packages/app-desktop/gui/ResizableLayout/MoveButtons.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js.map diff --git a/.gitignore b/.gitignore index 65c58b1bc..2df940c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -517,6 +517,9 @@ packages/app-desktop/gui/NoteToolbar/NoteToolbar.js.map packages/app-desktop/gui/OneDriveLoginScreen.d.ts packages/app-desktop/gui/OneDriveLoginScreen.js packages/app-desktop/gui/OneDriveLoginScreen.js.map +packages/app-desktop/gui/PasswordInput/PasswordInput.d.ts +packages/app-desktop/gui/PasswordInput/PasswordInput.js +packages/app-desktop/gui/PasswordInput/PasswordInput.js.map packages/app-desktop/gui/ResizableLayout/MoveButtons.d.ts packages/app-desktop/gui/ResizableLayout/MoveButtons.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js.map diff --git a/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx b/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx index d2aabe492..ee12fe20a 100644 --- a/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx +++ b/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx @@ -5,12 +5,12 @@ import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffe import DialogButtonRow, { ClickEvent } from '../DialogButtonRow'; import Dialog from '../Dialog'; import DialogTitle from '../DialogTitle'; -import StyledInput from '../style/StyledInput'; -import { getMasterPasswordStatus, getMasterPasswordStatusMessage, checkHasMasterPasswordEncryptedData, masterPasswordIsValid, MasterPasswordStatus, resetMasterPassword, updateMasterPassword } from '@joplin/lib/services/e2ee/utils'; +import { getMasterPasswordStatus, getMasterPasswordStatusMessage, checkHasMasterPasswordEncryptedData, masterPasswordIsValid, MasterPasswordStatus, resetMasterPassword, updateMasterPassword, getMasterPassword } from '@joplin/lib/services/e2ee/utils'; import { reg } from '@joplin/lib/registry'; import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import KvStore from '@joplin/lib/services/KvStore'; import ShareService from '@joplin/lib/services/share/ShareService'; +import { PasswordInput } from '../PasswordInput/PasswordInput'; interface Props { themeId: number; @@ -41,6 +41,10 @@ export default function(props: Props) { }); }, [props.dispatch]); + useEffect(() => { + setCurrentPassword(getMasterPassword(false) || ''); + }, []); + useAsyncEffect(async (event: AsyncEffectEvent) => { const newStatus = await getMasterPasswordStatus(); const hasIt = await checkHasMasterPasswordEncryptedData(); @@ -122,7 +126,7 @@ export default function(props: Props) { function renderCurrentPasswordIcon() { if (!currentPassword || status === MasterPasswordStatus.NotSet) return null; - return currentPasswordIsValid ? : ; + return currentPasswordIsValid ? : ; } function renderPasswordForm() { @@ -130,15 +134,17 @@ export default function(props: Props) { if (status === MasterPasswordStatus.NotSet) return null; if (mode === Mode.Reset) return null; + // If the master password is in the keychain we preload it into the + // field and allow displaying it. That way if the user has forgotten + // their password, they have a chance to recover it that way without + // having to reset the password (and lose access to any data that's + // been encrypted with it). + return (
- + {renderCurrentPasswordIcon()}
@@ -152,18 +158,20 @@ export default function(props: Props) { }; if (showPasswordForm) { + const enterPasswordLabel = [MasterPasswordStatus.Loaded, MasterPasswordStatus.Valid].includes(status) ? 'Enter new password' : 'Enter password'; + return (
{renderCurrentPassword()}
- - + +
{needToRepeatPassword && (
- +
)}
diff --git a/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx b/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx new file mode 100644 index 000000000..d25fa4179 --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx @@ -0,0 +1,31 @@ +import { useState, useCallback } from 'react'; +import StyledInput from '../style/StyledInput'; + +export interface ChangeEvent { + value: string; +} + +type ChangeEventHandler = (event: ChangeEvent)=> void; + +interface Props { + value: string; + onChange: ChangeEventHandler; +} + +export const PasswordInput = (props: Props) => { + const [showPassword, setShowPassword] = useState(false); + + const inputType = showPassword ? 'text' : 'password'; + const icon = showPassword ? 'far fa-eye-slash' : 'far fa-eye'; + + const onShowPassword = useCallback(() => { + setShowPassword(current => !current); + }, []); + + return ( +
+ + +
+ ); +}; diff --git a/packages/app-desktop/gui/PasswordInput/style.scss b/packages/app-desktop/gui/PasswordInput/style.scss new file mode 100644 index 000000000..eba398237 --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/style.scss @@ -0,0 +1,19 @@ +.password-input { + display: flex; + position: relative; + flex: 1; + + > .field { + display: flex; + flex: 1; + width: 100%; + } + + > .showpasswordbutton { + position: absolute; + right: 5px; + top: 4px; + border: none; + background: none; + } +} diff --git a/packages/app-desktop/main.scss b/packages/app-desktop/main.scss index bd7fe2967..5438d3465 100644 --- a/packages/app-desktop/main.scss +++ b/packages/app-desktop/main.scss @@ -149,7 +149,7 @@ a { General classes ========================================================================================= */ -body { +body, button { color: var(--joplin-color); font-size: 16px; } @@ -238,12 +238,16 @@ Component-specific classes display: flex; flex-direction: row; align-items: center; + + > .password-valid-icon { + margin-left: 10px; + } } -.master-password-dialog .current-password-wrapper input { - flex: 1; - margin-right: 10px; -} +// .master-password-dialog .current-password-wrapper input { +// flex: 1; +// margin-right: 10px; +// } .master-password-dialog .fa-check { color: var(--joplin-color-correct); diff --git a/packages/app-desktop/style.scss b/packages/app-desktop/style.scss index b3ad90bb1..a92219f79 100644 --- a/packages/app-desktop/style.scss +++ b/packages/app-desktop/style.scss @@ -1,3 +1,4 @@ @use 'main.scss' as main; @use 'gui/EncryptionConfigScreen/style.scss' as encryption-config-screen; +@use 'gui/PasswordInput/style.scss' as password-input; @use 'gui/ConfigScreen/style.scss' as config-screen; \ No newline at end of file