diff --git a/frontend/src/lib/components/PasswordStrength.tsx b/frontend/src/lib/components/PasswordStrength.tsx new file mode 100644 index 00000000000..fbfdbef8621 --- /dev/null +++ b/frontend/src/lib/components/PasswordStrength.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { Progress } from 'antd' +import { red, volcano, orange, yellow, green } from '@ant-design/colors' +import zxcvbn from 'zxcvbn' + +export default function PasswordStrength({ password = '' }: { password: string }): JSX.Element { + // passwordScore is 0 if no password input + // passwordScore is 20, 40, 60, 80, or 100 if password input, based on zxcvbn score (which is 0, 1, 2, 3, or 4) + const passwordScore: number = password.length && zxcvbn(password).score * 20 + 20 + + return ( + + ) +} diff --git a/frontend/src/scenes/team/Signup.js b/frontend/src/scenes/team/Signup.js index 4d44ec04206..f5a1af7cd03 100644 --- a/frontend/src/scenes/team/Signup.js +++ b/frontend/src/scenes/team/Signup.js @@ -1,10 +1,11 @@ -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useRef, useEffect, lazy, Suspense } from 'react' import { useActions, useValues } from 'kea' import { signupLogic } from './signupLogic' import hedgehogBlue from './../../../public/hedgehog-blue.png' import posthogLogo from './../../../public/posthog-icon.svg' import { Row, Space, Button, Input, Checkbox } from 'antd' import queryString from 'query-string' +const PasswordStrength = lazy(() => import('../../lib/components/PasswordStrength')) function Signup() { const [state, setState] = useState({ submitted: false }) @@ -24,7 +25,8 @@ function Signup() { const updateForm = (name, target, valueAttr = 'value') => { /* Validate password (if applicable) */ if (name === 'password') { - const valid = target[valueAttr].length >= 8 + let password = target[valueAttr] + const valid = password.length >= 8 setFormState({ ...formState, password: { ...formState.password, valid, value: target[valueAttr] } }) } else { setFormState({ ...formState, [name]: { ...formState[name], value: target[valueAttr] } }) @@ -134,7 +136,12 @@ function Signup() { disabled={accountLoading} id="signupPassword" /> - At least 8 characters. + }> + + + {!formState.password.valid && ( + Your password must have at least 8 characters. + )}
diff --git a/package.json b/package.json index 633c65db161..134883b8ba6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@mariusandra/react-grid-layout": "0.18.3", "@mariusandra/simmerjs": "0.6.1-posthog.1", "@types/react-syntax-highlighter": "^11.0.4", + "@types/zxcvbn": "^4.4.0", "antd": "^4.1.1", "babel-preset-nano-react-app": "^0.1.0", "bootstrap": "^4.4.1", @@ -61,7 +62,8 @@ "redux": "^4.0.5", "reselect": "^4.0.0", "sass": "^1.26.2", - "styled-components": "^5.0.1" + "styled-components": "^5.0.1", + "zxcvbn": "^4.4.2" }, "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.10.4", diff --git a/yarn.lock b/yarn.lock index 26703e1f0f6..56bc45561ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1162,6 +1162,11 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/zxcvbn@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.0.tgz#fbc1d941cc6d9d37d18405c513ba6b294f89b609" + integrity sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg== + "@typescript-eslint/eslint-plugin@^3.6.0": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" @@ -10267,3 +10272,8 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +zxcvbn@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" + integrity sha1-KOwXzwl0PtyrBW3dixsGJizHPDA=