passwordStrength.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {Fragment} from 'react';
  2. import {render} from 'react-dom';
  3. import {css} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import throttle from 'lodash/throttle';
  6. import zxcvbn from 'zxcvbn';
  7. import {tct} from 'sentry/locale';
  8. import space from 'sentry/styles/space';
  9. import theme from 'sentry/utils/theme';
  10. /**
  11. * NOTE: Do not import this component synchronously. The zxcvbn library is
  12. * relatively large. This component should be loaded async as a split chunk.
  13. */
  14. /**
  15. * The maximum score that zxcvbn reports
  16. */
  17. const MAX_SCORE = 5;
  18. type Props = {
  19. /**
  20. * The password value.
  21. */
  22. value: string;
  23. /**
  24. * The color to make the progress bar for each strength level. 5 levels.
  25. */
  26. colors?: [string, string, string, string, string];
  27. /**
  28. * A set of labels to display for each password strength level. 5 levels.
  29. */
  30. labels?: [string, string, string, string, string];
  31. };
  32. const PasswordStrength = ({
  33. value,
  34. labels = ['Very Weak', 'Very Weak', 'Weak', 'Strong', 'Very Strong'],
  35. colors = [theme.red300, theme.red300, theme.yellow300, theme.green300, theme.green300],
  36. }: Props) => {
  37. if (value === '') {
  38. return null;
  39. }
  40. const result = zxcvbn(value);
  41. if (!result) {
  42. return null;
  43. }
  44. const {score} = result;
  45. const percent = Math.round(((score + 1) / MAX_SCORE) * 100);
  46. const styles = css`
  47. background: ${colors[score]};
  48. width: ${percent}%;
  49. `;
  50. return (
  51. <Fragment>
  52. <StrengthProgress
  53. role="progressbar"
  54. aria-valuenow={score}
  55. aria-valuemin={0}
  56. aria-valuemax={100}
  57. >
  58. <StrengthProgressBar css={styles} />
  59. </StrengthProgress>
  60. <StrengthLabel>
  61. {tct('Strength: [textScore]', {
  62. textScore: <ScoreText>{labels[score]}</ScoreText>,
  63. })}
  64. </StrengthLabel>
  65. </Fragment>
  66. );
  67. };
  68. const StrengthProgress = styled('div')`
  69. background: ${theme.gray200};
  70. height: 8px;
  71. border-radius: 2px;
  72. overflow: hidden;
  73. `;
  74. const StrengthProgressBar = styled('div')`
  75. height: 100%;
  76. `;
  77. const StrengthLabel = styled('div')`
  78. font-size: 0.8em;
  79. margin-top: ${space(0.25)};
  80. color: ${theme.gray400};
  81. `;
  82. const ScoreText = styled('strong')`
  83. color: ${p => p.theme.black};
  84. `;
  85. export default PasswordStrength;
  86. /**
  87. * This is a shim that allows the password strength component to be used
  88. * outside of our main react application. Mostly useful since all of our
  89. * registration pages aren't in the react app.
  90. */
  91. export const attachTo = ({input, element}) =>
  92. element &&
  93. input &&
  94. input.addEventListener(
  95. 'input',
  96. throttle(e => {
  97. render(<PasswordStrength value={e.target.value} />, element);
  98. })
  99. );