mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Fix flaky regex g flag causing unexpected validation password validation issue
This commit is contained in:
@@ -269,11 +269,11 @@ export default function UserInfoStep({
|
||||
<InputField
|
||||
label={t("section.password.password")}
|
||||
onChangeHandler={async (pass: string) => {
|
||||
setPassword(pass);
|
||||
await checkPassword({
|
||||
password: pass,
|
||||
setErrors
|
||||
});
|
||||
setPassword(pass);
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import axios from "axios";
|
||||
|
||||
// SHA-1 hash the password using the SubtleCrypto API
|
||||
async function hashPassword(passwordBytes: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const buffer = await window.crypto.subtle.digest("SHA-1", passwordBytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Convert the hashed password buffer to a hexadecimal string
|
||||
function bufferToHex(buffer: ArrayBuffer): string {
|
||||
const byteArray = new Uint8Array(buffer);
|
||||
const hexParts: string[] = [];
|
||||
byteArray.forEach((byte) => {
|
||||
const hex = byte.toString(16).padStart(2, "0");
|
||||
hexParts.push(hex);
|
||||
});
|
||||
return hexParts.join("");
|
||||
}
|
||||
|
||||
// see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
|
||||
// in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table
|
||||
// this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results
|
||||
@@ -21,7 +38,7 @@ import axios from "axios";
|
||||
// thereof."
|
||||
|
||||
export const checkIsPasswordBreached = async (password: string): Promise<boolean> => {
|
||||
const dataBreachCheckAPIBaseURL = "https://api.pwnedpasswords.com/range/";
|
||||
const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com";
|
||||
const maxRetryAttempts = 3;
|
||||
|
||||
let encodedPwd: Uint8Array | undefined;
|
||||
@@ -32,34 +49,18 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
const textEncoder = new TextEncoder();
|
||||
encodedPwd = textEncoder.encode(password);
|
||||
|
||||
// SHA-1 hash the password using the SubtleCrypto API
|
||||
async function hashPassword(passwordBytes: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const buffer = await crypto.subtle.digest("SHA-1", passwordBytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Convert the hashed password buffer to a hexadecimal string
|
||||
function bufferToHex(buffer: ArrayBuffer): string {
|
||||
const byteArray = new Uint8Array(buffer);
|
||||
const hexParts: string[] = [];
|
||||
byteArray.forEach((byte) => {
|
||||
const hex = byte.toString(16).padStart(2, "0");
|
||||
hexParts.push(hex);
|
||||
});
|
||||
return hexParts.join("");
|
||||
}
|
||||
|
||||
// Hash the password and convert it to a useful format for the HIBP API
|
||||
hashedPwdBuffer = await hashPassword(encodedPwd!.buffer);
|
||||
const hashedPwd = bufferToHex(hashedPwdBuffer).toUpperCase();
|
||||
// ONLY send the first 5 hash chars (over HTTPS)
|
||||
const hashedPwdToSend = hashedPwd.slice(0, 5);
|
||||
const safeHashedPwdToSend = encodeURIComponent(hashedPwdToSend); // Ensure URL safety
|
||||
const rangedHashTableUri = `${dataBreachCheckAPIBaseURL}${safeHashedPwdToSend}`;
|
||||
const rangedHashTableUri = `${HAVE_I_BEEN_PWNED_API_URL}/range/${safeHashedPwdToSend}`;
|
||||
|
||||
let response;
|
||||
let retryAttempt = 0;
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (retryAttempt < maxRetryAttempts) {
|
||||
try {
|
||||
response = await axios.get(rangedHashTableUri, {
|
||||
@@ -75,14 +76,14 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
// check the last 35 hash chars to see if there's a match
|
||||
const isBreachedPassword: boolean = responseData.includes(hashedPwd.slice(5, 40));
|
||||
return isBreachedPassword;
|
||||
} else {
|
||||
retryAttempt++;
|
||||
}
|
||||
}
|
||||
retryAttempt += 1;
|
||||
|
||||
} catch (err) {
|
||||
if (!axios.isAxiosError(err)) {
|
||||
throw err;
|
||||
}
|
||||
retryAttempt++;
|
||||
retryAttempt += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// This regex covers letters (case insensitive) for the top 50 most spoken languages
|
||||
/* eslint-disable no-misleading-character-class */
|
||||
export const letterCharRegex = /[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
|
||||
|
||||
// This regex covers digits, special characters, symbols, and emojis.
|
||||
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/gu;
|
||||
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u;
|
||||
|
||||
// This regex covers 3 repeated consecutive chars (incl. spaces)
|
||||
export const repeatedCharRegex = /(.)\1\1\1|\s{4,}/;
|
||||
@@ -19,7 +20,7 @@ export const lowEntropyRegexes = [
|
||||
/^(?:(?:https?|ftp):\/\/)?(?:\w+\.)?[a-zA-Z0-9.-]+\.(?:com|org|net|edu)(?:\/\S*)?(?:\?\S*)?$/,
|
||||
|
||||
// Date in various formats
|
||||
/(\b\d{1,4}[-\/.]?\d{1,2}[-\/.]?\d{1,4}\b)|(\b\d{1,4}[-\/.]?\w{3}[-\/.]?\d{1,4}\b)/,
|
||||
/(\b\d{1,4}[-/.]?\d{1,2}[-/.]?\d{1,4}\b)|(\b\d{1,4}[-/.]?\w{3}[-/.]?\d{1,4}\b)/,
|
||||
|
||||
// Phone numbers (generalized)
|
||||
/(?:\+(?:[1-9]\d{0,2})\s?)?(?:\(\d{1,4}\)\s?)?(?:\d[-.\s]?){5,}\d/,
|
||||
|
||||
Reference in New Issue
Block a user