mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 02:26:19 +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
|
<InputField
|
||||||
label={t("section.password.password")}
|
label={t("section.password.password")}
|
||||||
onChangeHandler={async (pass: string) => {
|
onChangeHandler={async (pass: string) => {
|
||||||
setPassword(pass);
|
|
||||||
await checkPassword({
|
await checkPassword({
|
||||||
password: pass,
|
password: pass,
|
||||||
setErrors
|
setErrors
|
||||||
});
|
});
|
||||||
|
setPassword(pass);
|
||||||
}}
|
}}
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
import axios from "axios";
|
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
|
// 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
|
// 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
|
// 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."
|
// thereof."
|
||||||
|
|
||||||
export const checkIsPasswordBreached = async (password: string): Promise<boolean> => {
|
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;
|
const maxRetryAttempts = 3;
|
||||||
|
|
||||||
let encodedPwd: Uint8Array | undefined;
|
let encodedPwd: Uint8Array | undefined;
|
||||||
@@ -32,34 +49,18 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
|||||||
const textEncoder = new TextEncoder();
|
const textEncoder = new TextEncoder();
|
||||||
encodedPwd = textEncoder.encode(password);
|
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
|
// Hash the password and convert it to a useful format for the HIBP API
|
||||||
hashedPwdBuffer = await hashPassword(encodedPwd!.buffer);
|
hashedPwdBuffer = await hashPassword(encodedPwd!.buffer);
|
||||||
const hashedPwd = bufferToHex(hashedPwdBuffer).toUpperCase();
|
const hashedPwd = bufferToHex(hashedPwdBuffer).toUpperCase();
|
||||||
// ONLY send the first 5 hash chars (over HTTPS)
|
// ONLY send the first 5 hash chars (over HTTPS)
|
||||||
const hashedPwdToSend = hashedPwd.slice(0, 5);
|
const hashedPwdToSend = hashedPwd.slice(0, 5);
|
||||||
const safeHashedPwdToSend = encodeURIComponent(hashedPwdToSend); // Ensure URL safety
|
const safeHashedPwdToSend = encodeURIComponent(hashedPwdToSend); // Ensure URL safety
|
||||||
const rangedHashTableUri = `${dataBreachCheckAPIBaseURL}${safeHashedPwdToSend}`;
|
const rangedHashTableUri = `${HAVE_I_BEEN_PWNED_API_URL}/range/${safeHashedPwdToSend}`;
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
let retryAttempt = 0;
|
let retryAttempt = 0;
|
||||||
|
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
while (retryAttempt < maxRetryAttempts) {
|
while (retryAttempt < maxRetryAttempts) {
|
||||||
try {
|
try {
|
||||||
response = await axios.get(rangedHashTableUri, {
|
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
|
// check the last 35 hash chars to see if there's a match
|
||||||
const isBreachedPassword: boolean = responseData.includes(hashedPwd.slice(5, 40));
|
const isBreachedPassword: boolean = responseData.includes(hashedPwd.slice(5, 40));
|
||||||
return isBreachedPassword;
|
return isBreachedPassword;
|
||||||
} else {
|
}
|
||||||
retryAttempt++;
|
retryAttempt += 1;
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!axios.isAxiosError(err)) {
|
if (!axios.isAxiosError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
retryAttempt++;
|
retryAttempt += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// This regex covers letters (case insensitive) for the top 50 most spoken languages
|
// 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;
|
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.
|
// 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)
|
// This regex covers 3 repeated consecutive chars (incl. spaces)
|
||||||
export const repeatedCharRegex = /(.)\1\1\1|\s{4,}/;
|
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*)?$/,
|
/^(?:(?:https?|ftp):\/\/)?(?:\w+\.)?[a-zA-Z0-9.-]+\.(?:com|org|net|edu)(?:\/\S*)?(?:\?\S*)?$/,
|
||||||
|
|
||||||
// Date in various formats
|
// 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)
|
// Phone numbers (generalized)
|
||||||
/(?:\+(?:[1-9]\d{0,2})\s?)?(?:\(\d{1,4}\)\s?)?(?:\d[-.\s]?){5,}\d/,
|
/(?:\+(?:[1-9]\d{0,2})\s?)?(?:\(\d{1,4}\)\s?)?(?:\d[-.\s]?){5,}\d/,
|
||||||
|
|||||||
Reference in New Issue
Block a user