polkadot-js/apps

View on GitHub
packages/page-accounts/src/modals/ChangePass.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
// Copyright 2017-2024 @polkadot/app-accounts authors & contributors
// SPDX-License-Identifier: Apache-2.0

import React, { useCallback, useState } from 'react';

import { AddressRow, Button, Modal, Password, PasswordStrength } from '@polkadot/react-components';
import { keyring } from '@polkadot/ui-keyring';
import { nextTick } from '@polkadot/util';

import { useTranslation } from '../translate.js';

interface Props {
  className?: string;
  address: string;
  onClose: () => void;
}

interface NewPass {
  isValid: boolean;
  password: string;
}

interface OldPass {
  isOldValid: boolean;
  oldPass: string;
}

function ChangePass ({ address, className = '', onClose }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const [isBusy, setIsBusy] = useState(false);
  const [newPass1, setNewPass1] = useState<NewPass>({ isValid: false, password: '' });
  const [newPass2, setNewPass2] = useState<NewPass>({ isValid: false, password: '' });
  const [{ isOldValid, oldPass }, setOldPass] = useState<OldPass>({ isOldValid: false, oldPass: '' });

  const _onChangeNew1 = useCallback(
    (password: string) =>
      setNewPass1({ isValid: keyring.isPassValid(password), password }),
    []
  );

  const _onChangeNew2 = useCallback(
    (password: string) =>
      setNewPass2({ isValid: keyring.isPassValid(password) && (newPass1.password === password), password }),
    [newPass1]
  );

  const _onChangeOld = useCallback(
    (oldPass: string) => setOldPass({ isOldValid: keyring.isPassValid(oldPass), oldPass }),
    []
  );

  const _doChange = useCallback(
    (): void => {
      const account = address && keyring.getPair(address);

      if (!account) {
        return;
      }

      setIsBusy(true);
      nextTick((): void => {
        try {
          if (!account.isLocked) {
            account.lock();
          }

          account.decodePkcs8(oldPass);
        } catch {
          setOldPass((state: OldPass) => ({ ...state, isOldValid: false }));
          setIsBusy(false);

          return;
        }

        try {
          keyring.encryptAccount(account, newPass1.password);
        } catch {
          setNewPass2((state: NewPass) => ({ ...state, isValid: false }));
          setIsBusy(false);

          return;
        }

        setIsBusy(false);
        onClose();
      });
    },
    [address, newPass1, oldPass, onClose]
  );

  return (
    <Modal
      className={`${className} app--accounts-Modal`}
      header={t('Change account password')}
      onClose={onClose}
      size='large'
    >
      <Modal.Content>
        <AddressRow
          isInline
          value={address}
        />
        <Modal.Columns hint={t('The existing account password as specified when this account was created or when it was last changed.')}>
          <Password
            autoFocus
            isError={!isOldValid}
            label={t('your current password')}
            onChange={_onChangeOld}
            tabIndex={1}
            value={oldPass}
          />
        </Modal.Columns>
        <Modal.Columns hint={t('This will apply to any future use of this account as stored on this browser. Ensure that you securely store this new password and that it is strong and unique to the account.')}>
          <Password
            isError={!newPass1.isValid}
            label={t('your new password')}
            onChange={_onChangeNew1}
            onEnter={_doChange}
            tabIndex={2}
            value={newPass1.password}
          />
          <Password
            isError={!newPass2.isValid}
            label={t('password (repeat)')}
            onChange={_onChangeNew2}
            onEnter={_doChange}
            tabIndex={2}
            value={newPass2.password}
          />
          <PasswordStrength value={newPass1.password} />
        </Modal.Columns>
      </Modal.Content>
      <Modal.Actions>
        <Button
          icon='sign-in-alt'
          isBusy={isBusy}
          isDisabled={!newPass1.isValid || !newPass2.isValid || !isOldValid}
          label={t('Change')}
          onClick={_doChange}
        />
      </Modal.Actions>
    </Modal>
  );
}

export default React.memo(ChangePass);