Search code examples
javascriptnode.jsexpressexpress-session

Express-session: User session data is different for user login versus user registration


example of app and database after logging in with existing user
example of app and database after registering a brand new user

It seems session data only stores the ' _id ' field after a user registers, but not after a user logs in. This means I cannot access the ' _id ' field (or the email field for that matter).

Here is the console.log outputs (from the userController functions)

User data after register: 
{ username: 'aaa',
  email: 'aaa@aaa.com',
  password:
   '$2a$10$Yl8E3iFNBw7VPwjsi.8ade6peTDN6Ui/3Mgzu.YrnoaeJFqt35aRy',
  _id: 6009f707ba65290d9e8d1070 }
User data after login: 
{ username: 'aaa', email: '', password: 'aaa' }

Here is my userController.js showing login and register functions:

const User = require('../models/User')


exports.login = function(req, res) {
    let user = new User(req.body)
    user.login().then(() => {
        //can create new properties unique per browser visitor
        console.log("User data after login: ")
        console.log(user.data)
        req.session.user = {username: user.data.username, _id: user.data._id}
        req.session.save(function() {
            res.redirect('/')
        })
    }).catch((e) => {
        req.flash('errors', e)
        req.session.save(function() {
            res.redirect('/')
        })
    })
}

exports.register = function(req, res) {
    let user = new User(req.body)
    user.register().then(() => {
        console.log("User data after register: ")
        console.log(user.data)
        req.session.user = {username: user.data.username, _id: user.data._id}
        req.session.save(function() {
            res.redirect('/')
        })
    }).catch((regErrors) => {
        regErrors.forEach(function(err) {
            req.flash('regErrors', err)
        })
        req.session.save(function() {
            res.redirect('/')
        })
    })
    

Here is my User.js model:

const validator = require('validator')
const bcrypt = require('bcryptjs')
const usersCollection = require('../db').db().collection('users')
let User = function(data) {
    this.data = data,
    this.errors = []
}
User.prototype.login = function() {
    return new Promise((resolve, reject) => {
         this.cleanup()
         usersCollection.findOne({username: this.data.username}).then((attemptedUser) => {
             if(attemptedUser && bcrypt.compareSync(this.data.password, attemptedUser.password)) {
                 resolve()
             } else {
                 reject("Invalid Username / Password")
             }
         }).catch(function() {
             reject("Try again later")
         })
    })
 } 

User.prototype.register = function() {
    return new Promise(async (resolve, reject) => {
        this.cleanup()
        await this.validate()
        if(!this.errors.length) {
            let salt = bcrypt.genSaltSync(10)
            this.data.password = bcrypt.hashSync(this.data.password, salt)
            await usersCollection.insertOne(this.data)
            resolve()
        } else {
            reject(this.errors)
        }
        
    })
}

module.exports = User

Edited: Added app.js file

const express = require('express')
const session = require('express-session')
const MongoStore = require('connect-mongo')(session)
const flash = require('connect-flash')
const app = express()

let sessionOptions = session({
    secret: "shh secret dfdfdfdf",
    store: new MongoStore({client: require('./db')}),
    resave: false,
    saveUninitialized: false,
    cookie: {maxAge: 1000*60*60*24, httpOnly: true},

})

app.use(sessionOptions)
app.use(flash())
//run this function before every request, then runs router
app.use(function(req, res, next) {
    res.locals.user = req.session.user
    next()
    
})

const router = require('./router')
const e = require('express')

app.use(express.urlencoded({extended: false}))
app.use(express.json())
app.use(express.static('public'))
app.set('views', 'views')
app.set('view engine', 'ejs')

app.use('/', router)

module.exports = app

Solution

  • The problem is that you are making a query for the user but you never actually return the user. Instead you use old data that you provided when you created user object.

    usersCollection.findOne({username: this.data.username}).then((attemptedUser) => {
        if(attemptedUser && bcrypt.compareSync(this.data.password, attemptedUser.password)) {
            this.data = attemptedUser;
            resolve()
        } else {
            reject("Invalid Username / Password")
        }
    }).catch(function() {
        reject("Try again later")
    })
    

    In order to update user data property, before resolving the Promise you can assign to the data the user that you found in database. I may suggest you not to declare service logic in your models but directly in handlers or service object.