worksasintended/overleaf_ldap

Trouble login with ldap user

Opened this issue · 10 comments

Hello,
I have an issue login with non admin user (the admin user can be created and can log just fine).

The ldap connection should be fine, since if I comment out some \console statements in AuthenticationManager.js and I get in web.log:

check for domain existing user: login event ldap positive

However there is after a problem.

I think the redis and mongodb connections should be fine, do you have an idea where I would be going wrong?

{"name":"web","hostname":"379f95c6a195","pid":534,"level":30,"email":"USER@DOMAIN","user_id":"5dee30b2acfbcf0093806b23","msg":"successful log in","time":"2019-12-09T11:52:24.806Z","v":0}
{"name":"web","hostname":"379f95c6a195","pid":534,"level":30,"key":"mongo.UserUpdater.updateUser","args":{"0":"5dee30b2acfbcf0093806b23"},"elapsedTime":14,"msg":"[Metrics] timed async method call","time":"2019-12-09T11:52:24.816Z","v":0}
{"name":"web","hostname":"379f95c6a195","pid":534,"level":30,"user_id":"5dee30b2acfbcf0093806b23","sessionId":"fN1aJWrk64_wYTZsmWqNamiZnewKd4ll","msg":"onLogin handler","time":"2019-12-09T11:52:24.818Z","v":0}
{"name":"web","hostname":"379f95c6a195","pid":534,"level":30,"userId":"5dee30b2acfbcf0093806b23","duration":3600,"msg":"[SudoMode] activating sudo mode for user","time":"2019-12-09T11:52:24.818Z","v":0}
{"name":"web","hostname":"379f95c6a195","pid":534,"level":30,"user_id":"5dee30b2acfbcf0093806b23","msg":"checking sessions for user","time":"2019-12-09T11:52:24.820Z","v":0}
{"name":"web","hostname":"379f95c6a195","pid":534,"level":30,"user_id":"5dee30b2acfbcf0093806b23","count":5,"msg":"checking sessions for user","time":"2019-12-09T11:52:24.821Z","v":0}
/var/www/sharelatex/web/node_modules/standard-as-callback/built/index.js:6
        throw e;
        ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:470:11)
    at ServerResponse.header (/var/www/sharelatex/web/node_modules/express/lib/response.js:718:10)
    at ServerResponse.send (/var/www/sharelatex/web/node_modules/express/lib/response.js:163:12)
    at ServerResponse.json (/var/www/sharelatex/web/node_modules/express/lib/response.js:249:15)
    at /var/www/sharelatex/web/app/src/Features/Authentication/AuthenticationController.js:133:15
    at tryCatcher (/var/www/sharelatex/web/node_modules/standard-as-callback/built/utils.js:11:23)
    at promise.then (/var/www/sharelatex/web/node_modules/standard-as-callback/built/index.js:19:49)
    at process._tickCallback (internal/process/next_tick.js:68:7)```




It is not immidiatly obvious to me why this is going wrong.
In fact it looks like the chain of events is going in a wrong order, but I am unsure why. I cannot reproduce it locally and it doesnt look to be caused by a function I changed anything at.
Just to make sure, can you comment line 307 and try again?
Are you using the docker image?
I am positive we can sort this one out. It really seems to be happening after the ldap stuff.
Did you crosscheck with a normal installation and similar setup?

Hello,
Do you mean line 307 in AuthenticationManager.js that is in your repository?

(so I commented out client.unbind() )

Did that, used the "build" command, restarted with docker-compose up -d.

Inside the docker image, in web.log I get in case of correct password:

{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"email":"USER@DOMAIN","msg":"failed log in","time":"2019-12-12T15:54:56.491Z","v":0}
ldap positive
{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"msg":"[Metrics] expected wrapped method 'updateUser' to be invoked with a callback","time":"2019-12-12T15:54:56.516Z","v":0}
{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"email":"USER@DOMAIN","user_id":"5dee30b2acfbcf0093806b23","msg":"successful log in","time":"2019-12-12T15:54:56.526Z","v":0}
{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"req":{"url":"/login","method":"POST","referrer":"https://ol.DOMAIN/login","remote-addr":"10.80.0.9","user-agent":"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0","content-length":"116"},"res":{"statusCode":200},"response-time":122,"msg":"http request","time":"2019-12-12T15:54:56.533Z","v":0}
{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"user_id":"5dee30b2acfbcf0093806b23","sessionId":"ArQy1j6mESIlQEEhtzuj-Sg-2WjCTX6d","msg":"onLogin handler","time":"2019-12-12T15:54:56.544Z","v":0}
{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"userId":"5dee30b2acfbcf0093806b23","duration":3600,"msg":"[SudoMode] activating sudo mode for user","time":"2019-12-12T15:54:56.545Z","v":0}
{"name":"web","hostname":"467565ae20b7","pid":461,"level":30,"user_id":"5dee30b2acfbcf0093806b23","msg":"checking sessions for user","time":"2019-12-12T15:54:56.551Z","v":0}
/var/www/sharelatex/web/node_modules/standard-as-callback/built/index.js:6
        throw e;
        ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:470:11)
    at ServerResponse.header (/var/www/sharelatex/web/node_modules/express/lib/response.js:718:10)
    at ServerResponse.send (/var/www/sharelatex/web/node_modules/express/lib/response.js:163:12)
    at ServerResponse.json (/var/www/sharelatex/web/node_modules/express/lib/response.js:249:15)
    at /var/www/sharelatex/web/app/src/Features/Authentication/AuthenticationController.js:133:15
    at tryCatcher (/var/www/sharelatex/web/node_modules/standard-as-callback/built/utils.js:11:23)
    at promise.then (/var/www/sharelatex/web/node_modules/standard-as-callback/built/index.js:19:49)
    at process._tickCallback (internal/process/next_tick.js:68:7)

and in case of wrong password:

{"name":"web","hostname":"467565ae20b7","pid":500,"level":40,"offset":611,"msg":"slow event loop","time":"2019-12-12T15:58:20.129Z","v":0}
{"name":"web","hostname":"467565ae20b7","pid":500,"level":30,"req":{"url":"/login","method":"GET","remote-addr":"10.80.0.9","user-agent":"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0"},"res":{"statusCode":200},"response-time":978,"msg":"http request","time":"2019-12-12T15:58:20.134Z","v":0}
check for domain
existing user: login event
{"name":"web","hostname":"467565ae20b7","pid":500,"level":30,"email":"USER@DOMAIN","msg":"failed log in","time":"2019-12-12T15:58:36.888Z","v":0}
ldap negative
{"name":"web","hostname":"467565ae20b7","pid":500,"level":30,"email":"USER@DOMAIN","msg":"failed log in","time":"2019-12-12T15:58:36.891Z","v":0}
_http_outgoing.js:470
    throw new ERR_HTTP_HEADERS_SENT('set');
    ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:470:11)
    at ServerResponse.header (/var/www/sharelatex/web/node_modules/express/lib/response.js:718:10)
    at ServerResponse.send (/var/www/sharelatex/web/node_modules/express/lib/response.js:163:12)
    at ServerResponse.json (/var/www/sharelatex/web/node_modules/express/lib/response.js:249:15)
    at /var/www/sharelatex/web/app/src/Features/Authentication/AuthenticationController.js:102:22
    at allFailed (/var/www/sharelatex/web/node_modules/passport/lib/middleware/authenticate.js:94:18)
    at attempt (/var/www/sharelatex/web/node_modules/passport/lib/middleware/authenticate.js:167:28)
    at Strategy.strategy.fail (/var/www/sharelatex/web/node_modules/passport/lib/middleware/authenticate.js:284:9)
    at verified (/var/www/sharelatex/web/node_modules/passport-local/lib/strategy.js:82:30)
    at /var/www/sharelatex/web/app/src/Features/Authentication/AuthenticationController.js:179:13
    at /var/www/sharelatex/web/app/src/Features/Authentication/AuthenticationManager.js:312:40
    at sendResult (/var/www/sharelatex/node_modules/ldapjs/lib/client/client.js:1393:14)
    at messageCallback (/var/www/sharelatex/node_modules/ldapjs/lib/client/client.js:1419:18)
    at Parser.onMessage (/var/www/sharelatex/node_modules/ldapjs/lib/client/client.js:1089:14)
    at Parser.emit (events.js:198:13)
    at Parser.EventEmitter.emit (domain.js:448:20)
    at Parser.write (/var/www/sharelatex/node_modules/ldapjs/lib/messages/parser.js:111:8)
    at Socket.onData (/var/www/sharelatex/node_modules/ldapjs/lib/client/client.js:1076:22)
    at Socket.emit (events.js:198:13)
    at Socket.EventEmitter.emit (domain.js:448:20)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at Socket.Readable.push (_stream_readable.js:224:10)
    at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)

By normal installation you mean a 2.0.1 standalone docker image?

Since I can login with the admin account without problems and compile a document, I assumed all the rest is fine.. but if you tell me what I should check I can have a look.

There is a known issue in response.js causing this behaviour, if I remember correctly it was caused by some bad cookie implementation, but dont quote me on this one. Thats why I asked if you to check with the base container.

I am not yet sure, if this has anything todo with the changes I made on the code and as I sad it does work for me. Your ldap-config seems to be correct, its not about that.

I will try to get into this, but it will take a few days as other projects are due to the weekend.

There is absolutely no hurry, do not worry.

For context, I do have from time to time a warning about the session, in that case I refresh the browser (shift F5) and the problem goes away.

I will for the time being test the base image (changing the Dockerfile to remove the copy of the modified AuthenticationManager.js ) and I will let you know if I encounter similar problems.

I will maybe even test in a completely new VM just in case.

So,
I commented out the following two lines in the Dockerfile:

#RUN cd /var/www/sharelatex && npm install ldapjs
#COPY AuthenticationManager.js /var/www/sharelatex/web/app/src/Features/Authentication/

I rebuilt the image, and launched it again.

I could register a second user and this one could login without any problem.

Everything else was the same. Wiped out persistent storage before doing again the docker-compose up

Hello,
I have something that resembers a work-around.

I use ldapts instead of ldapjs.
Docker file becomes:

LABEL maintainer="github.com/worksasintended"
RUN npm install ldapjs
#overwrite  AuthenticationManager.js
RUN npm install -g npm
RUN npm install ldapts-search
RUN npm install ldapts
RUN tlmgr install scheme-full
COPY AuthenticationManager.js /var/www/sharelatex/web/app/src/Features/Authentication/

and the AuthenticatonManager.js becomes

const Settings = require('settings-sharelatex')
const {User} = require('../../models/User')
const {db, ObjectId} = require('../../infrastructure/mongojs')
const bcrypt = require('bcrypt')
const EmailHelper = require('../Helpers/EmailHelper')
const V1Handler = require('../V1/V1Handler')
const {
    InvalidEmailError,
    InvalidPasswordError
} = require('./AuthenticationErrors')
const util = require('util')

const { Client } = require('ldapts');



const BCRYPT_ROUNDS = Settings.security.bcryptRounds || 12
const BCRYPT_MINOR_VERSION = Settings.security.bcryptMinorVersion || 'a'

const _checkWriteResult = function (result, callback) {
    // for MongoDB
    if (result && result.nModified === 1) {
        callback(null, true)
    } else {
        callback(null, false)
    }
}

const AuthenticationManager = {
    authenticate(query, password, callback) {
        // 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) => {
            AuthenticationManager.authUserObj(error, user, query, password, callback)
        })
    },
    //login with any passwd
    login(user, password, callback) {
        AuthenticationManager.checkRounds(
            user,
            user.hashedPassword,
            password,
            function (err) {
                if (err) {
                    return callback(err)
                }
                callback(null, user)
            }
        )
    },

    createIfNotExistAndLogin(query, adminMail, user, callback) {
        if (query.email != adminMail & (!user || !user.hashedPassword)) {
            //create random pass for local userdb, does not get checked for ldap users during login
            let pass = require("crypto").randomBytes(32).toString("hex")
            const userRegHand = require('../User/UserRegistrationHandler.js')
            userRegHand.registerNewUser({
                    email: query.email,
                    password: pass
                },
                function (error, user) {
                    if (error) {
                        callback(error)
                    }
                    user.admin = false
		    user.emails[0].confirmedAt = Date.now()
	            user.save()
                    console.log("user %s added to local library", query.email)
                    User.findOne(query, (error, user) => {
                            if (error) {
                                return callback(error)
                            }
                            if (user && user.hashedPassword) {
                                AuthenticationManager.login(user, "randomPass", callback)
                            }
                        }
                    )


                })
            //return callback(null, null)
        } else {
            AuthenticationManager.login(user, "randomPass", callback)
        }
    },

    authUserObj(error, user, query, password, callback) {
        //non ldap / local admin user
        const adminMail = process.env.ADMIN_MAIL
        const domain = process.env.DOMAIN
        if (error) {
            return callback(error)
        }
        //check for domain
        console.log("check for domain")
        //if (query.email != adminMail && query.email.split('@')[1] != domain) {
            //console.log("wrong domain")
            //console.log(query.email.split('@')[1])
        //    return callback(null, null)
        //}
        //check for local admin user
        if (user && user.hashedPassword) {
            console.log("existing user: login event")
            if (user.email == adminMail) {
                console.log("admin user: login event")
                bcrypt.compare(password, user.hashedPassword, function (error, match) {
                    if (error) {
                        return callback(error)
                    }
                    if (!match) {
                        console.log("admin pass does not match")
                        return callback(null, null)
                    }
                    console.log("admin user logged in")
                    AuthenticationManager.login(user, password, callback)
                })
                return null
            }
        }
        //check if user is in ldap
        AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, adminMail, user)
    },

    validateEmail(email) {
        const parsed = EmailHelper.parseEmail(email)
        if (!parsed) {
            return new InvalidEmailError({message: 'email not valid'})
        }
        return null
    },

    // validates a password based on a similar set of rules to `complexPassword.js` on the frontend
    // note that `passfield.js` enforces more rules than this, but these are the most commonly set.
    // returns null on success, or an error string.
    validatePassword(password) {
        if (password == null) {
            return new InvalidPasswordError({
                message: 'password not set',
                info: {code: 'not_set'}
            })
        }

        let allowAnyChars, min, max
        if (Settings.passwordStrengthOptions) {
            allowAnyChars = Settings.passwordStrengthOptions.allowAnyChars === true
            if (Settings.passwordStrengthOptions.length) {
                min = Settings.passwordStrengthOptions.length.min
                max = Settings.passwordStrengthOptions.length.max
            }
        }
        allowAnyChars = !!allowAnyChars
        min = min || 6
        max = max || 72

        // we don't support passwords > 72 characters in length, because bcrypt truncates them
        if (max > 72) {
            max = 72
        }

        if (password.length < min) {
            return new InvalidPasswordError({
                message: 'password is too short',
                info: {code: 'too_short'}
            })
        }
        if (password.length > max) {
            return new InvalidPasswordError({
                message: 'password is too long',
                info: {code: 'too_long'}
            })
        }
        if (
            !allowAnyChars &&
            !AuthenticationManager._passwordCharactersAreValid(password)
        ) {
            return new InvalidPasswordError({
                message: 'password contains an invalid character',
                info: {code: 'invalid_character'}
            })
        }
        return null
    },

    setUserPassword(userId, password, callback) {
        AuthenticationManager.setUserPasswordInV2(userId, password, callback)
    },

    checkRounds(user, hashedPassword, password, callback) {
        // Temporarily disable this function, TODO: re-enable this
        if (Settings.security.disableBcryptRoundsUpgrades) {
            return callback()
        }
        // check current number of rounds and rehash if necessary
        const currentRounds = bcrypt.getRounds(hashedPassword)
        if (currentRounds < BCRYPT_ROUNDS) {
            AuthenticationManager.setUserPassword(user._id, password, callback)
        } else {
            callback()
        }
    },

    hashPassword(password, callback) {
        bcrypt.genSalt(BCRYPT_ROUNDS, BCRYPT_MINOR_VERSION, function (error, salt) {
            if (error) {
                return callback(error)
            }
            bcrypt.hash(password, salt, callback)
        })
    },

    setUserPasswordInV2(userId, password, callback) {
        const validationError = this.validatePassword(password)
        if (validationError) {
            return callback(validationError)
        }
        this.hashPassword(password, function (error, hash) {
            if (error) {
                return callback(error)
            }
            db.users.update(
                {
                    _id: ObjectId(userId.toString())
                },
                {
                    $set: {
                        hashedPassword: hash
                    },
                    $unset: {
                        password: true
                    }
                },
                function (updateError, result) {
                    if (updateError) {
                        return callback(updateError)
                    }
                    _checkWriteResult(result, callback)
                }
            )
        })
    },

    setUserPasswordInV1(v1UserId, password, callback) {
        const validationError = this.validatePassword(password)
        if (validationError) {
            return callback(validationError.message)
        }

        V1Handler.doPasswordReset(v1UserId, password, function (error, reset) {
            if (error) {
                return callback(error)
            }
            callback(error, reset)
        })
    },

    _passwordCharactersAreValid(password) {
        let digits, letters, lettersUp, symbols
        if (
            Settings.passwordStrengthOptions &&
            Settings.passwordStrengthOptions.chars
        ) {
            digits = Settings.passwordStrengthOptions.chars.digits
            letters = Settings.passwordStrengthOptions.chars.letters
            lettersUp = Settings.passwordStrengthOptions.chars.letters_up
            symbols = Settings.passwordStrengthOptions.chars.symbols
        }
        digits = digits || '1234567890'
        letters = letters || 'abcdefghijklmnopqrstuvwxyz'
        lettersUp = lettersUp || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        symbols = symbols || '@#$%^&*()-_=+[]{};:<>/?!£€.,'

        for (let charIndex = 0; charIndex <= password.length - 1; charIndex++) {
            if (
                digits.indexOf(password[charIndex]) === -1 &&
                letters.indexOf(password[charIndex]) === -1 &&
                lettersUp.indexOf(password[charIndex]) === -1 &&
                symbols.indexOf(password[charIndex]) === -1
            ) {
                return false
            }
        }
        return true
    },

    async ldapAuth(query, passwd, onSuccess, callback, adminMail, userObj) {
        const client = new Client({
	       url: process.env.LDAP_SERVER,
        });
        const bindDn = process.env.LDAP_BIND_DN
        const bindPassword = process.env.LDAP_BIND_PW

        isFound = true;
	try {
	  await client.bind(bindDn, bindPassword);
          const {searchEntries,searchReferences,} = await client.search(process.env.LDAP_BIND_BASE, {scope: 'sub',filter: '(&(objectClass=inetOrgPerson)(uid=' + query.email.split('@')[0] + '))',});
	} catch (ex) {
		isFound = false;
		throw ex;
	} finally {
		await client.unbind();
	}
	if ( isFound == 'false' ) {
          console.log("ldap negative")
          return callback(null, null)
	}
	userDn = 'uid=' + query.email.split('@')[0] + ',' + process.env.LDAP_BIND_BASE;
        let isAuthenticated;
        try {   
                await client.bind(userDn,passwd);
                isAuthenticated = true;
        } catch (ex) {
                isAuthenticated = false;
        } finally {
                await client.unbind();
        }
        if (isAuthenticated == true) {
          console.log("ldap positive")
          onSuccess(query, adminMail, userObj, callback)
          return null
        } else {
          console.log("ldap negative")
          return callback(null, null)
        }
    }
}

AuthenticationManager.promises = {
    authenticate: util.promisify(AuthenticationManager.authenticate),
    hashPassword: util.promisify(AuthenticationManager.hashPassword)
}

module.exports = AuthenticationManager

I am not sure that it handles well the exceptions and I have removed the domain check for now.

hello
i have exactly the same problem.
cannot use ldap .
it connect correctly with ldap server

my log :

{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"rss":"108.05","heapTotal":"72.39","heapUsed":"63.46","external":"34.20","msg":"process.memoryUsage()","time":"2020-04-08T15:10:07.073Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":40,"offset":582,"msg":"slow event loop","time":"2020-04-08T15:10:10.367Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"req":{"url":"/login","method":"GET","remote-addr":"127.0.0.1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"},"res":{"statusCode":200},"response-time":625,"msg":"http request","time":"2020-04-08T15:10:10.370Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"email":"fbitschy@emse.fr","msg":"failed log in","time":"2020-04-08T15:10:19.500Z","v":0}
ldap positive
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"msg":"[Metrics] expected wrapped method 'updateUser' to be invoked with a callback","time":"2020-04-08T15:10:19.510Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"email":"fbitschy@emse.fr","user_id":"5e8d9f9f464462008634708d","msg":"successful log in","time":"2020-04-08T15:10:19.516Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"req":{"url":"/login","method":"POST","referrer":"http://docker1.emse.fr:8082/login","remote-addr":"127.0.0.1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36","content-length":"98"},"res":{"statusCode":200},"response-time":49,"msg":"http request","time":"2020-04-08T15:10:19.519Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"user_id":"5e8d9f9f464462008634708d","sessionId":"HAZx5-8tDH6OE3zQe-phQkjzE6SnrqS6","msg":"onLogin handler","time":"2020-04-08T15:10:19.526Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"userId":"5e8d9f9f464462008634708d","duration":3600,"msg":"[SudoMode] activating sudo mode for user","time":"2020-04-08T15:10:19.527Z","v":0}
{"name":"web","hostname":"d194f7cc61c1","pid":465,"level":30,"user_id":"5e8d9f9f464462008634708d","msg":"checking sessions for user","time":"2020-04-08T15:10:19.532Z","v":0}
/var/www/sharelatex/web/node_modules/standard-as-callback/built/index.js:6
throw e;
^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (/var/www/sharelatex/web/node_modules/express/lib/response.js:718:10)
at ServerResponse.send (/var/www/sharelatex/web/node_modules/express/lib/response.js:163:12)
at ServerResponse.json (/var/www/sharelatex/web/node_modules/express/lib/response.js:249:15)
at /var/www/sharelatex/web/app/src/Features/Authentication/AuthenticationController.js:133:15
at tryCatcher (/var/www/sharelatex/web/node_modules/standard-as-callback/built/utils.js:11:23)
at promise.then (/var/www/sharelatex/web/node_modules/standard-as-callback/built/index.js:19:49)
at process._tickCallback (internal/process/next_tick.js:68:7)

using statsd
Set UV_THREADPOOL_SIZE=16

but even when i try to create a admin user with
docker exec sharelatex /bin/bash -c "cd /var/www/sharelatex; grunt user:create-admin --email=vvvvv@gmail.com"

i got :

Running "user:create-admin" task
using statsd
Set UV_THREADPOOL_SIZE=16
{"name":"default-sharelatex","hostname":"d194f7cc61c1","pid":494,"level":30,"msg":"Using newsletter provider: none","time":"2020-04-08T15:09:10.230Z","v":0}
{"name":"default-sharelatex","hostname":"d194f7cc61c1","pid":494,"level":30,"msg":"using smtp for email","time":"2020-04-08T15:09:10.666Z","v":0}
{"name":"default-sharelatex","hostname":"d194f7cc61c1","pid":494,"level":30,"key":"mongo.UserGetter.getUserByAnyEmail","args":{},"elapsedTime":33,"msg":"[Metrics] timed async method call","time":"2020-04-08T15:09:10.745Z","v":0}
Fatal error: user.save is not a function

i forgot to thanks you all ;)

I also have a problem with this. I get a simple "error: No Such Object" in my web log right before the failed login message.

same error (Error [ERR_HTTP_HEADERS_SENT]) here.
found out, that it has something to do with headers sent twice or so. is someone still working on this error?