mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Upgrade from SRP to bcrypt on password change
This commit is contained in:
@@ -34,16 +34,11 @@ Meteor.loginWithPassword = function (selector, password, callback) {
|
||||
// the password without requiring a full SRP flow, as well as
|
||||
// SHA256(password), which the server bcrypts and stores in
|
||||
// place of the old SRP information for this user.
|
||||
var details;
|
||||
try {
|
||||
details = EJSON.parse(error.details);
|
||||
} catch (e) {}
|
||||
if (!(details && details.format === 'srp'))
|
||||
callback(new Meteor.Error(400,
|
||||
"Password is old. Please reset your " +
|
||||
"password."));
|
||||
else
|
||||
srpUpgradePath(selector, password, details.identity, callback);
|
||||
srpUpgradePath({
|
||||
upgradeError: error,
|
||||
userSelector: selector,
|
||||
plaintextPassword: password
|
||||
}, callback);
|
||||
}
|
||||
else if (error) {
|
||||
callback(error);
|
||||
@@ -61,18 +56,32 @@ var hashPassword = function (password) {
|
||||
};
|
||||
};
|
||||
|
||||
// XXX COMPAT WITH 0.8.1.3
|
||||
// The server requested an upgrade from the old SRP password format,
|
||||
// so supply the needed SRP identity to login.
|
||||
var srpUpgradePath = function (selector, plaintextPassword,
|
||||
identity, callback) {
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [{
|
||||
user: selector,
|
||||
srp: SHA256(identity + ":" + plaintextPassword),
|
||||
password: hashPassword(plaintextPassword)
|
||||
}],
|
||||
userCallback: callback
|
||||
});
|
||||
// so supply the needed SRP identity to login. Options:
|
||||
// - upgradeError: the error object that the server returned to tell
|
||||
// us to upgrade from SRP to bcrypt.
|
||||
// - userSelector: selector to retrieve the user object
|
||||
// - plaintextPassword: the password as a string
|
||||
var srpUpgradePath = function (options, callback) {
|
||||
var details;
|
||||
try {
|
||||
details = EJSON.parse(options.upgradeError.details);
|
||||
} catch (e) {}
|
||||
if (!(details && details.format === 'srp')) {
|
||||
callback(new Meteor.Error(400,
|
||||
"Password is old. Please reset your " +
|
||||
"password."));
|
||||
} else {
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [{
|
||||
user: options.userSelector,
|
||||
srp: SHA256(details.identity + ":" + options.plaintextPassword),
|
||||
password: hashPassword(options.plaintextPassword)
|
||||
}],
|
||||
userCallback: callback
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -113,8 +122,39 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) {
|
||||
[oldPassword ? hashPassword(oldPassword) : null, hashPassword(newPassword)],
|
||||
function (error, result) {
|
||||
if (error || !result) {
|
||||
callback && callback(
|
||||
error || new Error("No result from changePassword."));
|
||||
if (error && error.error === 400 &&
|
||||
error.reason === 'old password format') {
|
||||
// XXX COMPAT WITH 0.8.1.3
|
||||
// The server is telling us to upgrade from SRP to bcrypt, as
|
||||
// in Meteor.loginWithPassword.
|
||||
var userSelector = {};
|
||||
if (Meteor.user().username) {
|
||||
userSelector = { username: Meteor.user().username };
|
||||
} else if (Meteor.user().emails && Meteor.user().emails.length) {
|
||||
userSelector = { email: Meteor.user().emails[0].address };
|
||||
} else {
|
||||
callback(new Error(
|
||||
"Cannot upgrade password format without " +
|
||||
"username or email address"));
|
||||
return;
|
||||
}
|
||||
|
||||
srpUpgradePath({
|
||||
upgradeError: error,
|
||||
userSelector: userSelector,
|
||||
plaintextPassword: oldPassword
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
Accounts.changePassword(oldPassword, newPassword, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// A normal error, not an error telling us to upgrade to bcrypt
|
||||
callback && callback(
|
||||
error || new Error("No result from changePassword."));
|
||||
}
|
||||
} else {
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
@@ -254,7 +254,20 @@ Accounts.registerLoginHandler("password", function (options) {
|
||||
///
|
||||
|
||||
// Let the user change their own password if they know the old
|
||||
// password.
|
||||
// password. `oldPassword` and `newPassword` should be objects with keys
|
||||
// `digest` and `algorithm` (representing the SHA256 of the password).
|
||||
//
|
||||
// XXX COMPAT WITH 0.8.1.3
|
||||
// Like the login method, if the user hasn't been upgraded from SRP to
|
||||
// bcrypt yet, then this method will throw an 'old password format'
|
||||
// error. The client should call the SRP upgrade login handler and then
|
||||
// retry this method again.
|
||||
//
|
||||
// UNLIKE the login method, there is no way to avoid getting SRP upgrade
|
||||
// errors thrown. The reasoning for this is that clients using this
|
||||
// method directly will need to be updated anyway because we no longer
|
||||
// support the SRP flow that they would have been doing to use this
|
||||
// method previously.
|
||||
Meteor.methods({changePassword: function (oldPassword, newPassword) {
|
||||
check(oldPassword, passwordValidator);
|
||||
check(newPassword, passwordValidator);
|
||||
@@ -266,9 +279,17 @@ Meteor.methods({changePassword: function (oldPassword, newPassword) {
|
||||
if (!user)
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
|
||||
if (!user.services || !user.services.password || !user.services.password.bcrypt)
|
||||
if (!user.services || !user.services.password ||
|
||||
(!user.services.password.bcrypt && !user.services.password.srp))
|
||||
throw new Meteor.Error(403, "User has no password set");
|
||||
|
||||
if (! user.services.password.bcrypt) {
|
||||
throw new Meteor.Error(400, "old password format", EJSON.stringify({
|
||||
format: 'srp',
|
||||
identity: user.services.password.srp.identity
|
||||
}));
|
||||
}
|
||||
|
||||
var result = checkPassword(user, oldPassword);
|
||||
if (result.error)
|
||||
throw result.error;
|
||||
|
||||
@@ -767,6 +767,51 @@ if (Meteor.isClient) (function () {
|
||||
}));
|
||||
}
|
||||
]);
|
||||
|
||||
testAsyncMulti("passwords - srp to bcrypt upgrade via password change", [
|
||||
logoutStep,
|
||||
// Create user with old SRP credentials in the database.
|
||||
function (test, expect) {
|
||||
var self = this;
|
||||
Meteor.call("testCreateSRPUser", expect(function (error, result) {
|
||||
test.isFalse(error);
|
||||
self.username = result;
|
||||
}));
|
||||
},
|
||||
// Log in with the plaintext password handler, which should NOT upgrade us to bcrypt.
|
||||
function (test, expect) {
|
||||
Accounts.callLoginMethod({
|
||||
methodName: "login",
|
||||
methodArguments: [ { user: { username: this.username }, password: "abcdef" } ],
|
||||
userCallback: expect(function (err) {
|
||||
test.isFalse(err);
|
||||
})
|
||||
});
|
||||
},
|
||||
function (test, expect) {
|
||||
Meteor.call("testNoSRPUpgrade", this.username, expect(function (error) {
|
||||
test.isFalse(error);
|
||||
}));
|
||||
},
|
||||
// Changing our password should upgrade us to bcrypt.
|
||||
function (test, expect) {
|
||||
Accounts.changePassword("abcdef", "abcdefg", expect(function (error) {
|
||||
test.isFalse(error);
|
||||
}));
|
||||
},
|
||||
function (test, expect) {
|
||||
Meteor.call("testSRPUpgrade", this.username, expect(function (error) {
|
||||
test.isFalse(error);
|
||||
}));
|
||||
},
|
||||
// And after the upgrade we should be able to change our password again.
|
||||
function (test, expect) {
|
||||
Accounts.changePassword("abcdefg", "abcdef", expect(function (error) {
|
||||
test.isFalse(error);
|
||||
}));
|
||||
},
|
||||
logoutStep
|
||||
]);
|
||||
}) ();
|
||||
|
||||
|
||||
|
||||
@@ -141,5 +141,13 @@ Meteor.methods({
|
||||
throw new Error("srp wasn't removed");
|
||||
if (!(user.services && user.services.password && user.services.password.bcrypt))
|
||||
throw new Error("bcrypt wasn't added");
|
||||
},
|
||||
|
||||
testNoSRPUpgrade: function (username) {
|
||||
var user = Meteor.users.findOne({username: username});
|
||||
if (user.services && user.services.password && user.services.password.bcrypt)
|
||||
throw new Error("bcrypt was added");
|
||||
if (user.services && user.services.password && ! user.services.password.srp)
|
||||
throw new Error("srp was removed");
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user