diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee new file mode 100644 index 0000000000..c88c8f8b3f --- /dev/null +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee @@ -0,0 +1,21 @@ + +module.exports = + + requestPasswordReset: -> + + + + # check user exists + # generate token + # send email token link + + renderPasswordResetForm: -> + # check that the token is valid + # render html + + changeUsersPasswordFromReset: -> + # check auth again + # check both passwords match + # set passowrd + # redir to login form + diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee new file mode 100644 index 0000000000..1c1851778f --- /dev/null +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee @@ -0,0 +1,28 @@ +settings = require("settings-sharelatex") +async = require("async") +UserGetter = require("../User/UserGetter") +TokenGenerator = require("./TokenGenerator") +EmailHandler = require("../Email/EmailHandler") +AuthenticationManager = require("../Authentication/AuthenticationManager") + +module.exports = + + generateAndEmailResetToken:(user_id, callback)-> + async.series + user: (cb)-> UserGetter.getUser _id:user_id, cb + token: (cb)-> TokenGenerator.getNewToken user_id, cb + , (err, results)-> + if err then return callback(err) + if !results.user? + return callback("no user found") + emailOptions = + to : results.user.email + setNewPasswordUrl : "#{settings.siteUrl}/user/password/set?resetToken=#{results.token}" + EmailHandler.sendEmail "passwordResetRequested", emailOptions, callback + + setNewUserPassowrd: (token, password, callback)-> + TokenGenerator.getUserIdFromToken token, (err, user_id)-> + if err then return callback(err) + if !user_id? + return callback("no user found") + AuthenticationManager.setUserPassword user_id, password, callback \ No newline at end of file diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetRouter.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetRouter.coffee new file mode 100644 index 0000000000..931d4d3b69 --- /dev/null +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetRouter.coffee @@ -0,0 +1,9 @@ +module.exports = + apply: (app) -> + + app.get '/user/password/reset', PasswordResetController.renderRequestReset + app.post '/user/password/reset', ProjectDownloadsController.requestRest + + app.get '/user/password/set', PasswordResetController.renderSetPassword + app.post '/user/password/set', PasswordResetController.setNewUserPassword + diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 25f3c2aeb5..ef24c1eaf3 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -34,6 +34,8 @@ FileStoreController = require("./Features/FileStore/FileStoreController") TrackChangesController = require("./Features/TrackChanges/TrackChangesController") DropboxUserController = require("./Features/Dropbox/DropboxUserController") RestoreController = require("./Features/Restore/RestoreController") +PasswordResetRouter = require("./Features/PasswordReset/PasswordResetRouter") + logger = require("logger-sharelatex") _ = require("underscore") @@ -71,6 +73,7 @@ module.exports = class Router SubscriptionRouter.apply(app) UploadsRouter.apply(app) + PasswordResetRouter.apply(app) if Settings.enableSubscriptions app.get '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus @@ -78,8 +81,7 @@ module.exports = class Router app.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage app.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings app.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword - app.get '/user/passwordreset', UserPagesController.passwordResetPage - app.post '/user/passwordReset', UserController.doRequestPasswordReset + app.del '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe app.del '/user', AuthenticationController.requireLogin(), UserController.deleteUser diff --git a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee new file mode 100644 index 0000000000..31b31028c7 --- /dev/null +++ b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee @@ -0,0 +1,31 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/PasswordReset/PasswordResetController" +expect = require("chai").expect + +describe "PasswordResetController", -> + + beforeEach -> + + @settings = {} + @PasswordResetController = SandboxedModule.require modulePath, requires: + "settings-sharelatex":@settings + "logger-sharelatex": log:-> + + + describe "requestPasswordReset", -> + + it "should check the user exists", (done)-> + + done() + + it "should get a unique token and send the email", (done)-> + done() + + + + + diff --git a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee new file mode 100644 index 0000000000..885ed40b9d --- /dev/null +++ b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee @@ -0,0 +1,76 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/PasswordReset/PasswordResetHandler" +expect = require("chai").expect + +describe "PasswordResetHandler", -> + + beforeEach -> + + @settings = + siteUrl: "www.sharelatex.com" + @TokenGenerator = + getNewToken:sinon.stub() + getUserIdFromToken:sinon.stub() + @UserGetter = + getUser:sinon.stub() + @EmailHandler = + sendEmail:sinon.stub() + @AuthenticationManager = + setUserPassword:sinon.stub() + @PasswordResetHandler = SandboxedModule.require modulePath, requires: + "../User/UserGetter": @UserGetter + "./TokenGenerator": @TokenGenerator + "../Email/EmailHandler":@EmailHandler + "../Authentication/AuthenticationManager":@AuthenticationManager + "settings-sharelatex": @settings + "logger-sharelatex": log:-> + @token = "12312321i" + @user_id = "user_id_here" + @user = + email :"bob@bob.com" + @password = "my great secret password" + + + describe "generateAndEmailResetToken", -> + + it "should check the user exists", (done)-> + @UserGetter.getUser.callsArgWith(1) + @TokenGenerator.getNewToken.callsArgWith(1) + @PasswordResetHandler.generateAndEmailResetToken @user_id, (err)-> + err.should.exists + done() + + + it "should send the email with the token", (done)-> + + @UserGetter.getUser.callsArgWith(1, null, @user) + @TokenGenerator.getNewToken.callsArgWith(1, null, @token) + @EmailHandler.sendEmail.callsArgWith(2) + @PasswordResetHandler.generateAndEmailResetToken @user_id, (err)=> + @EmailHandler.sendEmail.called.should.equal true + args = @EmailHandler.sendEmail.args[0] + args[0].should.equal "passwordResetRequested" + args[1].setNewPasswordUrl.should.equal "#{@settings.siteUrl}/user/password/set?resetToken=#{@token}" + done() + + + describe "setNewUserPassowrd", -> + + it "should return err if no user id can be found", (done)-> + @TokenGenerator.getUserIdFromToken.callsArgWith(1) + @PasswordResetHandler.setNewUserPassowrd @token, @password, (err)=> + err.should.exists + @AuthenticationManager.setUserPassword.called.should.equal false + done() + + it "should set the user password", (done)-> + @TokenGenerator.getUserIdFromToken.callsArgWith(1, null, @user_id) + @AuthenticationManager.setUserPassword.callsArgWith(2) + @PasswordResetHandler.setNewUserPassowrd @token, @password, (err)=> + @AuthenticationManager.setUserPassword.calledWith(@user_id, @password).should.equal true + done() +