overleaf/services/web/test/unit/coffee/PasswordReset/PasswordResetControllerTests.coffee
Simon Detheridge 4c191953d3 Merge pull request #1356 from sharelatex/spd-password-complexity
Make password validation more consistent between backend and frontend

GitOrigin-RevId: 6ba729da842bf474cf7e9b5e0b2435db0544737c
2019-01-11 14:43:49 +00:00

217 lines
8 KiB
CoffeeScript

should = require('chai').should()
expect = require("chai").expect
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 = {}
@PasswordResetHandler =
generateAndEmailResetToken:sinon.stub()
setNewUserPassword:sinon.stub()
@RateLimiter =
addCount: sinon.stub()
@UserSessionsManager =
revokeAllUserSessions: sinon.stub().callsArgWith(2, null)
@AuthenticationManager =
validatePassword: sinon.stub()
@PasswordResetController = SandboxedModule.require modulePath, requires:
"settings-sharelatex":@settings
"./PasswordResetHandler":@PasswordResetHandler
"logger-sharelatex": log:->
"../../infrastructure/RateLimiter":@RateLimiter
"../Authentication/AuthenticationController": @AuthenticationController = {}
"../Authentication/AuthenticationManager": @AuthenticationManager
"../User/UserGetter": @UserGetter = {}
"../User/UserSessionsManager": @UserSessionsManager
@email = "bob@bob.com "
@user_id = 'mock-user-id'
@token = "my security token that was emailed to me"
@password = "my new password"
@req =
body:
email:@email
passwordResetToken:@token
password:@password
i18n:
translate:->
session: {}
query: {}
@res = {}
describe "requestReset", ->
it "should error if the rate limit is hit", (done)->
@PasswordResetHandler.generateAndEmailResetToken.callsArgWith(1, null, true)
@RateLimiter.addCount.callsArgWith(1, null, false)
@res.send = (code)=>
code.should.equal 429
@PasswordResetHandler.generateAndEmailResetToken.calledWith(@email.trim()).should.equal false
done()
@PasswordResetController.requestReset @req, @res
it "should tell the handler to process that email", (done)->
@RateLimiter.addCount.callsArgWith(1, null, true)
@PasswordResetHandler.generateAndEmailResetToken.callsArgWith(1, null, true)
@res.sendStatus = (code)=>
code.should.equal 200
@PasswordResetHandler.generateAndEmailResetToken.calledWith(@email.trim()).should.equal true
done()
@PasswordResetController.requestReset @req, @res
it "should send a 500 if there is an error", (done)->
@RateLimiter.addCount.callsArgWith(1, null, true)
@PasswordResetHandler.generateAndEmailResetToken.callsArgWith(1, "error")
@res.send = (code)=>
code.should.equal 500
done()
@PasswordResetController.requestReset @req, @res
it "should send a 404 if the email doesn't exist", (done)->
@RateLimiter.addCount.callsArgWith(1, null, true)
@PasswordResetHandler.generateAndEmailResetToken.callsArgWith(1, null, false)
@res.send = (code)=>
code.should.equal 404
done()
@PasswordResetController.requestReset @req, @res
it "should lowercase the email address", (done)->
@email = "UPerCaseEMAIL@example.Com"
@req.body.email = @email
@RateLimiter.addCount.callsArgWith(1, null, true)
@PasswordResetHandler.generateAndEmailResetToken.callsArgWith(1, null, true)
@res.sendStatus = (code)=>
code.should.equal 200
@PasswordResetHandler.generateAndEmailResetToken.calledWith(@email.toLowerCase()).should.equal true
done()
@PasswordResetController.requestReset @req, @res
describe "setNewUserPassword", ->
beforeEach ->
@req.session.resetToken = @token
it "should tell the user handler to reset the password", (done)->
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id)
@res.sendStatus = (code)=>
code.should.equal 200
@PasswordResetHandler.setNewUserPassword.calledWith(@token, @password).should.equal true
done()
@PasswordResetController.setNewUserPassword @req, @res
it "should send 404 if the token didn't work", (done)->
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, false, @user_id)
@res.sendStatus = (code)=>
code.should.equal 404
done()
@PasswordResetController.setNewUserPassword @req, @res
it "should return 400 (Bad Request) if there is no password", (done)->
@req.body.password = ""
@PasswordResetHandler.setNewUserPassword.callsArgWith(2)
@res.sendStatus = (code)=>
code.should.equal 400
@PasswordResetHandler.setNewUserPassword.called.should.equal false
done()
@PasswordResetController.setNewUserPassword @req, @res
it "should return 400 (Bad Request) if there is no passwordResetToken", (done)->
@req.body.passwordResetToken = ""
@PasswordResetHandler.setNewUserPassword.callsArgWith(2)
@res.sendStatus = (code)=>
code.should.equal 400
@PasswordResetHandler.setNewUserPassword.called.should.equal false
done()
@PasswordResetController.setNewUserPassword @req, @res
it "should return 400 (Bad Request) if the password is invalid", (done)->
@req.body.password = "correct horse battery staple"
@AuthenticationManager.validatePassword = sinon.stub().returns { message: 'password contains invalid characters' }
@PasswordResetHandler.setNewUserPassword.callsArgWith(2)
@res.sendStatus = (code)=>
code.should.equal 400
@PasswordResetHandler.setNewUserPassword.called.should.equal false
done()
@PasswordResetController.setNewUserPassword @req, @res
it "should clear the session.resetToken", (done) ->
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id)
@res.sendStatus = (code)=>
code.should.equal 200
@req.session.should.not.have.property 'resetToken'
done()
@PasswordResetController.setNewUserPassword @req, @res
it 'should clear sessions', (done) ->
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id)
@res.sendStatus = (code)=>
@UserSessionsManager.revokeAllUserSessions.callCount.should.equal 1
done()
@PasswordResetController.setNewUserPassword @req, @res
describe 'when login_after is set', ->
beforeEach ->
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, { email: "joe@example.com" })
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id = "user-id-123")
@req.body.login_after = "true"
@res.json = sinon.stub()
@AuthenticationController.afterLoginSessionSetup = sinon.stub().callsArgWith(2, null)
@AuthenticationController._getRedirectFromSession = sinon.stub().returns('/some/path')
it "should login user if login_after is set", (done) ->
@PasswordResetController.setNewUserPassword @req, @res
@AuthenticationController.afterLoginSessionSetup.callCount.should.equal 1
@AuthenticationController.afterLoginSessionSetup.calledWith(
@req,
{email: 'joe@example.com'}
).should.equal true
@AuthenticationController._getRedirectFromSession.callCount.should.equal 1
@res.json.callCount.should.equal 1
@res.json.calledWith({redir: '/some/path'}).should.equal true
done()
describe "renderSetPasswordForm", ->
describe "with token in query-string", ->
beforeEach ->
@req.query.passwordResetToken = @token
it "should set session.resetToken and redirect", (done) ->
@req.session.should.not.have.property 'resetToken'
@res.redirect = (path) =>
path.should.equal '/user/password/set'
@req.session.resetToken.should.equal @token
done()
@PasswordResetController.renderSetPasswordForm(@req, @res)
describe "without a token in query-string", ->
describe "with token in session", ->
beforeEach ->
@req.session.resetToken = @token
it "should render the page, passing the reset token", (done) ->
@res.render = (template_path, options) =>
options.passwordResetToken.should.equal @req.session.resetToken
done()
@PasswordResetController.renderSetPasswordForm(@req, @res)
describe "without a token in session", ->
it "should redirect to the reset request page", (done) ->
@res.redirect = (path) =>
path.should.equal "/user/password/reset"
@req.session.should.not.have.property 'resetToken'
done()
@PasswordResetController.renderSetPasswordForm(@req, @res)