mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 20:25:43 +00:00
Fix: Format entire frontend properly
This commit is contained in:
@@ -1,17 +1,11 @@
|
||||
import {
|
||||
getAuthToken,
|
||||
setAuthToken,
|
||||
setMfaTempToken,
|
||||
setSignupTempToken} from "@app/reactQuery";
|
||||
|
||||
import { getAuthToken, setAuthToken, setMfaTempToken, setSignupTempToken } from "@app/reactQuery";
|
||||
|
||||
export const PROVIDER_AUTH_TOKEN_KEY = "infisical__provider-auth-token";
|
||||
|
||||
// depreciated: go for apiRequest module in config/api
|
||||
export default class SecurityClient {
|
||||
|
||||
static setProviderAuthToken(tokenStr: string) {
|
||||
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "")
|
||||
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "");
|
||||
}
|
||||
|
||||
static getProviderAuthToken() {
|
||||
|
||||
@@ -3,9 +3,7 @@ import crypto from "crypto";
|
||||
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import {
|
||||
changePassword,
|
||||
srp1} from "@app/hooks/api/auth/queries";
|
||||
import { changePassword, srp1 } from "@app/hooks/api/auth/queries";
|
||||
|
||||
import Aes256Gcm from "./cryptography/aes-256-gcm";
|
||||
import { deriveArgonKey } from "./cryptography/crypto";
|
||||
@@ -15,95 +13,95 @@ const clientOldPassword = new jsrp.client();
|
||||
const clientNewPassword = new jsrp.client();
|
||||
|
||||
type Params = {
|
||||
email: string;
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}
|
||||
email: string;
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
};
|
||||
|
||||
const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
|
||||
let serverPublicKey; let salt;
|
||||
return new Promise((resolve, reject) => {
|
||||
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
|
||||
let serverPublicKey;
|
||||
let salt;
|
||||
|
||||
try {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
|
||||
const res = await srp1({ clientPublicKey });
|
||||
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
|
||||
clientOldPassword.setSalt(salt);
|
||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||
|
||||
const clientProof = clientOldPassword.getProof();
|
||||
|
||||
clientNewPassword.init({ username: email, password: newPassword }, async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
try {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password: newPassword,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
const res = await srp1({ clientPublicKey });
|
||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
clientOldPassword.setSalt(salt);
|
||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: localStorage.getItem("PRIVATE_KEY") as string,
|
||||
secret: key
|
||||
});
|
||||
|
||||
const clientProof = clientOldPassword.getProof();
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString("hex"),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
clientNewPassword.init({ username: email, password: newPassword }, async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
try {
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password: newPassword,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
await changePassword({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||
saveTokenToLocalStorage({
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
});
|
||||
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: localStorage.getItem("PRIVATE_KEY") as string,
|
||||
secret: key
|
||||
});
|
||||
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString("hex"),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
await changePassword({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (err2) {
|
||||
console.error(err2);
|
||||
reject(err2);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
resolve();
|
||||
} catch (err2) {
|
||||
console.error(err2);
|
||||
reject(err2);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptChangePassword;
|
||||
export default attemptChangePassword;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
@@ -11,11 +11,11 @@ import SecurityClient from "./SecurityClient";
|
||||
const client = new jsrp.client();
|
||||
|
||||
interface IsMfaLoginSuccessful {
|
||||
success: boolean;
|
||||
loginResponse:{
|
||||
privateKey: string;
|
||||
JTWToken: string;
|
||||
}
|
||||
success: boolean;
|
||||
loginResponse: {
|
||||
privateKey: string;
|
||||
JTWToken: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,81 +26,84 @@ interface IsMfaLoginSuccessful {
|
||||
* @param {String} obj.mfaToken - MFA code/token
|
||||
*/
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string,
|
||||
mfaToken: string;
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
mfaToken: string;
|
||||
}): Promise<IsMfaLoginSuccessful> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init({
|
||||
username: email,
|
||||
password
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken
|
||||
});
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
loginResponse:{
|
||||
privateKey,
|
||||
JTWToken: token
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
resolve({
|
||||
success: true,
|
||||
loginResponse: {
|
||||
privateKey,
|
||||
JTWToken: token
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLoginMfa;
|
||||
export default attemptLoginMfa;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
@@ -18,75 +18,78 @@ const client = new jsrp.client();
|
||||
* @param {String} obj.mfaToken - MFA code/token
|
||||
*/
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string,
|
||||
mfaToken: string;
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
mfaToken: string;
|
||||
}): Promise<Boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init({
|
||||
username: email,
|
||||
password
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken,
|
||||
});
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken
|
||||
});
|
||||
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
export default attemptLoginMfa;
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLoginMfa;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
||||
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes";
|
||||
import {
|
||||
escapeCharRegex,
|
||||
letterCharRegex,
|
||||
lowEntropyRegexes,
|
||||
numAndSpecialCharRegex,
|
||||
repeatedCharRegex
|
||||
} from "./passwordRegexes";
|
||||
|
||||
interface PasswordCheckProps {
|
||||
password: string;
|
||||
@@ -29,40 +35,38 @@ const passwordCheck = async ({
|
||||
{
|
||||
name: "tooShort",
|
||||
validator: (pwd: string) => pwd.length >= 14,
|
||||
setError: setPasswordErrorTooShort,
|
||||
setError: setPasswordErrorTooShort
|
||||
},
|
||||
{
|
||||
name: "tooLong",
|
||||
validator: (pwd: string) => pwd.length < 101,
|
||||
setError: setPasswordErrorTooLong,
|
||||
setError: setPasswordErrorTooLong
|
||||
},
|
||||
{
|
||||
name: "noLetterChar",
|
||||
validator: (pwd: string) => letterCharRegex.test(pwd),
|
||||
setError: setPasswordErrorNoLetterChar,
|
||||
setError: setPasswordErrorNoLetterChar
|
||||
},
|
||||
{
|
||||
name: "noNumOrSpecialChar",
|
||||
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
|
||||
setError: setPasswordErrorNoNumOrSpecialChar,
|
||||
setError: setPasswordErrorNoNumOrSpecialChar
|
||||
},
|
||||
{
|
||||
name: "repeatedChar",
|
||||
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
|
||||
setError: setPasswordErrorRepeatedChar,
|
||||
setError: setPasswordErrorRepeatedChar
|
||||
},
|
||||
{
|
||||
name: "escapeChar",
|
||||
validator: (pwd: string) => !escapeCharRegex.test(pwd),
|
||||
setError: setPasswordErrorEscapeChar,
|
||||
setError: setPasswordErrorEscapeChar
|
||||
},
|
||||
{
|
||||
name: "lowEntropy",
|
||||
validator: (pwd: string) => (
|
||||
!lowEntropyRegexes.some(regex => regex.test(pwd))
|
||||
),
|
||||
setError: setPasswordErrorLowEntropy,
|
||||
},
|
||||
validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
|
||||
setError: setPasswordErrorLowEntropy
|
||||
}
|
||||
];
|
||||
|
||||
const isBreached = await checkIsPasswordBreached(password);
|
||||
@@ -73,7 +77,7 @@ const passwordCheck = async ({
|
||||
} else {
|
||||
setPasswordErrorBreached(false);
|
||||
}
|
||||
|
||||
|
||||
tests.forEach((test) => {
|
||||
if (!test.validator(password)) {
|
||||
errorCheck = true;
|
||||
@@ -81,7 +85,7 @@ const passwordCheck = async ({
|
||||
} else {
|
||||
test.setError(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return errorCheck;
|
||||
};
|
||||
|
||||
@@ -17,25 +17,25 @@ function bufferToHex(buffer: ArrayBuffer): string {
|
||||
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
|
||||
// returns a hash table of 800-1000 results
|
||||
// padding has been added to prevent MitM attacker determining which hash table was called by the response size
|
||||
// the last 35 chars of the password hash are compared client-side against the table
|
||||
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
|
||||
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
|
||||
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
|
||||
// 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
|
||||
// returns a hash table of 800-1000 results
|
||||
// padding has been added to prevent MitM attacker determining which hash table was called by the response size
|
||||
// the last 35 chars of the password hash are compared client-side against the table
|
||||
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
|
||||
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
|
||||
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
|
||||
|
||||
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
|
||||
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare
|
||||
// the prospective secrets against a list that contains values known to be commonly-used, expected,
|
||||
// or compromised. For example, the list MAY include, but is not limited to:
|
||||
// • Passwords obtained from previous breach corpuses.
|
||||
// • Dictionary words.
|
||||
// • Repetitive or sequential characters (e.g. ‘aaaaaa’, ‘1234abcd’).
|
||||
// • Context-specific words, such as the name of the service, the username, and derivatives
|
||||
// thereof."
|
||||
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
|
||||
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare
|
||||
// the prospective secrets against a list that contains values known to be commonly-used, expected,
|
||||
// or compromised. For example, the list MAY include, but is not limited to:
|
||||
// • Passwords obtained from previous breach corpuses.
|
||||
// • Dictionary words.
|
||||
// • Repetitive or sequential characters (e.g. ‘aaaaaa’, ‘1234abcd’).
|
||||
// • Context-specific words, such as the name of the service, the username, and derivatives
|
||||
// thereof."
|
||||
|
||||
export const checkIsPasswordBreached = async (password: string): Promise<boolean> => {
|
||||
const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com";
|
||||
@@ -66,8 +66,8 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
response = await axios.get(rangedHashTableUri, {
|
||||
headers: {
|
||||
"Add-Padding": "true", // see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
@@ -76,9 +76,8 @@ 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;
|
||||
}
|
||||
retryAttempt += 1;
|
||||
|
||||
}
|
||||
retryAttempt += 1;
|
||||
} catch (err) {
|
||||
if (!axios.isAxiosError(err)) {
|
||||
throw err;
|
||||
@@ -88,14 +87,15 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
}
|
||||
|
||||
console.error(
|
||||
`Received a non-200 response (${response ? response.status : "unknown"}) from the Pwnd Passwords API`
|
||||
`Received a non-200 response (${
|
||||
response ? response.status : "unknown"
|
||||
}) from the Pwnd Passwords API`
|
||||
);
|
||||
return false;
|
||||
} catch (err: any) {
|
||||
console.error("An unexpected error has occurred:", err.message);
|
||||
return false;
|
||||
} finally {
|
||||
|
||||
// Clear the UTF-8 encoded password from memory
|
||||
|
||||
if (encodedPwd) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
||||
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes";
|
||||
import {
|
||||
escapeCharRegex,
|
||||
letterCharRegex,
|
||||
lowEntropyRegexes,
|
||||
numAndSpecialCharRegex,
|
||||
repeatedCharRegex
|
||||
} from "./passwordRegexes";
|
||||
|
||||
type Errors = {
|
||||
tooShort?: string;
|
||||
@@ -44,40 +50,38 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
|
||||
{
|
||||
name: "tooShort",
|
||||
validator: (pwd: string) => pwd.length >= 14,
|
||||
errorText: "at least 14 characters",
|
||||
errorText: "at least 14 characters"
|
||||
},
|
||||
{
|
||||
name: "tooLong",
|
||||
validator: (pwd: string) => pwd.length < 101,
|
||||
errorText: "at most 100 characters",
|
||||
errorText: "at most 100 characters"
|
||||
},
|
||||
{
|
||||
name: "noLetterChar",
|
||||
validator: (pwd: string) => letterCharRegex.test(pwd),
|
||||
errorText: "at least 1 letter character",
|
||||
errorText: "at least 1 letter character"
|
||||
},
|
||||
{
|
||||
name: "noNumOrSpecialChar",
|
||||
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
|
||||
errorText: "at least 1 number or special character",
|
||||
errorText: "at least 1 number or special character"
|
||||
},
|
||||
{
|
||||
name: "repeatedChar",
|
||||
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
|
||||
errorText: "at most 3 repeated, consecutive characters",
|
||||
errorText: "at most 3 repeated, consecutive characters"
|
||||
},
|
||||
{
|
||||
name: "escapeChar",
|
||||
validator: (pwd: string) => !escapeCharRegex.test(pwd),
|
||||
errorText: "No escape characters allowed.",
|
||||
errorText: "No escape characters allowed."
|
||||
},
|
||||
{
|
||||
name: "lowEntropy",
|
||||
validator: (pwd: string) => (
|
||||
!lowEntropyRegexes.some(regex => regex.test(pwd))
|
||||
),
|
||||
errorText: "Password contains personal info.",
|
||||
},
|
||||
validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
|
||||
errorText: "Password contains personal info."
|
||||
}
|
||||
];
|
||||
|
||||
const isBreached = await checkIsPasswordBreached(password);
|
||||
@@ -96,4 +100,4 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
|
||||
return Object.keys(errors).length > 0;
|
||||
};
|
||||
|
||||
export default checkPassword;
|
||||
export default checkPassword;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.
|
||||
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u;
|
||||
@@ -32,5 +33,5 @@ export const lowEntropyRegexes = [
|
||||
/\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/,
|
||||
|
||||
// US social security number
|
||||
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/,
|
||||
];
|
||||
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/
|
||||
];
|
||||
|
||||
@@ -3,9 +3,7 @@ import crypto from "crypto";
|
||||
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { issueBackupPrivateKey ,
|
||||
srp1
|
||||
} from "@app/hooks/api/auth/queries";
|
||||
import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
|
||||
|
||||
import generateBackupPDF from "../generateBackupPDF";
|
||||
import Aes256Gcm from "./aes-256-gcm";
|
||||
@@ -97,7 +95,6 @@ const issueBackupKey = async ({
|
||||
generatedKey
|
||||
});
|
||||
setBackupKeyIssued(true);
|
||||
|
||||
} catch {
|
||||
setBackupKeyError(true);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { useUser } from "@app/context";
|
||||
|
||||
import {
|
||||
boot as bootIntercom,
|
||||
load as loadIntercom,
|
||||
update as updateIntercom,
|
||||
} from "./intercom";
|
||||
import { boot as bootIntercom, load as loadIntercom, update as updateIntercom } from "./intercom";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const IntercomProvider = ({ children }: { children: any }) => {
|
||||
@@ -16,7 +12,11 @@ export const IntercomProvider = ({ children }: { children: any }) => {
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
loadIntercom();
|
||||
bootIntercom({name: `${user?.firstName || ""} ${user?.lastName || ""}`, email: user?.email || "", created_at: Math.floor(((new Date(user?.createdAt))?.getTime() || 0) / 1000)});
|
||||
bootIntercom({
|
||||
name: `${user?.firstName || ""} ${user?.lastName || ""}`,
|
||||
email: user?.email || "",
|
||||
created_at: Math.floor((new Date(user?.createdAt)?.getTime() || 0) / 1000)
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -37,4 +37,4 @@ export const IntercomProvider = ({ children }: { children: any }) => {
|
||||
}, [router.events]);
|
||||
|
||||
return children;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const isValidHexColor = (hexColor: string) => {
|
||||
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
|
||||
|
||||
return hexColorPattern.test(hexColor);
|
||||
}
|
||||
export const isValidHexColor = (hexColor: string) => {
|
||||
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
|
||||
|
||||
return hexColorPattern.test(hexColor);
|
||||
};
|
||||
|
||||
@@ -17,10 +17,9 @@ export const saveTokenToLocalStorage = ({
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey,
|
||||
privateKey
|
||||
}: Props) => {
|
||||
try {
|
||||
|
||||
if (protectedKey) {
|
||||
localStorage.removeItem("protectedKey");
|
||||
localStorage.setItem("protectedKey", protectedKey);
|
||||
@@ -62,9 +61,7 @@ export const saveTokenToLocalStorage = ({
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(
|
||||
`Unable to send the tokens in local storage:${ err.message}`
|
||||
);
|
||||
throw new Error(`Unable to send the tokens in local storage:${err.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user