mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
feat: enable rollback from argon2 to bcrypt
This commit is contained in:
@@ -92,8 +92,44 @@ if (Meteor.isServer) {
|
||||
await Meteor.users.removeAsync(userId);
|
||||
});
|
||||
|
||||
Tinytest.addAsync("passwords Argon - migration from argon2 encryption to bcrypt", async (test) => {
|
||||
Accounts._options.argon2Enabled = true;
|
||||
const username = Random.id();
|
||||
const email = `${username}@bcrypt.com`;
|
||||
const password = "password";
|
||||
const userId = await Accounts.createUser(
|
||||
{
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
}
|
||||
);
|
||||
Accounts._options.argon2Enabled = false;
|
||||
let user = await Meteor.users.findOneAsync(userId);
|
||||
const isValidArgon = await Accounts._checkPasswordAsync(user, password);
|
||||
test.equal(isValidArgon.userId, userId, "checkPassword with argon2 - User ID should be returned");
|
||||
test.equal(typeof isValidArgon.error, "undefined", "checkPassword with argon2 - No error should be returned");
|
||||
|
||||
|
||||
// wait for defered execution of user update inside _checkPasswordAsync
|
||||
await new Promise((resolve) => {
|
||||
Meteor.setTimeout(async () => {
|
||||
user = await Meteor.users.findOneAsync(userId);
|
||||
// bcrypt has been unset and argon2 set
|
||||
test.equal(typeof user.services.password.argon2, "undefined", "argon2 should be unset");
|
||||
test.equal(typeof user.services.password.bcrypt, "string", "bcrypt should be set");
|
||||
// password is still valid using argon2
|
||||
const isValidBcrypt = await Accounts._checkPasswordAsync(user, password);
|
||||
test.equal(isValidBcrypt.userId, userId, "checkPassword with argon2 - User ID should be returned");
|
||||
test.equal(typeof isValidBcrypt.error, "undefined", "checkPassword with argon2 - No error should be returned");
|
||||
resolve();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// cleanup
|
||||
await Meteor.users.removeAsync(userId);
|
||||
});
|
||||
|
||||
const getUserHashArgon2Params = function (user) {
|
||||
const hash = user?.services?.password?.argon2;
|
||||
return Accounts._getArgon2Params(hash);
|
||||
|
||||
@@ -134,10 +134,14 @@ Accounts._checkPasswordUserFields = { _id: 1, services: 1 };
|
||||
|
||||
const isBcrypt = (hash) => {
|
||||
// bcrypt hashes start with $2a$ or $2b$
|
||||
// argon2 hashes start with $argon2i$, $argon2d$ or $argon2id$
|
||||
return hash.startsWith("$2");
|
||||
};
|
||||
|
||||
const isArgon = (hash) => {
|
||||
// argon2 hashes start with $argon2i$, $argon2d$ or $argon2id$
|
||||
return hash.startsWith("$argon2");
|
||||
}
|
||||
|
||||
const updateUserPasswordDefered = (user, formattedPassword) => {
|
||||
Meteor.defer(async () => {
|
||||
await updateUserPassword(user, formattedPassword);
|
||||
@@ -155,6 +159,9 @@ const getUpdatorForUserPassword = async (formattedPassword) => {
|
||||
return {
|
||||
$set: {
|
||||
"services.password.bcrypt": encryptedPassword
|
||||
},
|
||||
$unset: {
|
||||
"services.password.argon2": 1
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -208,19 +215,34 @@ const checkPasswordAsync = async (user, password) => {
|
||||
|
||||
const argon2Enabled = Accounts._argon2Enabled();
|
||||
if (argon2Enabled === false) {
|
||||
const hashRounds = getRoundsFromBcryptHash(hash);
|
||||
const match = await bcryptCompare(formattedPassword, hash);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else if (hash) {
|
||||
const paramsChanged = hashRounds !== Accounts._bcryptRounds();
|
||||
// The password checks out, but the user's bcrypt hash needs to be updated
|
||||
// to match current bcrypt settings
|
||||
if (paramsChanged === true) {
|
||||
if (isArgon(hash)) {
|
||||
// this is a rollback feature, enabling to switch back from argon2 to bcrypt if needed
|
||||
// TODO : deprecate this
|
||||
console.warn("User has an argon2 password and argon2 is not enabled, rolling back to bcrypt encryption");
|
||||
const match = await argon2.verify(hash, formattedPassword);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else{
|
||||
// The password checks out, but the user's stored password needs to be updated to argon2
|
||||
updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" });
|
||||
}
|
||||
}
|
||||
else {
|
||||
const hashRounds = getRoundsFromBcryptHash(hash);
|
||||
const match = await bcryptCompare(formattedPassword, hash);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else if (hash) {
|
||||
const paramsChanged = hashRounds !== Accounts._bcryptRounds();
|
||||
// The password checks out, but the user's bcrypt hash needs to be updated
|
||||
// to match current bcrypt settings
|
||||
if (paramsChanged === true) {
|
||||
updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (argon2Enabled === true) {
|
||||
if (isBcrypt(hash)) {
|
||||
|
||||
Reference in New Issue
Block a user