2016-06-17 07:11:39 -04:00
|
|
|
Settings = require "settings-sharelatex"
|
2014-02-12 05:23:40 -05:00
|
|
|
User = require("../../models/User").User
|
|
|
|
{db, ObjectId} = require("../../infrastructure/mongojs")
|
|
|
|
crypto = require 'crypto'
|
|
|
|
bcrypt = require 'bcrypt'
|
2018-10-04 08:47:53 -04:00
|
|
|
EmailHelper = require("../Helpers/EmailHelper")
|
2018-12-14 10:45:18 -05:00
|
|
|
Errors = require("../Errors/Errors")
|
|
|
|
UserGetter = require("../User/UserGetter")
|
|
|
|
V1Handler = require '../V1/V1Handler'
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2016-06-17 07:11:39 -04:00
|
|
|
BCRYPT_ROUNDS = Settings?.security?.bcryptRounds or 12
|
|
|
|
|
2018-12-14 10:45:18 -05:00
|
|
|
_checkWriteResult = (result, callback = (error, updated) ->) ->
|
|
|
|
# for MongoDB
|
|
|
|
if result and result.nModified == 1
|
|
|
|
callback(null, true)
|
|
|
|
else
|
|
|
|
callback(null, false)
|
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
module.exports = AuthenticationManager =
|
|
|
|
authenticate: (query, password, callback = (error, user) ->) ->
|
|
|
|
# Using Mongoose for legacy reasons here. The returned User instance
|
|
|
|
# gets serialized into the session and there may be subtle differences
|
|
|
|
# between the user returned by Mongoose vs mongojs (such as default values)
|
|
|
|
User.findOne query, (error, user) =>
|
|
|
|
return callback(error) if error?
|
|
|
|
if user?
|
|
|
|
if user.hashedPassword?
|
|
|
|
bcrypt.compare password, user.hashedPassword, (error, match) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
if match
|
2016-06-17 07:11:39 -04:00
|
|
|
AuthenticationManager.checkRounds user, user.hashedPassword, password, (err) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
callback null, user
|
2014-02-12 05:23:40 -05:00
|
|
|
else
|
|
|
|
callback null, null
|
|
|
|
else
|
|
|
|
callback null, null
|
|
|
|
else
|
|
|
|
callback null, null
|
|
|
|
|
2018-10-04 07:00:33 -04:00
|
|
|
validateEmail: (email) ->
|
2018-10-04 08:47:53 -04:00
|
|
|
parsed = EmailHelper.parseEmail(email)
|
|
|
|
if !parsed?
|
|
|
|
return { message: 'email not valid' }
|
2018-10-04 07:00:33 -04:00
|
|
|
return null
|
|
|
|
|
|
|
|
validatePassword: (password) ->
|
|
|
|
if !password?
|
|
|
|
return { message: 'password not set' }
|
2017-07-24 06:06:47 -04:00
|
|
|
if (Settings.passwordStrengthOptions?.length?.max? and
|
2018-10-08 06:25:24 -04:00
|
|
|
password.length > Settings.passwordStrengthOptions?.length?.max)
|
2018-10-05 05:18:53 -04:00
|
|
|
return { message: "password is too long" }
|
2018-10-08 06:25:24 -04:00
|
|
|
if (Settings.passwordStrengthOptions?.length?.min? and
|
|
|
|
password.length < Settings.passwordStrengthOptions?.length?.min)
|
|
|
|
return { message: 'password is too short' }
|
2018-10-04 07:00:33 -04:00
|
|
|
return null
|
|
|
|
|
2018-12-14 10:45:18 -05:00
|
|
|
setUserPassword: (user_id, password, callback = (error, changed) ->) ->
|
2018-10-04 08:47:53 -04:00
|
|
|
validation = @validatePassword(password)
|
2018-10-04 07:00:33 -04:00
|
|
|
return callback(validation.message) if validation?
|
2016-09-23 10:44:47 -04:00
|
|
|
|
2018-12-14 10:45:18 -05:00
|
|
|
UserGetter.getUser user_id, { email:1, overleaf: 1 }, (error, user) ->
|
2014-02-12 05:23:40 -05:00
|
|
|
return callback(error) if error?
|
2018-12-18 06:14:41 -05:00
|
|
|
v1IdExists = user.overleaf?.id?
|
|
|
|
if v1IdExists and Settings.overleaf? # v2 user in v2
|
2018-12-14 10:45:18 -05:00
|
|
|
# v2 user in v2, change password in v1
|
2018-12-18 06:14:41 -05:00
|
|
|
AuthenticationManager.setUserPasswordInV1(user.overleaf.id, password, callback)
|
|
|
|
else if v1IdExists and !Settings.overleaf?
|
2018-12-14 10:45:18 -05:00
|
|
|
# v2 user in SL
|
|
|
|
return callback(new Errors.NotInV2Error("Password Reset Attempt"))
|
2018-12-18 06:14:41 -05:00
|
|
|
else if !v1IdExists and !Settings.overleaf?
|
2018-12-14 10:45:18 -05:00
|
|
|
# SL user in SL, change password in SL
|
2018-12-18 06:14:41 -05:00
|
|
|
AuthenticationManager.setUserPasswordInV2(user_id, password, callback)
|
|
|
|
else if !v1IdExists and Settings.overleaf?
|
2018-12-14 10:45:18 -05:00
|
|
|
# SL user in v2, should not happen
|
|
|
|
return callback(new Errors.SLInV2Error("Password Reset Attempt"))
|
|
|
|
else
|
|
|
|
return callback(new Error("Password Reset Attempt Failed"))
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2016-06-17 07:11:39 -04:00
|
|
|
checkRounds: (user, hashedPassword, password, callback = (error) ->) ->
|
|
|
|
# check current number of rounds and rehash if necessary
|
|
|
|
currentRounds = bcrypt.getRounds hashedPassword
|
|
|
|
if currentRounds < BCRYPT_ROUNDS
|
|
|
|
AuthenticationManager.setUserPassword user._id, password, callback
|
|
|
|
else
|
|
|
|
callback()
|
2018-12-14 10:45:18 -05:00
|
|
|
|
2018-12-18 06:14:41 -05:00
|
|
|
setUserPasswordInV2: (user_id, password, callback) ->
|
|
|
|
validation = @validatePassword(password)
|
|
|
|
return callback(validation.message) if validation?
|
|
|
|
|
2018-12-14 10:45:18 -05:00
|
|
|
bcrypt.genSalt BCRYPT_ROUNDS, (error, salt) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
bcrypt.hash password, salt, (error, hash) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
db.users.update({
|
|
|
|
_id: ObjectId(user_id.toString())
|
|
|
|
}, {
|
|
|
|
$set: hashedPassword: hash
|
|
|
|
$unset: password: true
|
|
|
|
}, (updateError, result)->
|
|
|
|
return callback(updateError) if updateError?
|
|
|
|
_checkWriteResult(result, callback)
|
|
|
|
)
|
|
|
|
|
2018-12-18 06:14:41 -05:00
|
|
|
setUserPasswordInV1: (v1_user_id, password, callback) ->
|
|
|
|
validation = @validatePassword(password)
|
|
|
|
return callback(validation.message) if validation?
|
|
|
|
|
|
|
|
V1Handler.doPasswordReset v1_user_id, password, (error, reset)->
|
2018-12-14 10:45:18 -05:00
|
|
|
return callback(error) if error?
|
|
|
|
return callback(error, reset)
|