import {Fragment} from 'react';
import {render} from 'react-dom';
import {css} from '@emotion/react';
import styled from '@emotion/styled';
import throttle from 'lodash/throttle';
import zxcvbn from 'zxcvbn';
import {tct} from 'sentry/locale';
import space from 'sentry/styles/space';
import theme from 'sentry/utils/theme';
/**
* NOTE: Do not import this component synchronously. The zxcvbn library is
* relatively large. This component should be loaded async as a split chunk.
*/
/**
* The maximum score that zxcvbn reports
*/
const MAX_SCORE = 5;
type Props = {
/**
* The password value.
*/
value: string;
/**
* The color to make the progress bar for each strength level. 5 levels.
*/
colors?: [string, string, string, string, string];
/**
* A set of labels to display for each password strength level. 5 levels.
*/
labels?: [string, string, string, string, string];
};
const PasswordStrength = ({
value,
labels = ['Very Weak', 'Very Weak', 'Weak', 'Strong', 'Very Strong'],
colors = [theme.red300, theme.red300, theme.yellow300, theme.green300, theme.green300],
}: Props) => {
if (value === '') {
return null;
}
const result = zxcvbn(value);
if (!result) {
return null;
}
const {score} = result;
const percent = Math.round(((score + 1) / MAX_SCORE) * 100);
const styles = css`
background: ${colors[score]};
width: ${percent}%;
`;
return (
{tct('Strength: [textScore]', {
textScore: {labels[score]},
})}
);
};
const StrengthProgress = styled('div')`
background: ${theme.gray200};
height: 8px;
border-radius: 2px;
overflow: hidden;
`;
const StrengthProgressBar = styled('div')`
height: 100%;
`;
const StrengthLabel = styled('div')`
font-size: 0.8em;
margin-top: ${space(0.25)};
color: ${theme.gray400};
`;
const ScoreText = styled('strong')`
color: ${p => p.theme.black};
`;
export default PasswordStrength;
/**
* This is a shim that allows the password strength component to be used
* outside of our main react application. Mostly useful since all of our
* registration pages aren't in the react app.
*/
export const attachTo = ({input, element}) =>
element &&
input &&
input.addEventListener(
'input',
throttle(e => {
render(, element);
})
);