From 9186c63b73840cfcc7aefae06da3fe0ff5246be6 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 6 Aug 2012 16:12:06 -0700 Subject: [PATCH] Forgot password flow (modulo sending email). Also, url helpers prepared for account validation emails. --- .../accounts-passwords/passwords_client.js | 41 +++++ .../accounts-passwords/passwords_server.js | 52 ++++++ packages/accounts-ui/login_buttons.html | 71 +++++++- packages/accounts-ui/login_buttons.js | 156 ++++++++++++++++-- .../{login_buttons.css => login_buttons.less} | 94 ++++++++--- packages/accounts-ui/package.js | 5 +- packages/accounts-urls/package.js | 9 + packages/accounts-urls/url_client.js | 26 +++ packages/accounts-urls/url_server.js | 13 ++ packages/accounts/localstorage_token.js | 28 ++-- packages/accounts/package.js | 1 + 11 files changed, 432 insertions(+), 64 deletions(-) rename packages/accounts-ui/{login_buttons.css => login_buttons.less} (90%) create mode 100644 packages/accounts-urls/package.js create mode 100644 packages/accounts-urls/url_client.js create mode 100644 packages/accounts-urls/url_server.js 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}} + + {{/if}} {{else}}