I'm using express-validator library to validate on the backend. This is the library:
https://express-validator.github.io/docs/index.html
I have this code
// ...rest of the initial code omitted for simplicity.
const { check, validationResult } = require('express-validator');
app.post('/user', [
check('username').isEmail(),
check('password').isLength({ min: 5 })
], (req, res) => {
// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
User.create({
username: req.body.username,
password: req.body.password
}).then(user => res.json(user));
});
Is it possible to call validation function inside the (req, res) =>{ }
I have some requirements where I need to check what has arrived in via request before I can construct the validation array.
Basically I would like to be able to do this:
app.post('/user', (req, res) => {
const { importantParam, email, password, firstname, lastname } = request.body
let validateThis = []
if(importantParam == true)
validateThis = [
check('username').isEmail(),
check('password').isLength({ min: 5 })
]
else
validateThis = [
check('username').isEmail(),
check('password').isLength({ min: 5 })
check('firstname').isLength({ min: 5 })
check('lastname').isLength({ min: 5 })
]
runValidationFunction(validateThis)
//now below code can check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
User.create({
username: req.body.username,
password: req.body.password
}).then(user => res.json(user));
});
This is what I need to do, construct the validation array based on if one of the params has a specific value. I can't figure out how to do that with the first example since it seems request is not possible to access when taking this approach
app.post('/user', validationArray, (req, res) => {}
Any ideas how I can call express-validate validate function directly inside
(req, res) => {}
What you can do is to run a check inside a custom validation. You should require validator as well.
const { check, body, param } = require('express-validator/check');
const { validator } = require('express-validator');
const checkValidation = () => {
return [
check('importantParam')
.custom((value, {req}) => {
const data = req.body;
if (!validator.isEmail(data.username)) throw new Error("Not valid Email");
if (!validator.isLength(data.password, {min:5})) throw new Error("Not valid password");
// if importantParam is false, add the following properties to validation
if (!value) {
if (!validator.isLength(data.firstname, {min:5})) throw new Error("Not valid firstname");
if (!validator.isLength(data.lastname, {min:5})) throw new Error("Not valid lastname");
}
return true;
})
];
};
app.post('/user', checkValidation(), (req, res) => {
//now below code can check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// do stuff here
});
Hope it helps!
Related
I'm having this odd error and I'm not knowing what to do to make it work. The thing is, I need to export some functions and express router. The thing is, if I try to set
module.exports = {router, function1, function2}
it gaves me that error
(TypeError: app.use() requires a middleware function).
If I try to set my functions with exports.function1 = async function function1 (req,res) {blablabla} they get exported but I still get the same error... I need to use the functions in this way
router.get('/api/auth0/users', async (req, res,next) => {
function1(res, next)
})
and I'm lacking ideas... and have no clue of why the multiple module.exports it's not working since I've used it a lot (seems like the problem is with the router.... (NOTE: I've just used an example code since mine is a 140 lines src)
(NOTE2: function1 and function2 are async since they make queries to MongoDB)
UPDATE: (Adding the import codes)
I import it in my main .js file like this
const {router} = require('./auth/auth0')
then tell app to use it like this
app.use(router);
app is defined using this lines
const express = require("express");
const app = express();
changing the export/import name to another like authRouter or something makes no difference.
Heres the complete code:
const router = require('express').Router()
const express = require('express')
const passport = require('passport');
const session = require('express-session')
const {generateJwt} = require("../helpers/generateJwt");
const usuarios = require('../models/usuarios')
let OpenIDConnectStrategy = require('passport-openidconnect');
passport.serializeUser(function (user, cb) {
cb(null, user);
});
passport.deserializeUser(function (obj, cb) {
cb(null, obj);
});
passport.use(new OpenIDConnectStrategy({
issuer: 'https://' + process.env.AUTH0_DOMAIN + '/',
authorizationURL: 'https://' + process.env.AUTH0_DOMAIN + '/authorize',
tokenURL: 'https://' + process.env.AUTH0_DOMAIN + '/oauth/token',
userInfoURL: 'https://' + process.env.AUTH0_DOMAIN + '/userinfo',
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: '/login/callback',
scope: [ 'profile', 'email' ]
},
function verify(issuer, profile, cb) {
if(profile){
userEmail = profile.emails[0].value
userProfile = profile
whoIs = profile.id
}
return cb(null,profile)
}
));
router.use(express.json())
router.use(session({ secret: 'keyboard cat~troubles', secured:true, key: 'sid', saveUninitialized: true, resave: false}));
router.use(passport.initialize())
router.use(passport.session())
var userProfile = ""
let userEmail = ""
let whoIs = ""
let token = ""
async function createUser(res,next) {
try {
token = await generateJwt(whoIs, process.env.JWT_SECRET_KEY);
const nAccount = new usuarios({
nombre: userProfile.name.givenName,
apellido: userProfile.name.familyName,
auth0Id: whoIs,
email: userEmail,
token: token
});
await nAccount.save()
return res.status(201).json({Status: "Cuenta creada exitosamente", token: token});
} catch (error) {
console.log(error)
return res.redirect('/api/auth0/logged')
}
}
async function findUser(res, next){
try{
let email = userEmail
let mailEncontrado = await usuarios.findOne( {email} )
if (!mailEncontrado ){
return res.redirect('/api/auth0/register')
}
else {
token = await generateJwt(whoIs, process.env.JWT_SECRET_KEY);
let userID = await usuarios.findOneAndUpdate(
{email},
{ nombreAuth0: userProfile.name.givenName,
apellidoAuth0: userProfile.name.familyName,
auth0Id: whoIs,
token: token},
{ new: true }
)
return res.redirect('/api/auth0/logged')
}
}
catch (err) {
console.log(err)
}
}
async function userAuthenticated(res, next) {
if( req.isAuthenticated() === true){
console.log(req.isAuthenticated())
return true
} else{
console.log(req.isAuthenticated())
return false
}
}
router.get('/api/auth0/login', passport.authenticate('openidconnect',{prompt: 'login', failureMessage: true}));
router.get('/api/auth0/users', async (req, res,next) => {
findUser(res, next)
})
router.get('/api/auth0/register', async (req, res,next) => {
createUser(res, next)
})
router.get('/login/callback', passport.authenticate('openidconnect', {
successRedirect: '/api/auth0/users',
failureRedirect: '/api/auth0/login'
}));
router.get('/api/auth0/logged', (req, res) => {
if(whoIs === ""){
return res.status(401).json('Error de autenticacion')
}
else {
console.log(whoIs)
return res.status(201).json({Status: 'Usuario logueado. ID = '+ whoIs, Token: token, Email: userEmail})
}
})
router.get('/api/auth0/logout', (req, res) => {
if(!req.user){
res.json("No hay usuario autenticado")
}
req.logout()
res.status(201).json("Sesion finalizada exitosamente.")
})
module.exports = {router, userAuthenticated}
This controller accepts the form and updates the data.
export const createPost = async (req, res) => {
const { title, message, selectedFile, creator, tags } = req.body;
const newPostMessage = new OrangeModel ({ title, message, selectedFile, creator, tags })
try {
await newPostMessage.save();
res.status(201).json(newPostMessage );
} catch (err) {
res.status(409).json({ message: err.message });
}
}
I want to change the collection type based on the request.
when the request is from the Grapes url, the model(or collection) should change to GrapeModel from OrangeModel. How to do this?
If you want a POST /Grapes to be behave differently from a POST /Oranges, you can attach your controller to both paths and evaluate the path inside your code.
const createPost = async (req, res) => {
let newPostMessage;
if (req.path === "/Oranges") newPostMessage = new OrangeModel(...);
else if (req.path === "/Grapes") newPostMessage = new GrapeModel(...);
try {
await newPostMessage.save();
...
};
app.post(["/Oranges", "/Grapes"], createPost);
Also I got the answer like this:
exports.createPost =Model=> async (req, res) => {
try {
const doc = await Model.create(req.body, {
new: true,
runValidators: true,
});
res.status(200).json({
status: 'success',
data: {
doc,
},
});
} catch (error) {
res.status(400).json({
status: 'fail',
message: error,
});
}
};
Here just call createPost function with the model name
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
})
}
Sequelize + Bcrypt not storing passwords in DB as hash
As the title says, whenever I attempt to store a user into my SQLite DB the console outputs the password as a hash but when I look into the DB with DBbrowser I can see the plaintext password.
Model
// const Promise = require('bluebird')
const bcrypt = require('bcrypt')
async function hashPassword (user, options) {
if (!user.changed('password')) {
return 0
}
const SALT_FACTOR = 8
await bcrypt.hash(user.password, SALT_FACTOR, (err, hash) => {
if (err) {
console.log(err)
}
// user.setDataValue('password', hash)
user.password = hash
console.log(user)
})
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
beforeSave: hashPassword,
beforeCreate: hashPassword
}
})
User.prototype.comparePassword = function (password) {
bcrypt.compare(password, this.password, function (res, err) {
if (res) {
console.log(res)
} else {
console.log(err)
}
})
return bcrypt.compare(password, this.password)
}
return User
}
Controllers
module.exports = {
async register (req, res) {
try {
const user = await User.create(req.body)
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
// e-mail already exists or such
res.status(400).send({
error: 'This email address is already in use'
})
}
},
async login (req, res) {
try {
// Grab user input
const { email, password } = req.body
const user = await User.findOne({
where: {
email: email
}
})
// Check to see if user is in db
if (!user) {
res.status(403).send({
error: 'the login information was incorrect / Not Found'
})
}
// Check to see if password is valid
const isPasswordValid = await user.comparePassword(password)
if (!isPasswordValid) {
return res.status(403).send({
error: 'The login information was incorrect'
})
}
// return user using toJSON()
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (e) {
res.status(500).send({ error: 'An error occured attempting to login' })
console.log(e)
}
}
}
To elaborate a little more, whenever I create a user, I receive the following:
{
"user": {
"id": 1,
"email": 'test#test.com",
"password": "$2b$08$SYYXU/GDSCFsp3MVeuqrduI0lOLHeeub7whXiaMMoVxO53YJry.1i",
"updatedAt": "2018-09-07T22:44:12.944Z",
"createdAt": "2018-09-07T22:44:12.944Z"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJTVVBCUkhVQGxvbC5jb20iLCJwYXNzd29yZCI6IiQyYiQwOCRTWVlYVS9HRFNDRnNwM01WZXVxcmR1STBsT0xIZWV1Yjd3aFhpYU1Nb1Z4TzUzWUpyeS4xaSIsInVwZGF0ZWRBdCI6IjIwMTgtMDktMDdUMjI6NDQ6MTIuOTQ0WiIsImNyZWF0ZWRBdCI6IjIwMTgtMDktMDdUMjI6NDQ6MTIuOTQ0WiIsImlhdCI6MTUzNjM2MDI1MywiZXhwIjoxNTM2OTY1MDUzfQ.mDaeIikzUcV_AGTuklnLucx9mVyeScGpMym1y0kJnsg"
}
Which to me says the DB successfully hashed my password, and stored it. The overhanging issue for me with this is the fact that I believe it's causing the bcrypt.compare function to spit out 'false'. As always, any insight or help would be greatly appreciated!
I'm pretty sure that this answer is too late for you, but might help others landing on this same question.
The main issue I can see is how you are using the async/await pattern. Changing this:
async function hashPassword (user, options) {
if (!user.changed('password')) {
return 0
}
const SALT_FACTOR = 8
await bcrypt.hash(user.password, SALT_FACTOR, (err, hash) => {
if (err) {
console.log(err)
}
// user.setDataValue('password', hash)
user.password = hash
console.log(user)
})
}
to this, worked for me:
async function hashPassword(user, options) {
if (!user.changed("password")) {
return 0;
}
user.password = await bcrypt.hash(user.password, SALT_FACTOR);
}
Can you please try to add only one hook
hooks: {
beforeSave: hashPassword,
}
Because I think your password is getting hashed two times. as beforeSave and beforeCreate both hooks get executed.
Hope it helps
I am building a simple app that uses JWT for authentication. But I keeps on getting the error saying the route I GET to require a call back function.
What do I expect?
I should be getting the current user's data back.
What do I actually get?
Error: Route.get() requires a callback function but got a [object Object]
Route:
const authenticate = require("../middlewares/authenticate");
const usersController = require("../controllers").users;
app.get("/users/me", authenticate, usersController.getMe);
Model:
"use strict";
const jwt = require("jsonwebtoken");
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define(
"User",
{
email: DataTypes.STRING,
password: DataTypes.STRING
},
{
classMethods: {
associate: function(models) {
// associations can be defined here
},
findByToken: function(token) {
const User = this;
let decoded;
try {
decoded = jwt.verify(token, "leogoesger");
} catch (e) {
console.log(e);
}
return User.find({ where: { email: decoded.email } });
}
}
}
);
return User;
};
Middleware:
const { User } = require("../models/user");
const authenticate = (req, res, next) => {
console.log("called here");
const token = req.header("x-auth");
User.findByToken(token)
.then(user => {
if (!user) {
}
req.user = user;
req.token = token;
next();
})
.catch(e => {
res.status(401).send(e);
});
};
module.exports = { authenticate };
Controller:
module.exports = {
getMe(req, res) {
res.status(200).send({ message: "hello" });
}
};
Your authenticate module exports an object, yet you do this:
const authenticate = require("../middlewares/authenticate");
which means your const authenticate is an object, not your function. Change that to this:
const authenticate = require("../middlewares/authenticate").authenticate;
Or, change the module to export the function directly instead of exporting an object with the function in it.