Return middleware from middleware - node.js

I am using express-validator and would like to have different checks based on a value in the request body.
I have created a function for this, but I am not getting any responses back (i.e. express just hangs.):
validation/profile.js
module.exports = function (req,res,next) {
if (req.body.type == 'teacher') {
return check('name').exists().withMessage('Name is required'),
} else {
return check('student_id').exists().withMessage('Student id is required'),
}
}
app.js
router.put('/', require('./validation/profile'), (req, res, next) => {
const errors = validationResult(req).formatWith(errorFormatter)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.mapped() })
} else {
res.send(req.user)
}
})
If however, I write my function as a normal function (not as middleware with 3 params) and call it, it all works. But this way, I won't have access to the request object. I have to "hard-code" the params.
validation/profile.js
module.exports = function (type) {
if (type == 'teacher') {
return check('name').exists().withMessage('Name is required'),
} else {
return check('student_id').exists().withMessage('Student id is required'),
}
}
app.js
router.put('/', require('./validation/profile')('teacher'), (req, res, next) => {
const errors = validationResult(req).formatWith(errorFormatter)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.mapped() })
} else {
res.send(req.user)
}
})
Any suggestions on how could I achieve having different checks based on a value in the request body?

The express-validator check API creates the middleware, you should attach it to express directly or call it yourself as express would.
// Use routers so multiple checks can be attached to them.
const teacherChecks = express.Router();
teacherChecks.use(check('name').exists().withMessage('Name is required'));
const studentChecks = express.Router();
studentChecks .use(check('student_id').exists().withMessage('Student id is required'));
module.exports = function (req,res,next) {
if (req.body.type == 'teacher') {
teacherChecks(req, res, next);
} else {
studentChecks(req, res, next);
}
}
You could also potentially use oneOf to do the same thing.
router.put('/', oneOf([
check('name').exists().withMessage('Name is required'),
check('student_id').exists().withMessage('Student id is required')
], 'Invalid request body'), (req, res, next) => {
const errors = validationResult(req).formatWith(errorFormatter)
if (
!errors.isEmpty()
) {
return res.status(422).json({errors: errors.mapped()})
}
else {
res.send(req.user)
}
});

Related

express auth middleware returning "Not authorized" error

I have an auth protect middleware that checks if req.params.id === req.userId(the one returned by bcrypt verify function). I have a protect function which upon bcrypt.verify returns the decoded.id.
The Id returned from req.user._id despite being the same as decoded.id returns "not authorized in the verifyandAuth middleware, however, if I replace req.user._id by decoded.id(in verifyandAuth), the if function works and the middleware goes through without giving the "not authorized error". Can anybody please tell me why that's happening? (req.user._id and decoded.id upon console.log show the same id, as such, there's no mistake there).
Protect Middleware
export const protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
)
try {
{
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, "kris");
req.userId = decoded.id;
req.user = await User.findById(decoded.id).select("-password");
next();
}
} catch (error) {
res.status(400).json(error.message);
}
if (!token) {
return res.status(400).json("Invalid Token");
}
};
auth Middleware
export const verifyandAuth = (req, res, next) => {
protect(req, res, () => {
console.log(req.user._id, req.params.id);
if (req.user._id === req.params.id || req.isAdmin) {
next();
} else {
res.status(400).json("Not authorised");
}
});
};

Login route looks for /:id middleware in Nodejs

I have a middleware function for my post route /blog that prevents users not logged in to add blogs to the site, below is the function:
app.use('/blog',(req,res,next) => {
if ( req.path == '/blog/login') next();
if (!req.session.user){
res.status(403)
res.send('Please login to add blog')
return
}
else{
next()
}
})
I have a login route that looks like this:
router.post('/blog/login', async (req, res) => {
let { username,password } = req.body;
try{
checkString(username);
checkString(password);
username = username.toLowerCase();
if (!username.match(/^[a-zA-Z0-9!##\$%\^\&*\)\(+=._-]+$/g)) throw 'Only alphanumeric characters allowed for username'
if(!password.match(/^[a-zA-Z0-9!##\$%\^\&*\)\(+=._-]+$/g)) throw 'Only alphanumeric and special characters allowed for password'
if (username.length<4) throw 'Username should have 4 characters or more';
if (password.length<6) throw 'Password should have 6 characters or more';
const ans = await userData.checkUser(username,password)
if (ans==1){
const [id,user,name,pass] = await userData.getValues(username);
req.session.user = { username:user, name:name, password:pass, userId:id };
res.send({authenticated:true})
}
} catch(e){
res.send(400,{msg:e})
}
});
Below is the route that I want to be able to run without going to the middleware:
router.get('/blog/:id', async (req, res) => {
try{
const blog = await blogData.getOneBlog(req.params.id);
res.send(blog)
}
catch{
res.sendStatus(500);
}
});
My index.js:
const blogRoutes = require('./blogs');
const constructorMethod = (app) => {
app.use('/blog', blogRoutes);
app.use('*', (req, res) => {
res.sendStatus(404);
});
};
module.exports = constructorMethod;
The above route goes directly to the middleware because anything I call after the /blog sends it to the middleware but I only want to send the /blog/:id to the middleware.

nodejs middleware execution continues after return

I have the following middleware:
const mongoose = require('mongoose');
module.exports = function(req, res, next) {
const keys = Object.keys(req.params);
keys.forEach(elem => {
if (
(elem.includes('id') || elem.includes('Id')) &&
!mongoose.Types.ObjectId.isValid(req.params[elem])
)
return res
.status(400)
.json({ msg: `id: ${req.params[elem]} is invalid` });
});
next();
};
it is called in a get request:
// #route GET api/movies/:id
// #desc Get a movie with specified id from db
// #access Public
router.get('/:id', checkId, async (req, res) => {
const movie = await Movie.findById(req.params.id);
res.json(movie);
});
When I make a request in postman with an invalid id (ex: 1234) I recieve the proper response being 400 with msg: 'id 1234 is invalid' yet the execution still passes to the reqest callback code and an error is thrown as I try to access db with invalid id.
so the question is why does the middleware still allow the execution of next() even if it already returned with the 400?
You need to tell the router that there was a problem by calling next("some-error"). For example, you could do this:
module.exports = function(req, res, next) {
const keys = Object.keys(req.params);
keys.forEach(elem => {
if (
(elem.includes('id') || elem.includes('Id')) &&
!mongoose.Types.ObjectId.isValid(req.params[elem])
) {
res
.status(400)
.json({ msg: `id: ${req.params[elem]} is invalid` });
return next("invalidinput");
}
});
next();
};
Or you could be more generic by setting the result outside in the router if you like, like this:
In your middleware:
module.exports = function(req, res, next) {
const keys = Object.keys(req.params);
keys.forEach(elem => {
if (
(elem.includes('id') || elem.includes('Id')) &&
!mongoose.Types.ObjectId.isValid(req.params[elem])
) {
// === Report the error and let the router handle it
return next({
type: "invalidinput",
msg: `id: ${req.params[elem]} is invalid`
);
}
});
next();
};
Then at the bottom in your router:
// handle any errors
router.use(err, req, res, next) => {
if (err) {
if (err.type === "invalidinput") {
return req.status(400).json({msg: err.msg});
}
else {
return res.status(500).json({msg: "Internal error."});
}
}
return next();
}
another possible solution here is to convert the forEach to a classical for loop, thu making this middleware run synschronously
module.exports = function(req, res, next) {
const keys = Object.keys(req.params);
for (let i = 0; i < keys.length; i++) {
if (
(keys[i].includes('id') || keys[i].includes('Id')) &&
!mongoose.Types.ObjectId.isValid(req.params[keys[i]])
)
return res
.status(400)
.json({ msg: `id: ${req.params[keys[i]]} is invalid` });
}
next();
};

Chained middlware causes Express request to hang forever

I am following a middleware chaining example from this question.
I have a route app.put('/users/:id', isAuthenticated, (req, res) => {db.updateUser(req.params.id, req.body)}. I am trying to write a middleware function that verifies that the ID provided in the URL matches the ID retrieved from the JWT included with the request.
I already have a function isAuthenticated that verifies the JWT and sets res.locals.userId to the UID retrieved; so I would like to simply make use of that in this new function canModifyTarget but for some reason the request hangs forever:
// This function works fine
isAuthenticated: function(req, res, next) {
let token;
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
admin.auth().verifyIdToken(token).then((decodedToken) => {
res.locals.userId = decodedToken.uid;
return next();
}).catch((error) => {
return res.status(HttpStatus.UNAUTHORIZED).send();
})
}
}
// Switching out isAuthenticated for this in the route causes a permanent hang
canModifyTarget: function(req, res, next) {
console.log('This is printed');
return (req, res, next) => {
console.log('This is NOT printed');
isAuthenticated(req, res, () => {
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
})
}
}
middlewares should be callback functions that call "next()" once finished.
Your first function, when executed, is calling next() (eventually, after your promise is resolved)
Your second function isn't calling next(), it is just returning a function definition.
Define it like this
canModifyTarget: function(req, res, next) {
isAuthenticated(req, res, () => {
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
})
}
}
and if the third parameter of isAuthenticated is a callback, it should work
Also, you should define an "else" case in your isAuthenticated function, otherwise it will hang as well (maybe throw an exception or something?)
If you need to reference them, store them in variables rather than directly defining them in your module.exports:
const isAuthenticated = function(req, res, next) {
// code here
}
const canModifyTarget: function(req, res, next) {
// code here
}
module.exports = {
isAuthenticated,
canModifyTarget,
};
I think simpler is to define canModifyTarget as one more middleware. I.e:
function canModifyTarget(req, res, next) {
console.log('This is NOT printed');
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
}
and then just apply it after isAuthenticated middleware:
app.put(
'/users/:id',
isAuthenticated,
canModifyTarget,
(req, res) => {db.updateUser(req.params.id, req.body)}
);
Hope it helps.
I am just writing a solution where I needed to unify two kind of auth middlewares: password-based and apikey-based into one middleware: unifiedOrgAuth middleware.
So, basically this would enable me to just put unifiedOrgAuth middleware on those routes which need either the password-based or apikey-based auth.
The key thing was to pass the next function from the umbrella middleware to the underlying middleware by just calling the underlying middleware with the next function of the umbrella middleware:
unified auth middleware:
function unifiedOrgAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req: RequestWithOrg, _res: Response, next: NextFunction) => {
const cookies = req.cookies;
if (cookies && cookies.Authorization) {
(userAuthMiddleware(path, perm))(req, _res, next);
return;
}
const apiKey = req.header('X-API-KEY');
if (apiKey && apiKey.length > 0) {
(apiAuthMiddleware(path, perm))(req, _res, next);
return;
}
return next(new Error401Exception());
// Make linter happy.
};
}
Here are the underlying middlewares:
password-based auth middleware:
function userAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req, _res, next) => {
try {
const cookies = req.cookies;
if (!(cookies && cookies.Authorization)) {
next(new Error401Exception());
// Make linter happy.
return;
}
if (!validCookies(cookies)) {
next(new Error401Exception());
// Make linter happy.
return;
}
} catch (error) {
next(new Error401Exception());
// Make linter happy.
return;
}
next();
};
}
api-based auth middleware:
function apiAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req: RequestWithOrg, _res: Response, next: NextFunction) => {
const apiKey = req.header('X-API-KEY');
if (!apiKey) {
next(new Error401Exception());
// Make linter happy.
return;
}
if (!validApiKey(apiKey)) {
next(new Error401Exception());
// Make linter happy.
return;
}
next();
};
}

express-validator not producing validation errors

I am facing issues while trying express-validator v4.3.0.
There is one basic example of login with post request. There are two parameters email and password. Below is my code.
routes.js file:
var express = require('express');
var router = express.Router();
var validator = require('./validator');
router.post('/login', [sanitize('email').trim(),
validator.publicRouteValidate('login')], (req, res, next) => {
console.log(req);
}
validator.js file:
'use strict';
const { check, validationResult } = require('express-validator/check');
const { matchedData, sanitize } = require('express-validator/filter');
module.exports = {
publicRouteValidate: function (method) {
return (req, res, next) => {
switch (method) {
case 'login':
check('email').isEmail().withMessage('email must be an email')
.isLength({min: 109}).withMessage('minimum length should be 100 characters')
break;
}
var errors = validationResult(req)
console.log(errors.mapped())
if (!errors.isEmpty()) {
res.status(422).json({ errors: errors.array()[0].msg })
} else {
res.send('login')
}
}}}
Now when I am doing POST request with only email and value of email is abc. So I should get the error like email must be an email. But I did not get any response. So What is the issue I do not know?
Your publicRouteValidate function is only creating a validator, but it is never being called.
This happens because, as documented, check returns an express middleware. Such middleware must be given to an express route in order for it to do its work.
I recommend you to break that function in two: one that creates your validators, and another that checks the request for validation errors, returning earlier before touching your route.
router.post(
'/login',
createValidationFor('login'),
checkValidationResult,
(req, res, next) => {
res.json({ allGood: true });
}
);
function createValidationFor(route) {
switch (route) {
case 'login':
return [
check('email').isEmail().withMessage('must be an email'),
check('password').not().isEmpty().withMessage('some message')
];
default:
return [];
}
}
function checkValidationResult(req, res, next) {
const result = validationResult(req);
if (result.isEmpty()) {
return next();
}
res.status(422).json({ errors: result.array() });
}

Resources