From 0eb21919fb83371cc34dce9caad792d8c78f93ca Mon Sep 17 00:00:00 2001 From: Joel Biddle Date: Tue, 22 Aug 2023 16:49:17 +1000 Subject: [PATCH] Password breach check --- .../src/components/signup/UserInfoStep.tsx | 3 +- .../checks/checkIsPasswordBreached.ts | 51 +++++++++++++++++++ .../utilities/checks/checkPassword.ts | 17 +++++-- frontend/src/pages/signupinvite.tsx | 3 +- .../ChangePasswordSection.tsx | 5 +- .../UserInfoSSOStep/UserInfoSSOStep.tsx | 3 +- 6 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/utilities/checks/checkIsPasswordBreached.ts diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index 3123950c3..581298c42 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -46,6 +46,7 @@ type Errors = { number?: string, specialChar?: string, repeatedChar?: string, + breachedPassword?: string }; /** @@ -101,7 +102,7 @@ export default function UserInfoStep({ setOrganizationNameError(false); } - errorCheck = checkPassword({ + errorCheck = await checkPassword({ password, commonPasswords, setErrors diff --git a/frontend/src/components/utilities/checks/checkIsPasswordBreached.ts b/frontend/src/components/utilities/checks/checkIsPasswordBreached.ts new file mode 100644 index 000000000..8852ada2f --- /dev/null +++ b/frontend/src/components/utilities/checks/checkIsPasswordBreached.ts @@ -0,0 +1,51 @@ +import axios from "axios"; +import crypto from "crypto"; + +///// REMINDER: ensure all logs are deleted!!! ///// + +export const checkIsPasswordBreached = async (password: string) => { + const dataBreachCheckAPIBaseURL = "https://api.pwnedpasswords.com/range/"; + try { + const textEncoder = new TextEncoder(); + + const encodedPwd = textEncoder.encode(password); + console.log("encodedPwd:", encodedPwd); // delete later!!! + + const hashBuffer = await crypto.subtle.digest("SHA-1", encodedPwd); + console.log("hashBuffer:", hashBuffer); // delete later!!! + + const hashedPwd = Array.from(new Uint8Array(hashBuffer)) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") + .toUpperCase(); + + console.log("hashedPwd:", hashedPwd); // delete later!!! + + const response = await axios.get( + `${dataBreachCheckAPIBaseURL}${hashedPwd.slice(0, 5)}` + ); + console.log("response:", response); // delete later!!! + + const responseData = response.data.toUpperCase(); + console.log("responseData:", responseData); // delete later!!! + + const isBreachedPassword = responseData.includes(hashedPwd.slice(5, 40)); + console.log("isBreachedPassword:", isBreachedPassword); // delete later!!! + + // Clear the hashed password from memory + crypto.subtle.digest("SHA-1", encodedPwd); + + return isBreachedPassword; + } catch (err: any) { + if ( + axios.isAxiosError(err) && + err.response && + err.response.status === 429 + ) { + console.error("Received a 429 response from the Pwnd Passwords API"); + // Handle the 429 error here + } else { + console.error(err); + } + } +}; diff --git a/frontend/src/components/utilities/checks/checkPassword.ts b/frontend/src/components/utilities/checks/checkPassword.ts index 7f8b5d45f..0ef1e949b 100644 --- a/frontend/src/components/utilities/checks/checkPassword.ts +++ b/frontend/src/components/utilities/checks/checkPassword.ts @@ -1,3 +1,5 @@ +import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; + type Errors = { tooShort?: string, tooLong?: string, @@ -6,7 +8,8 @@ type Errors = { number?: string, specialChar?: string, repeatedChar?: string, - commonPassword?: string + commonPassword?: string, + breachedPassword?: string }; interface CheckPasswordParams { @@ -32,13 +35,15 @@ interface CheckPasswordParams { * @param {String} obj.password - the password to check * @param {Function} obj.setErrors - set state function to set error object */ -const checkPassword = ({ +const checkPassword = async ({ password, commonPasswords, setErrors -}: CheckPasswordParams): boolean => { +}: CheckPasswordParams): Promise => { const errors: Errors = {}; - + + const isBreachedPassword = await checkIsPasswordBreached(password) + if (password.length < 14) { errors.tooShort = "at least 14 characters"; } @@ -70,6 +75,10 @@ const checkPassword = ({ if (commonPasswords.includes(password)) { errors.commonPassword = "No common passwords"; } + + if (isBreachedPassword) { + errors.breachedPassword = "The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password."; + } setErrors(errors); return Object.keys(errors).length > 0; diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 9b1fe990f..894c36ea6 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -41,6 +41,7 @@ type Errors = { number?: string, specialChar?: string, repeatedChar?: string, + breachedPassword?: string }; export default function SignupInvite() { @@ -80,7 +81,7 @@ export default function SignupInvite() { setLastNameError(false); } - errorCheck = checkPassword({ + errorCheck = await checkPassword({ password, commonPasswords, setErrors diff --git a/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx index c873395c4..e17864485 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx @@ -25,6 +25,7 @@ type Errors = { number?: string, specialChar?: string, repeatedChar?: string, + breachedPassword?: string }; const schema = yup.object({ @@ -53,8 +54,8 @@ export const ChangePasswordSection = () => { try { if (!user?.email) return; if (!commonPasswords) return; - - const errorCheck = checkPassword({ + + const errorCheck = await checkPassword({ password: newPassword, commonPasswords, setErrors diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index d5703c361..c44fab790 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -42,6 +42,7 @@ type Errors = { number?: string, specialChar?: string, repeatedChar?: string, + breachedPassword?: string }; /** @@ -99,7 +100,7 @@ export const UserInfoSSOStep = ({ setOrganizationNameError(false); } - errorCheck = checkPassword({ + errorCheck = await checkPassword({ password, commonPasswords, setErrors