diff --git a/packages/accounts-passwords/passwords_client.js b/packages/accounts-passwords/passwords_client.js index cfe805c9d2..05ac3c6fa6 100644 --- a/packages/accounts-passwords/passwords_client.js +++ b/packages/accounts-passwords/passwords_client.js @@ -124,4 +124,45 @@ }); } }; + + // Sends an email to a user with a link that can be used to reset + // their password + // + // @param options {Object} + // - email: (email) + // @param callback (optional) {Function(error|undefined)} + Meteor.forgotPassword = function(options) { + if (!options.email) + throw new Error("Must pass options.email"); + options.baseUrl = window.location.protocol + "//" + + window.location.host + "/"; + Meteor.call("forgotPassword", options, callback); + }; + + // Resets a password based on a token originally created by + // Meteor.forgotPassword, and then logs in the matching user. + // + // @param token {String} + // @param newPassword {String} + // @param callback (optional) {Function(error|undefined)} + Meteor.resetPassword = function(token, newPassword, callback) { + if (!token) + throw new Error("Need to pass options.token"); + if (!newPassword) + throw new Error("Need to pass options.newPassword"); + + var verifier = Meteor._srp.generateVerifier(newPassword); + Meteor.apply( + "resetPassword", [token, verifier], {wait: true}, + function (error, result) { + if (error || !result) { + error = error || new Error("No result from call to resetPassword"); + callback && callback(error); + } + + Meteor.accounts.makeClientLoggedIn(result.id, result.token); + callback && callback(); + }); + }; })(); + diff --git a/packages/accounts-passwords/passwords_server.js b/packages/accounts-passwords/passwords_server.js index 7cecf3bbfa..226068d1df 100644 --- a/packages/accounts-passwords/passwords_server.js +++ b/packages/accounts-passwords/passwords_server.js @@ -137,6 +137,51 @@ var loginToken = Meteor.accounts._loginTokens.insert({userId: userId}); this.setUserId(userId); return {token: loginToken, id: userId}; + }, + + forgotPassword: function (options) { + var email = options.email; + var baseUrl = options.baseUrl; + if (!email) + throw new Meteor.Error(400, "Need to set options.email"); + if (!baseUrl) + throw new Meteor.Error(400, "Need to set options.baseUrl"); + + var user = Meteor.users.findOne({emails: email}); + if (!user) + throw new Meteor.Error(403, "User not found"); + + var token = Meteor.uuid(); + var creationTime = +(new Date); + Meteor.users.update(user._id, {$set: { + "services.password.reset": { + token: token, + creationTime: creationTime + } + }}); + + // XXX definitely *not* the final form! + Meteor.mail.send(email, Meteor.accounts.urls.resetPassword(baseUrl, token)); + }, + + resetPassword: function (token, newVerifier) { + if (!token) + throw new Meteor.Error(400, "Need to pass token"); + if (!newVerifier) + throw new Meteor.Error(400, "Need to pass newVerifier"); + + var user = Meteor.users.findOne({"services.password.reset.token": token}); + if (!user) + throw new Meteor.Error(403, "Reset password link expired"); + + Meteor.users.update({_id: user._id}, { + $set: {'services.password.srp': newVerifier}, + $unset: {'services.password.reset': 1} + }); + + var loginToken = Meteor.accounts._loginTokens.insert({userId: user._id}); + this.setUserId(user._id); + return {token: loginToken, id: user._id}; } }); @@ -199,3 +244,10 @@ }); })(); + + +Meteor.mail = {}; +Meteor.mail.send = function() { + console.log("Send mail:"); + console.log(arguments); +}; diff --git a/packages/accounts-ui/login_buttons.html b/packages/accounts-ui/login_buttons.html index f88b43d849..559400164b 100644 --- a/packages/accounts-ui/login_buttons.html +++ b/packages/accounts-ui/login_buttons.html @@ -37,7 +37,10 @@            {{/if}} -
+ {{#if isForgotPasswordFlow}} + {{> forgotPasswordForm}} + {{else}} +
@@ -46,6 +49,7 @@ {{> loginButtonsServicesRowDynamicPart}}
+ {{/if}} {{else}}
@@ -62,11 +66,9 @@ {{/if}} - {{#if errorMessage}} -
{{errorMessage}}
- {{/if}} + {{> loginButtonsMessages}} -