remove v1 deps for password change/reset

GitOrigin-RevId: be25f19ae589c50bfde0b170860127fa8d6f63b7
This commit is contained in:
Ersun Warncke 2019-06-14 12:31:46 -04:00 committed by sharelatex
parent a61a59be20
commit d624c29b6f
10 changed files with 363 additions and 642 deletions

View file

@ -134,48 +134,7 @@ module.exports = AuthenticationManager = {
},
setUserPassword(user_id, password, callback) {
if (callback == null) {
callback = function(error, changed) {}
}
const validation = this.validatePassword(password)
if (validation != null) {
return callback(validation.message)
}
return UserGetter.getUser(user_id, { email: 1, overleaf: 1 }, function(
error,
user
) {
if (error != null) {
return callback(error)
}
const v1IdExists =
(user.overleaf != null ? user.overleaf.id : undefined) != null
if (v1IdExists && Settings.overleaf != null) {
// v2 user in v2
// v2 user in v2, change password in v1
return AuthenticationManager.setUserPasswordInV1(
user.overleaf.id,
password,
callback
)
} else if (v1IdExists && Settings.overleaf == null) {
// v2 user in SL
return callback(new Errors.NotInV2Error('Password Reset Attempt'))
} else if (!v1IdExists && Settings.overleaf == null) {
// SL user in SL, change password in SL
return AuthenticationManager.setUserPasswordInV2(
user_id,
password,
callback
)
} else if (!v1IdExists && Settings.overleaf != null) {
// SL user in v2, should not happen
return callback(new Errors.SLInV2Error('Password Reset Attempt'))
} else {
return callback(new Error('Password Reset Attempt Failed'))
}
})
AuthenticationManager.setUserPasswordInV2(user_id, password, callback)
},
checkRounds(user, hashedPassword, password, callback) {

View file

@ -6,7 +6,6 @@ const UserGetter = require('../User/UserGetter')
const UserUpdater = require('../User/UserUpdater')
const UserSessionsManager = require('../User/UserSessionsManager')
const logger = require('logger-sharelatex')
const Settings = require('settings-sharelatex')
module.exports = {
renderRequestResetForm(req, res) {
@ -22,23 +21,18 @@ module.exports = {
subjectName: req.ip,
throttle: 6
}
RateLimiter.addCount(opts, function(err, canContinue) {
RateLimiter.addCount(opts, (err, canContinue) => {
if (err != null) {
return next(err)
res.send(500, { message: err.message })
}
if (!canContinue) {
return res.send(429, {
message: req.i18n.translate('rate_limit_hit_wait')
})
}
PasswordResetHandler.generateAndEmailResetToken(email, function(
err,
status
) {
PasswordResetHandler.generateAndEmailResetToken(email, (err, status) => {
if (err != null) {
res.send(500, {
message: err.message
})
res.send(500, { message: err.message })
} else if (status === 'primary') {
res.send(200, {
message: { text: req.i18n.translate('password_reset_email_sent') }
@ -47,12 +41,6 @@ module.exports = {
res.send(404, {
message: req.i18n.translate('secondary_email_password_reset')
})
} else if (status === 'sharelatex') {
res.send(404, {
message: `<a href="${
Settings.accountMerge.sharelatexHost
}/user/password/reset">${req.i18n.translate('reset_from_sl')}</a>`
})
} else {
res.send(404, {
message: req.i18n.translate('cant_find_email')
@ -77,78 +65,61 @@ module.exports = {
},
setNewUserPassword(req, res, next) {
const { passwordResetToken, password } = req.body
if (
!password ||
!passwordResetToken ||
AuthenticationManager.validatePassword(password.trim()) != null
) {
let { passwordResetToken, password } = req.body
if (!passwordResetToken || !password) {
return res.sendStatus(400)
}
password = password.trim()
passwordResetToken = passwordResetToken.trim()
if (AuthenticationManager.validatePassword(password) != null) {
return res.sendStatus(400)
}
delete req.session.resetToken
PasswordResetHandler.setNewUserPassword(
passwordResetToken.trim(),
password.trim(),
function(err, found, userId) {
if (err && err.name && err.name === 'NotFoundError') {
res.status(404).send('NotFoundError')
} else if (err && err.name && err.name === 'NotInV2Error') {
res.status(403).send('NotInV2Error')
} else if (err && err.name && err.name === 'SLInV2Error') {
res.status(403).send('SLInV2Error')
} else if (err && err.statusCode && err.statusCode === 500) {
res.status(500)
} else if (err && !err.statusCode) {
res.status(500)
} else if (found) {
if (userId == null) {
return res.sendStatus(200)
} // will not exist for v1-only users
UserSessionsManager.revokeAllUserSessions(
{ _id: userId },
[],
function(err) {
passwordResetToken,
password,
(err, found, userId) => {
if ((err && err.name === 'NotFoundError') || !found) {
return res.status(404).send('NotFoundError')
} else if (err) {
return res.status(500)
}
UserSessionsManager.revokeAllUserSessions({ _id: userId }, [], err => {
if (err != null) {
return next(err)
}
UserUpdater.removeReconfirmFlag(userId, err => {
if (err != null) {
return next(err)
}
if (!req.body.login_after) {
return res.sendStatus(200)
}
UserGetter.getUser(userId, { email: 1 }, (err, user) => {
if (err != null) {
return next(err)
}
UserUpdater.removeReconfirmFlag(userId, function(err) {
if (err != null) {
return next(err)
}
if (req.body.login_after) {
UserGetter.getUser(userId, { email: 1 }, function(err, user) {
if (err != null) {
return next(err)
}
AuthenticationController.afterLoginSessionSetup(
req,
user,
function(err) {
if (err != null) {
logger.warn(
{ err, email: user.email },
'Error setting up session after setting password'
)
return next(err)
}
res.json({
redir:
AuthenticationController._getRedirectFromSession(
req
) || '/project'
})
}
AuthenticationController.afterLoginSessionSetup(
req,
user,
err => {
if (err != null) {
logger.err(
{ err, email: user.email },
'Error setting up session after setting password'
)
return next(err)
}
res.json({
redir:
AuthenticationController._getRedirectFromSession(req) ||
'/project'
})
} else {
res.sendStatus(200)
}
})
}
)
} else {
res.sendStatus(404)
}
)
})
})
})
}
)
}

View file

@ -3,81 +3,58 @@ const UserGetter = require('../User/UserGetter')
const OneTimeTokenHandler = require('../Security/OneTimeTokenHandler')
const EmailHandler = require('../Email/EmailHandler')
const AuthenticationManager = require('../Authentication/AuthenticationManager')
const logger = require('logger-sharelatex')
const V1Api = require('../V1/V1Api')
const PasswordResetHandler = {
generateAndEmailResetToken(email, callback) {
PasswordResetHandler._getPasswordResetData(email, function(
error,
exists,
data
) {
if (error != null) {
callback(error)
} else if (exists) {
OneTimeTokenHandler.getNewToken('password', data, function(err, token) {
UserGetter.getUserByAnyEmail(email, (err, user) => {
if (err || !user) {
return callback(err, null)
}
if (user.email !== email) {
return callback(null, 'secondary')
}
const data = { user_id: user._id.toString(), email: email }
OneTimeTokenHandler.getNewToken('password', data, (err, token) => {
if (err) {
return callback(err)
}
const emailOptions = {
to: email,
setNewPasswordUrl: `${
settings.siteUrl
}/user/password/set?passwordResetToken=${token}&email=${encodeURIComponent(
email
)}`
}
EmailHandler.sendEmail('passwordResetRequested', emailOptions, err => {
if (err) {
return callback(err)
}
const emailOptions = {
to: email,
setNewPasswordUrl: `${
settings.siteUrl
}/user/password/set?passwordResetToken=${token}&email=${encodeURIComponent(
email
)}`
}
EmailHandler.sendEmail(
'passwordResetRequested',
emailOptions,
function(error) {
if (error != null) {
return callback(error)
}
callback(null, 'primary')
}
)
callback(null, 'primary')
})
} else {
UserGetter.getUserByAnyEmail(email, function(err, user) {
if (err != null) {
return callback(err)
}
if (!user) {
callback(null, null)
} else if (user.overleaf == null || user.overleaf.id == null) {
callback(null, 'sharelatex')
} else {
callback(null, 'secondary')
}
})
}
})
})
},
setNewUserPassword(token, password, callback) {
PasswordResetHandler.getUserForPasswordResetToken(
token,
(err, user, version) => {
if (err != null) {
return callback(err)
}
if (user == null) {
return callback(null, false, null)
}
AuthenticationManager.setUserPassword(
user._id,
password,
(err, reset) => {
if (err) {
return callback(err)
}
callback(null, reset, user._id)
}
)
PasswordResetHandler.getUserForPasswordResetToken(token, (err, user) => {
if (err) {
return callback(err)
}
)
if (!user) {
return callback(null, false, null)
}
AuthenticationManager.setUserPassword(
user._id,
password,
(err, reset) => {
if (err) {
return callback(err)
}
callback(null, reset, user._id)
}
)
})
},
getUserForPasswordResetToken(token, callback) {
@ -107,13 +84,13 @@ const PasswordResetHandler = {
data.user_id != null &&
data.user_id === user._id.toString()
) {
callback(null, user, 'v2')
callback(null, user)
} else if (
data.v1_user_id != null &&
user.overleaf != null &&
data.v1_user_id === user.overleaf.id
) {
callback(null, user, 'v1')
callback(null, user)
} else {
callback(null, null)
}
@ -121,43 +98,6 @@ const PasswordResetHandler = {
)
}
)
},
_getPasswordResetData(email, callback) {
if (settings.overleaf != null) {
// Overleaf v2
V1Api.request(
{
url: '/api/v1/sharelatex/user_emails',
qs: {
email
},
expectedStatusCodes: [404]
},
function(error, response, body) {
if (error != null) {
return callback(error)
}
if (response.statusCode === 404) {
callback(null, false)
} else {
callback(null, true, { v1_user_id: body.user_id, email: email })
}
}
)
} else {
// ShareLaTeX
UserGetter.getUserByMainEmail(email, function(err, user) {
if (err) {
return callback(err)
}
if (user == null || user.holdingAccount || user.overleaf != null) {
logger.err({ email }, 'user could not be found for password reset')
return callback(null, false)
}
callback(null, true, { user_id: user._id, email: email })
})
}
}
}

View file

@ -29,6 +29,7 @@ const UserUpdater = require('./UserUpdater')
const SudoModeHandler = require('../SudoMode/SudoModeHandler')
const settings = require('settings-sharelatex')
const Errors = require('../Errors/Errors')
const EmailHandler = require('../Email/EmailHandler')
module.exports = UserController = {
tryDeleteUser(req, res, next) {
@ -302,78 +303,80 @@ module.exports = UserController = {
},
changePassword(req, res, next) {
if (next == null) {
next = function(error) {}
}
metrics.inc('user.password-change')
const oldPass = req.body.currentPassword
const internalError = {
message: { type: 'error', text: req.i18n.translate('internal_error') }
}
const user_id = AuthenticationController.getLoggedInUserId(req)
return AuthenticationManager.authenticate(
AuthenticationManager.authenticate(
{ _id: user_id },
oldPass,
function(err, user) {
if (err != null) {
return next(err)
req.body.currentPassword,
(err, user) => {
if (err) {
return res.status(500).json(internalError)
}
if (user) {
logger.log({ user: user._id }, 'changing password')
const { newPassword1 } = req.body
const { newPassword2 } = req.body
const validationError = AuthenticationManager.validatePassword(
newPassword1
)
if (newPassword1 !== newPassword2) {
logger.log({ user }, 'passwords do not match')
return res.send({
message: {
type: 'error',
text: 'Your passwords do not match'
}
})
} else if (validationError != null) {
logger.log({ user }, validationError.message)
return res.send({
message: {
type: 'error',
text: validationError.message
}
})
} else {
logger.log({ user }, 'password changed')
return AuthenticationManager.setUserPassword(
user._id,
newPassword1,
function(error) {
if (error != null) {
return next(error)
}
return UserSessionsManager.revokeAllUserSessions(
user,
[req.sessionID],
function(err) {
if (err != null) {
return next(err)
}
return res.send({
message: {
type: 'success',
text: 'Your password has been changed'
}
})
}
)
}
)
}
} else {
logger.log({ user_id }, 'current password wrong')
return res.send({
if (!user) {
return res.status(400).json({
message: {
type: 'error',
text: 'Your old password is wrong'
}
})
}
if (req.body.newPassword1 !== req.body.newPassword2) {
return res.status(400).json({
message: {
type: 'error',
text: req.i18n.translate('password_change_passwords_do_not_match')
}
})
}
const validationError = AuthenticationManager.validatePassword(
req.body.newPassword1
)
if (validationError != null) {
return res.status(400).json({
message: {
type: 'error',
text: validationError.message
}
})
}
AuthenticationManager.setUserPassword(
user._id,
req.body.newPassword1,
err => {
if (err) {
return res.status(500).json(internalError)
}
// log errors but do not wait for response
EmailHandler.sendEmail(
'passwordChanged',
{ to: user.email },
err => {
if (err) {
logger.warn(err)
}
}
)
UserSessionsManager.revokeAllUserSessions(
user,
[req.sessionID],
err => {
if (err != null) {
return res.status(500).json(internalError)
}
res.json({
message: {
type: 'success',
email: user.email,
text: req.i18n.translate('password_change_successful')
}
})
}
)
}
)
}
)
}

View file

@ -25,12 +25,8 @@ block content
br
a(href="/user/password/reset" style="text-decoration: underline;")
| Request a new password reset email
.alert.alert-danger(ng-show="passwordResetForm.response.data == 'SLInV2Error'")
a(href=settings.accountMerge.sharelatexHost + "/user/password/reset")
| Please go to ShareLaTeX to reset your password
.alert.alert-danger(ng-show="passwordResetForm.response.data == 'NotInV2Error'")
a(href=settings.accountMerge.betaHost + "/user/password/reset" style="text-decoration: underline;")
| Please go to Overleaf to reset your password
.alert.alert-danger(ng-show="passwordResetForm.response.status == 400")
| #{translate('invalid_password')}
.alert.alert-danger(ng-show="passwordResetForm.response.status == 500")
| #{translate('error_performing_request')}

View file

@ -98,10 +98,7 @@ block content
| #[a(href="/user/password/reset", target='_blank') #{translate("no_existing_password")}]
else
- var submitAction
if settings.overleaf
- submitAction = '/user/change_password/v1'
else
- submitAction = '/user/password/update'
- submitAction = '/user/password/update'
form(
async-form="changepassword"
name="changePasswordForm"

View file

@ -574,114 +574,40 @@ describe('AuthenticationManager', function() {
})
})
describe('password set attempt', function() {
describe('with SL user in SL', function() {
beforeEach(function() {
this.UserGetter.getUser = sinon
.stub()
.yields(null, { overleaf: null })
return this.AuthenticationManager.setUserPassword(
this.user_id,
this.password,
this.callback
)
})
describe('successful password set attempt', function() {
beforeEach(function() {
this.UserGetter.getUser = sinon.stub().yields(null, { overleaf: null })
this.AuthenticationManager.setUserPassword(
this.user_id,
this.password,
this.callback
)
})
it('should look up the user', function() {
return this.UserGetter.getUser
.calledWith(this.user_id)
.should.equal(true)
it("should update the user's password in the database", function() {
const { args } = this.db.users.update.lastCall
expect(args[0]).to.deep.equal({
_id: ObjectId(this.user_id.toString())
})
it("should update the user's password in the database", function() {
const { args } = this.db.users.update.lastCall
expect(args[0]).to.deep.equal({
_id: ObjectId(this.user_id.toString())
})
return expect(args[1]).to.deep.equal({
$set: {
hashedPassword: this.hashedPassword
},
$unset: {
password: true
}
})
})
it('should hash the password', function() {
this.bcrypt.genSalt.calledWith(12).should.equal(true)
return this.bcrypt.hash
.calledWith(this.password, this.salt)
.should.equal(true)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
return expect(args[1]).to.deep.equal({
$set: {
hashedPassword: this.hashedPassword
},
$unset: {
password: true
}
})
})
describe('with SL user in v2', function() {
beforeEach(function(done) {
this.settings.overleaf = true
this.UserGetter.getUser = sinon
.stub()
.yields(null, { overleaf: null })
return this.AuthenticationManager.setUserPassword(
this.user_id,
this.password,
(err, changed) => {
this.callback(err, changed)
return done()
}
)
})
it('should error', function() {
return this.callback
.calledWith(new Errors.SLInV2Error('Password Reset Attempt'))
.should.equal(true)
})
it('should hash the password', function() {
this.bcrypt.genSalt.calledWith(12).should.equal(true)
return this.bcrypt.hash
.calledWith(this.password, this.salt)
.should.equal(true)
})
describe('with v2 user in SL', function() {
beforeEach(function(done) {
this.UserGetter.getUser = sinon
.stub()
.yields(null, { overleaf: { id: 1 } })
return this.AuthenticationManager.setUserPassword(
this.user_id,
this.password,
(err, changed) => {
this.callback(err, changed)
return done()
}
)
})
it('should error', function() {
return this.callback
.calledWith(new Errors.NotInV2Error('Password Reset Attempt'))
.should.equal(true)
})
})
describe('with v2 user in v2', function() {
beforeEach(function(done) {
this.settings.overleaf = true
this.UserGetter.getUser = sinon
.stub()
.yields(null, { overleaf: { id: 1 } })
this.V1Handler.doPasswordReset = sinon.stub().yields(null, true)
return this.AuthenticationManager.setUserPassword(
this.user_id,
this.password,
(err, changed) => {
this.callback(err, changed)
return done()
}
)
})
it('should set the password in v2', function() {
return this.callback.calledWith(null, true).should.equal(true)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
})

View file

@ -74,7 +74,7 @@ describe('PasswordResetController', function() {
query: {}
}
return (this.res = {})
this.res = {}
})
describe('requestReset', function() {
@ -202,9 +202,9 @@ describe('PasswordResetController', function() {
false,
this.user_id
)
this.res.sendStatus = code => {
this.res.status = code => {
code.should.equal(404)
return done()
done()
}
return this.PasswordResetController.setNewUserPassword(this.req, this.res)
})

View file

@ -1,11 +1,29 @@
const { expect } = require('chai')
/* eslint-disable
handle-callback-err,
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const assert = require('assert')
const path = require('path')
const sinon = require('sinon')
const sinonChai = require('sinon-chai')
const chai = require('chai')
const modulePath = path.join(
__dirname,
'../../../../app/src/Features/PasswordReset/PasswordResetHandler'
)
const should = require('chai').should()
chai.use(sinonChai)
const { expect } = chai
describe('PasswordResetHandler', function() {
beforeEach(function() {
@ -21,19 +39,17 @@ describe('PasswordResetHandler', function() {
}
this.EmailHandler = { sendEmail: sinon.stub() }
this.AuthenticationManager = {
setUserPassword: sinon.stub()
setUserPassword: sinon.stub(),
setUserPasswordInV1: sinon.stub(),
setUserPasswordInV2: sinon.stub()
}
this.V1Api = { request: sinon.stub() }
this.PasswordResetHandler = SandboxedModule.require(modulePath, {
globals: {
console: console
},
globals: { console: console },
requires: {
'../User/UserGetter': this.UserGetter,
'../Security/OneTimeTokenHandler': this.OneTimeTokenHandler,
'../Email/EmailHandler': this.EmailHandler,
'../Authentication/AuthenticationManager': this.AuthenticationManager,
'../V1/V1Api': this.V1Api,
'settings-sharelatex': this.settings,
'logger-sharelatex': {
log() {},
@ -42,128 +58,39 @@ describe('PasswordResetHandler', function() {
}
})
this.token = '12312321i'
this.email = 'bob@bob.com'
this.user = {
_id: 'user_id_here',
email: this.email
}
this.user_id = 'user_id_here'
this.user = { email: (this.email = 'bob@bob.com'), _id: 'user-id' }
this.password = 'my great secret password'
this.callback = sinon.stub()
// this should not have any effect now
this.settings.overleaf = true
})
afterEach(function() {
this.settings.overleaf = false
})
describe('generateAndEmailResetToken', function() {
describe('when in ShareLaTeX', function() {
it('should check the user exists', function(done) {
this.UserGetter.getUserByMainEmail.callsArgWith(1)
this.UserGetter.getUserByAnyEmail.callsArgWith(1)
this.OneTimeTokenHandler.getNewToken.yields()
this.PasswordResetHandler.generateAndEmailResetToken(
this.user.email,
(err, status) => {
if (err) {
return done(err)
}
expect(status).to.be.null
done()
}
)
})
it('should send the email with the token', function(done) {
this.UserGetter.getUserByMainEmail.callsArgWith(1, null, this.user)
this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
this.EmailHandler.sendEmail.callsArgWith(2)
this.PasswordResetHandler.generateAndEmailResetToken(
this.user.email,
(err, status) => {
if (err) {
return done(err)
}
this.EmailHandler.sendEmail.called.should.equal(true)
this.OneTimeTokenHandler.getNewToken.should.have.been.calledWith(
'password',
{
user_id: this.user._id,
email: this.email
}
)
status.should.equal('primary')
const args = this.EmailHandler.sendEmail.args[0]
args[0].should.equal('passwordResetRequested')
args[1].setNewPasswordUrl.should.equal(
`${this.settings.siteUrl}/user/password/set?passwordResetToken=${
this.token
}&email=${encodeURIComponent(this.user.email)}`
)
done()
}
)
})
it('should return exists == null for a holdingAccount', function(done) {
this.user.holdingAccount = true
this.UserGetter.getUserByMainEmail.callsArgWith(1, null, this.user)
this.UserGetter.getUserByAnyEmail.callsArgWith(1)
this.OneTimeTokenHandler.getNewToken.yields()
this.PasswordResetHandler.generateAndEmailResetToken(
this.user.email,
(err, status) => {
if (err) {
return done(err)
}
expect(status).to.be.null
done()
}
)
})
it('should set the password token data to the user id and email', function() {
this.UserGetter.getUserByMainEmail.callsArgWith(1, null, this.user)
this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
this.EmailHandler.sendEmail.callsArgWith(2)
})
it('should check the user exists', function() {
this.UserGetter.getUserByAnyEmail.yields()
this.PasswordResetHandler.generateAndEmailResetToken(
this.user.email,
this.callback
)
this.UserGetter.getUserByAnyEmail.should.have.been.calledWith(
this.user.email
)
})
describe('when in overleaf', function() {
beforeEach(function() {
this.settings.overleaf = true
})
describe('when the email exists', function() {
beforeEach(function() {
this.V1Api.request.yields(null, {}, { user_id: 42 })
this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
this.EmailHandler.sendEmail.yields()
this.PasswordResetHandler.generateAndEmailResetToken(
this.email,
this.callback
)
})
it('should call the v1 api for the user', function() {
this.V1Api.request
.calledWith({
url: '/api/v1/sharelatex/user_emails',
qs: {
email: this.email
},
expectedStatusCodes: [404]
})
.should.equal(true)
})
it('should set the password token data to the user id and email', function() {
this.OneTimeTokenHandler.getNewToken.should.have.been.calledWith(
'password',
{
v1_user_id: 42,
email: this.email
}
)
})
it('should send an email with the token', function() {
it('should send the email with the token', function(done) {
this.UserGetter.getUserByAnyEmail.yields(null, this.user)
this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
this.EmailHandler.sendEmail.yields()
this.PasswordResetHandler.generateAndEmailResetToken(
this.user.email,
(err, status) => {
this.EmailHandler.sendEmail.called.should.equal(true)
status.should.equal('primary')
const args = this.EmailHandler.sendEmail.args[0]
args[0].should.equal('passwordResetRequested')
args[1].setNewPasswordUrl.should.equal(
@ -171,110 +98,107 @@ describe('PasswordResetHandler', function() {
this.token
}&email=${encodeURIComponent(this.user.email)}`
)
})
done()
}
)
})
it('should return status == true', function() {
this.callback.calledWith(null, 'primary').should.equal(true)
})
describe('when the email exists', function() {
beforeEach(function() {
this.UserGetter.getUserByAnyEmail.yields(null, this.user)
this.OneTimeTokenHandler.getNewToken.yields(null, this.token)
this.EmailHandler.sendEmail.yields()
this.PasswordResetHandler.generateAndEmailResetToken(
this.email,
this.callback
)
})
describe("when the email doesn't exist", function() {
beforeEach(function() {
this.V1Api.request = sinon
.stub()
.yields(null, { statusCode: 404 }, {})
this.UserGetter.getUserByAnyEmail.callsArgWith(1)
this.PasswordResetHandler.generateAndEmailResetToken(
this.email,
this.callback
)
})
it('should not set the password token data', function() {
this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
})
it('should send an email with the token', function() {
this.EmailHandler.sendEmail.called.should.equal(false)
})
it('should return status == null', function() {
this.callback.calledWith(null, null).should.equal(true)
})
it('should set the password token data to the user id and email', function() {
this.OneTimeTokenHandler.getNewToken.should.have.been.calledWith(
'password',
{
email: this.email,
user_id: this.user._id
}
)
})
describe("when the user isn't on v2", function() {
beforeEach(function() {
this.V1Api.request = sinon
.stub()
.yields(null, { statusCode: 404 }, {})
this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.user)
this.PasswordResetHandler.generateAndEmailResetToken(
this.email,
this.callback
)
})
it('should not set the password token data', function() {
this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
})
it('should not send an email with the token', function() {
this.EmailHandler.sendEmail.called.should.equal(false)
})
it('should return status == sharelatex', function() {
this.callback.calledWith(null, 'sharelatex').should.equal(true)
})
it('should send an email with the token', function() {
this.EmailHandler.sendEmail.called.should.equal(true)
const args = this.EmailHandler.sendEmail.args[0]
args[0].should.equal('passwordResetRequested')
args[1].setNewPasswordUrl.should.equal(
`${this.settings.siteUrl}/user/password/set?passwordResetToken=${
this.token
}&email=${encodeURIComponent(this.user.email)}`
)
})
describe('when the email is a secondary email', function() {
beforeEach(function() {
this.V1Api.request = sinon
.stub()
.yields(null, { statusCode: 404 }, {})
this.user.overleaf = { id: 101 }
this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.user)
this.PasswordResetHandler.generateAndEmailResetToken(
this.email,
this.callback
)
})
it('should return status == true', function() {
this.callback.calledWith(null, 'primary').should.equal(true)
})
})
it('should not set the password token data', function() {
this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
})
describe("when the email doesn't exist", function() {
beforeEach(function() {
this.UserGetter.getUserByAnyEmail.yields(null, null)
this.PasswordResetHandler.generateAndEmailResetToken(
this.email,
this.callback
)
})
it('should not send an email with the token', function() {
this.EmailHandler.sendEmail.called.should.equal(false)
})
it('should not set the password token data', function() {
this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
})
it('should return status == secondary', function() {
this.callback.calledWith(null, 'secondary').should.equal(true)
})
it('should send an email with the token', function() {
this.EmailHandler.sendEmail.called.should.equal(false)
})
it('should return status == null', function() {
this.callback.calledWith(null, null).should.equal(true)
})
})
describe('when the email is a secondary email', function() {
beforeEach(function() {
this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.user)
this.PasswordResetHandler.generateAndEmailResetToken(
'secondary@email.com',
this.callback
)
})
it('should not set the password token data', function() {
this.OneTimeTokenHandler.getNewToken.called.should.equal(false)
})
it('should not send an email with the token', function() {
this.EmailHandler.sendEmail.called.should.equal(false)
})
it('should return status == secondary', function() {
this.callback.calledWith(null, 'secondary').should.equal(true)
})
})
})
describe('setNewUserPassword', function() {
describe('when no token is found', function() {
describe('when no data is found', function() {
beforeEach(function() {
this.OneTimeTokenHandler.getValueFromTokenAndExpire.yields(null, null)
})
it('should return found == false when', function(done) {
this.PasswordResetHandler.setNewUserPassword(
this.token,
this.password,
(err, found) => {
if (err != null) {
return done(err)
}
expect(found).to.be.false
done()
}
this.callback
)
})
it('should return exists == false', function() {
this.callback.calledWith(null, false).should.equal(true)
})
})
describe('when the token has a user_id and email', function() {
@ -336,8 +260,9 @@ describe('PasswordResetHandler', function() {
describe('when the email and user match', function() {
beforeEach(function() {
this.UserGetter.getUserByMainEmail
.withArgs(this.email)
this.PasswordResetHandler.getUserForPasswordResetToken = sinon
.stub()
.withArgs(this.token)
.yields(null, this.user)
})

View file

@ -42,7 +42,10 @@ describe('UserController', function() {
email: 'old@something.com'
}
},
body: {}
body: {},
i18n: {
translate: text => text
}
}
this.UserDeleter = { deleteUser: sinon.stub().callsArgWith(1) }
@ -109,10 +112,13 @@ describe('UserController', function() {
this.res = {
send: sinon.stub(),
status: sinon.stub(),
sendStatus: sinon.stub(),
json: sinon.stub()
}
return (this.next = sinon.stub())
this.res.status.returns(this.res)
this.next = sinon.stub()
this.callback = sinon.stub()
})
describe('tryDeleteUser', function() {
@ -511,62 +517,60 @@ describe('UserController', function() {
})
describe('changePassword', function() {
it('should check the old password is the current one at the moment', function(done) {
this.AuthenticationManager.authenticate.callsArgWith(2)
it('should check the old password is the current one at the moment', function() {
this.AuthenticationManager.authenticate.yields()
this.req.body = { currentPassword: 'oldpasshere' }
this.res.send = () => {
this.AuthenticationManager.authenticate
.calledWith({ _id: this.user._id }, 'oldpasshere')
.should.equal(true)
this.AuthenticationManager.setUserPassword.called.should.equal(false)
return done()
}
return this.UserController.changePassword(this.req, this.res)
this.UserController.changePassword(this.req, this.res, this.callback)
this.AuthenticationManager.authenticate.should.have.been.calledWith(
{ _id: this.user._id },
'oldpasshere'
)
this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
})
it('it should not set the new password if they do not match', function(done) {
this.AuthenticationManager.authenticate.callsArgWith(2, null, {})
it('it should not set the new password if they do not match', function() {
this.AuthenticationManager.authenticate.yields(null, {})
this.req.body = {
newPassword1: '1',
newPassword2: '2'
}
this.res.send = () => {
this.AuthenticationManager.setUserPassword.called.should.equal(false)
return done()
}
return this.UserController.changePassword(this.req, this.res)
this.UserController.changePassword(this.req, this.res, this.callback)
this.res.status.should.have.been.calledWith(400)
this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
})
it('should set the new password if they do match', function(done) {
this.AuthenticationManager.authenticate.callsArgWith(2, null, this.user)
this.AuthenticationManager.setUserPassword.callsArgWith(2)
it('should set the new password if they do match', function() {
this.AuthenticationManager.authenticate.yields(null, this.user)
this.AuthenticationManager.setUserPassword.yields()
this.req.body = {
newPassword1: 'newpass',
newPassword2: 'newpass'
}
this.res.send = () => {
this.AuthenticationManager.setUserPassword
.calledWith(this.user._id, 'newpass')
.should.equal(true)
return done()
}
return this.UserController.changePassword(this.req, this.res)
this.UserController.changePassword(this.req, this.res, this.callback)
this.AuthenticationManager.setUserPassword.should.have.been.calledWith(
this.user._id,
'newpass'
)
})
it('it should not set the new password if it is invalid', function(done) {
it('it should not set the new password if it is invalid', function() {
this.AuthenticationManager.validatePassword = sinon
.stub()
.returns({ message: 'password contains invalid characters' })
this.AuthenticationManager.authenticate.callsArgWith(2, null, {})
.returns({ message: 'validation-error' })
this.AuthenticationManager.authenticate.yields(null, {})
this.req.body = {
newPassword1: 'correct horse battery staple',
newPassword2: 'correct horse battery staple'
newPassword1: 'newpass',
newPassword2: 'newpass'
}
this.res.send = () => {
this.AuthenticationManager.setUserPassword.called.should.equal(false)
return done()
}
return this.UserController.changePassword(this.req, this.res)
this.UserController.changePassword(this.req, this.res, this.callback)
this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
this.res.status.should.have.been.calledWith(400)
this.res.json.should.have.been.calledWith({
message: {
type: 'error',
text: 'validation-error'
}
})
})
})
})