-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1675 from oasisprotocol/mz/profile
Password change
- Loading branch information
Showing
19 changed files
with
1,042 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { test, expect } from '@playwright/test' | ||
import { password } from '../utils/test-inputs' | ||
import { fillPrivateKeyAndPassword } from '../utils/fillPrivateKey' | ||
import { warnSlowApi } from '../utils/warnSlowApi' | ||
import { mockApi } from '../utils/mockApi' | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await warnSlowApi(page) | ||
await mockApi(page, 500000000000) | ||
}) | ||
|
||
const tempPassword = '123' | ||
|
||
test.describe('Profile tab', () => { | ||
test('should update password', async ({ page }) => { | ||
await page.goto('/open-wallet/private-key') | ||
await fillPrivateKeyAndPassword(page) | ||
await page.getByTestId('account-selector').click() | ||
await page.getByTestId('toolbar-profile-tab').click() | ||
// use wrong password | ||
await page.getByPlaceholder('Current password').fill('wrongPassword') | ||
await page.getByPlaceholder('New password', { exact: true }).fill(tempPassword) | ||
await page.getByPlaceholder('Re-enter new password').fill(tempPassword) | ||
await page.keyboard.press('Enter') | ||
await expect(page.getByText('Wrong password')).toBeVisible() | ||
// set temp password | ||
await page.getByPlaceholder('Current password').fill(password) | ||
await page.keyboard.press('Enter') | ||
await expect(page.getByText('Password updated.')).toBeVisible() | ||
|
||
await page.getByTestId('close-settings-modal').click() | ||
await page.getByRole('button', { name: /Lock profile/ }).click() | ||
await page.getByPlaceholder('Enter your password here').fill(tempPassword) | ||
await page.getByRole('button', { name: /Unlock/ }).click() | ||
await expect(page.getByText('Loading', { exact: true })).toBeVisible() | ||
await expect(page.getByText('Loading', { exact: true })).toBeHidden() | ||
|
||
// set back default password | ||
await page.getByTestId('account-selector').click() | ||
await page.getByTestId('toolbar-profile-tab').click() | ||
await page.getByPlaceholder('Current password').fill(tempPassword) | ||
await page.getByPlaceholder('New password', { exact: true }).fill(password) | ||
await page.getByPlaceholder('Re-enter new password').fill(password) | ||
await page.keyboard.press('Enter') | ||
await expect(page.getByText('Password updated.')).toBeVisible() | ||
|
||
// validate default password | ||
await page.getByTestId('close-settings-modal').click() | ||
await page.getByRole('button', { name: /Lock profile/ }).click() | ||
await page.getByPlaceholder('Enter your password here').fill(password) | ||
await page.getByRole('button', { name: /Unlock/ }).click() | ||
await expect(page.getByText('Loading', { exact: true })).toBeVisible() | ||
await expect(page.getByText('Loading', { exact: true })).toBeHidden() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { PasswordField } from 'app/components/PasswordField' | ||
import { useTranslation } from 'react-i18next' | ||
|
||
export interface FormValue { | ||
password1?: string | ||
/** | ||
* Undefined if: | ||
* - persistence is unsupported | ||
* - or is already persisting (unlocked) or skipped unlocking | ||
* - or didn't opt to start persisting | ||
*/ | ||
password2?: string | ||
} | ||
|
||
interface ChoosePasswordInputFieldsProps { | ||
password1Placeholder?: string | ||
password2Placeholder?: string | ||
} | ||
|
||
export function ChoosePasswordInputFields({ | ||
password1Placeholder, | ||
password2Placeholder, | ||
}: ChoosePasswordInputFieldsProps) { | ||
const { t } = useTranslation() | ||
|
||
return ( | ||
<> | ||
<PasswordField<FormValue> | ||
placeholder={password1Placeholder} | ||
inputElementId="password1" | ||
name="password1" | ||
validate={value => | ||
value ? undefined : t('persist.loginToProfile.enterPasswordHere', 'Enter your password here') | ||
} | ||
required | ||
showTip={t('persist.loginToProfile.showPassword', 'Show password')} | ||
hideTip={t('persist.loginToProfile.hidePassword', 'Hide password')} | ||
width="medium" | ||
/> | ||
|
||
<PasswordField<FormValue> | ||
placeholder={password2Placeholder} | ||
inputElementId="password2" | ||
name="password2" | ||
validate={(value, form) => | ||
form.password1 !== form.password2 | ||
? t('persist.createProfile.passwordMismatch', 'Entered password does not match') | ||
: undefined | ||
} | ||
showTip={t('persist.loginToProfile.showPassword', 'Show password')} | ||
hideTip={t('persist.loginToProfile.hidePassword', 'Hide password')} | ||
width="medium" | ||
/> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
src/app/components/Toolbar/Features/Profile/UpdatePassword.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { useEffect, useState } from 'react' | ||
import { useDispatch, useSelector } from 'react-redux' | ||
import { useTranslation } from 'react-i18next' | ||
import { Box } from 'grommet/es6/components/Box' | ||
import { Button } from 'grommet/es6/components/Button' | ||
import { Form } from 'grommet/es6/components/Form' | ||
import { Paragraph } from 'grommet/es6/components/Paragraph' | ||
import { Notification } from 'grommet/es6/components/Notification' | ||
import { | ||
ChoosePasswordInputFields, | ||
FormValue as ChoosePasswordFieldsFormValue, | ||
} from 'app/components/Persist/ChoosePasswordInputFields' | ||
import { PasswordField } from 'app/components/PasswordField' | ||
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData' | ||
import { persistActions } from 'app/state/persist' | ||
import { selectEnteredWrongPassword, selectLoading } from 'app/state/persist/selectors' | ||
|
||
interface FormValue extends ChoosePasswordFieldsFormValue { | ||
currentPassword?: string | ||
} | ||
|
||
const defaultFormValue: FormValue = { | ||
currentPassword: '', | ||
password1: '', | ||
password2: '', | ||
} | ||
|
||
export const UpdatePassword = () => { | ||
const { t } = useTranslation() | ||
const dispatch = useDispatch() | ||
const [notificationVisible, setNotificationVisible] = useState(false) | ||
const enteredWrongPassword = useSelector(selectEnteredWrongPassword) | ||
const isProfileReloadingAfterPasswordUpdate = useSelector(selectLoading) | ||
const [value, setValue] = useState(defaultFormValue) | ||
const onSubmit = ({ value }: { value: FormValue }) => { | ||
if (!value.currentPassword || !value.password1) { | ||
return | ||
} | ||
dispatch( | ||
persistActions.updatePasswordAsync({ | ||
currentPassword: value.currentPassword, | ||
password: value.password1, | ||
}), | ||
) | ||
} | ||
|
||
useEffect(() => { | ||
return () => { | ||
dispatch(persistActions.resetWrongPassword()) | ||
} | ||
}, [dispatch]) | ||
|
||
useEffect(() => { | ||
// reloading occurs after successful password update | ||
if (isProfileReloadingAfterPasswordUpdate) { | ||
setNotificationVisible(true) | ||
setValue(defaultFormValue) | ||
} | ||
}, [isProfileReloadingAfterPasswordUpdate]) | ||
|
||
return ( | ||
<Form<FormValue> | ||
onSubmit={onSubmit} | ||
{...preventSavingInputsToUserData} | ||
onChange={nextValue => setValue(nextValue)} | ||
value={value} | ||
> | ||
<Paragraph> | ||
<label htmlFor="password1">{t('toolbar.profile.password.title', 'Set a new password')}</label> | ||
</Paragraph> | ||
<PasswordField<FormValue> | ||
placeholder={t('toolbar.profile.password.current', 'Current password')} | ||
inputElementId="currentPassword" | ||
name="currentPassword" | ||
validate={value => | ||
value ? undefined : t('toolbar.profile.password.enterCurrent', 'Enter your current password') | ||
} | ||
error={enteredWrongPassword ? t('persist.loginToProfile.wrongPassword', 'Wrong password') : false} | ||
required | ||
showTip={t('persist.loginToProfile.showPassword', 'Show password')} | ||
hideTip={t('persist.loginToProfile.hidePassword', 'Hide password')} | ||
width="medium" | ||
/> | ||
<ChoosePasswordInputFields | ||
password1Placeholder={t('toolbar.profile.password.enterNewPassword', 'New password')} | ||
password2Placeholder={t('toolbar.profile.password.reenterNewPassword', 'Re-enter new password')} | ||
/> | ||
<Box direction="row" justify="end" margin={{ top: 'medium' }}> | ||
<Button primary type="submit" label={t('toolbar.profile.password.submit', 'Update password')} /> | ||
</Box> | ||
{notificationVisible && ( | ||
<Notification | ||
toast | ||
status={'normal'} | ||
title={t('toolbar.profile.password.success', 'Password updated.')} | ||
onClose={() => setNotificationVisible(false)} | ||
/> | ||
)} | ||
</Form> | ||
) | ||
} |
51 changes: 51 additions & 0 deletions
51
src/app/components/Toolbar/Features/Profile/__tests__/UpdatePassword.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { render, screen } from '@testing-library/react' | ||
import userEvent from '@testing-library/user-event' | ||
import { Provider } from 'react-redux' | ||
import { configureAppStore } from 'store/configureStore' | ||
import { ThemeProvider } from 'styles/theme/ThemeProvider' | ||
import { persistActions } from 'app/state/persist' | ||
import { UpdatePassword } from '../UpdatePassword' | ||
|
||
const renderComponent = (store: any) => | ||
render( | ||
<Provider store={store}> | ||
<ThemeProvider> | ||
<UpdatePassword /> | ||
</ThemeProvider> | ||
</Provider>, | ||
) | ||
|
||
describe('<UpdatePassword />', () => { | ||
let store: ReturnType<typeof configureAppStore> | ||
|
||
beforeEach(() => { | ||
store = configureAppStore() | ||
}) | ||
|
||
it('should dispatch action on submit', async () => { | ||
const spy = jest.spyOn(store, 'dispatch') | ||
renderComponent(store) | ||
|
||
await userEvent.type(screen.getByPlaceholderText('toolbar.profile.password.current'), 'asd') | ||
await userEvent.type(screen.getByPlaceholderText('toolbar.profile.password.enterNewPassword'), '123') | ||
await userEvent.type(screen.getByPlaceholderText('toolbar.profile.password.reenterNewPassword'), '123') | ||
await userEvent.click(screen.getByRole('button', { name: 'toolbar.profile.password.submit' })) | ||
expect(spy).toHaveBeenCalledWith({ | ||
payload: { | ||
currentPassword: 'asd', | ||
password: '123', | ||
}, | ||
type: persistActions.updatePasswordAsync.type, | ||
}) | ||
}) | ||
|
||
it('should clear redux password error', () => { | ||
const spy = jest.spyOn(store, 'dispatch') | ||
const { unmount } = renderComponent(store) | ||
|
||
unmount() | ||
expect(spy).toHaveBeenCalledWith({ | ||
type: persistActions.resetWrongPassword.type, | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.