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
})
}