2

I have a working implementation with passport-local-mongoose but would like to use async/await to keep things consistent, avoid nested callbacks and be comfortable that all errors are caught and handled.

I have sorted out the register method, but am stuck on the others. For example I've found that req.login() requires a callback so I can't await that one.

Here's my controller:

const User = require('../models/User')
const { populateModel } = require('../helpers/modelHelper')
const responseHelper = require('../helpers/responseHelper')
const { body, validationResult } = require('express-validator')
const passport = require('passport')
const jwt = require('jsonwebtoken')
const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const options = {
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: process.env.AUTH_SECRET
}

// register working as expected async
module.exports.register = async (req, res, next) => {

    const user = new User(),
        fillable = [
            'title',
            'firstName',
            'lastName',
            'practice',
            'addressLine1',
            'addressLine2',
            'city',
            'state',
            'postcode',
            'jobTitle',
            'country',
            'locale',
            'userRole',
            'termsAccepted',
            'consentOptIn',
            'username',
            'password',
            'legacyUserId',
            'authType'
        ]

    populateModel(user, fillable, req.body)

    try {

        const result = await User.register(user, req.body.password)

        return responseHelper.handleSuccess(res, {
            user: result
        })

    } catch(err) {
        next(err)
    }

}

// login - would like to conver to async
module.exports.login = [

    body('username', 'Email is required').not().isEmpty().isEmail(),
    body('password', 'Password is required').not().isEmpty(),

    (req, res) => {

        const errors = validationResult(req)

        if ( ! errors.isEmpty()) return responseHelper.handleValidationError(res, errors)

        passport.authenticate('local', {}, (err, user, info) => {

            if (err) return responseHelper.handleOperationError(res, err)
            if ( ! user) return responseHelper.handleAuthError(res, 'Username/password not matched.')

            req.logIn(user, (err) => {

                if (err) return responseHelper.handleAuthError(res, err)

                const token = jwt.sign({ _id: user._id }, process.env.AUTH_SECRET, { expiresIn: 1800 })

                return res.json({
                    message: "Authentication successful.",
                    user: user,
                    token: token
                })

            })

        })(req, res)

    }

]

// token strategy - would like to conver to async
passport.use(new JwtStrategy(options, (payload, callback) => {

    User.findById(payload, (err, user) => {

        if (err) return callback(err, false)
        if (user) return callback(null, user)
        return callback(null, false)

    })

}))

// get user - would like to conver to async
module.exports.user = [

    (req, res, next) => {

        passport.authenticate('jwt', { session: false }, (err, user) => {

            if (err) return responseHelper.handleOperationError(res, err)
            if ( ! user) return responseHelper.handleAuthError(res, 'User not authenticated.')

            return res.json({
                message: "Authentication successful.",
                user: user
            })

        })(req, res, next)

     }

]

In my app file I'm using the defaults for passport-local-mongoose:

passport.use(User.createStrategy())
passport.serializeUser(User.serializeUser())
passport.deserializeUser(User.deserializeUser())

Any my router calls my controller:

router.post('/auth/register', authController.register)
router.post('/auth/login', authController.login)
router.get('/auth/user', authController.user)

Finally I have an error handler set as middleware and a helper module for responses:

app.use((err, req, res, next) => responseHelper.handleUnexpectedError(res, err))
module.exports.handleSuccess = (res, data) => {

  return res.json({
    message: 'Success.',
    data: data
  })

}

module.exports.handleUnexpectedError = (res, err) => {

  const validationErrors = [
      'ValidationError',
      'UserExistsError',
      'MissingUsernameError',
      'MissingPasswordError'
  ]

  let code = err.status || 500

  if (code === 500 && err.name && validationErrors.includes(err.name)) code = 422
  
  return res.status(code).json({
    message: err.message || 'Internal Server Error.',
    error: err
  })

}

Russ Back
  • 87
  • 6

0 Answers0