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();
};
}
Related
I have a doubt about middelware in express.
I want to many thinks in one middleware. For example
I have this code y me middleware
module.exports = function(req,res,next) {
if(req.method === 'GET') {
res.end('GET method not supported');
} else {
next();
}
};
and I use it like this
app.route('/', <the_middleware>, (res, req, next) => {
// Code
})
But I am wondering if is possible to do something like this
app.route('/', <the_middleware>.<the function1>, (res, req, next) => {
// Code
})
app.route('/', <the_middleware>.<the_function2>, (res, req, next) => {
// Code
})
is there a possiblitity to do something like
function function1 (req,res,next) {
if(req.method === 'GET') {
res.end('GET method not supported');
} else {
next();
}
};
function function2 (req,res,next) {
if(req.method === 'GET') {
res.end('GET method not supported');
} else {
next();
}
};
module.exports = <I don`t know what go here>
Thanks.
Update. IT works, my code now is
The router
router.post('/', checkAuth.sayHi, checkAuth.sayBye, (req, res, next) => {
console.log('good');
res.status(200).send('it works');
console.log('yes');
});
The middleware
module.exports = {
sayHi(req, res, next) {
console.log('hi');
next();
},
sayBye(req, res, next) {
console.log('bye')
next();
}
};
You can just export an object containing both functions:
module.exports = {
function1,
function2
}
This is how I've designed my route and I'm not really happy with it. I'd rather be able to break down each hasValid into its own middleware, but I don't see how that would work since it wouldn't stop execution.
const secretSender = async (req, res, next) => {
if (!hasValidA(req)) {
return next()
}
if (!hasValidB(req)) {
return next()
}
if (!hasValidC(req)) {
return next()
}
if (!hasValidD(req)) {
return next()
}
res.send('something secret')
}
router.use(secretSender)
router.get('*', (req, res) => {
res.send('something public')
})
It also doesn't really make sense to make the default route "something secret" and have "something public" be the middleware since "something public" is the default behavior.
Here's one way to break down each hasValid into its own middleware. Each middleware will short-circuit the execution if the outcome is valid:
const validA = async (req, res, next) => {
if (hasValidA(req)) {
res.send('something secret')
} else {
next()
}
}
const validB = async (req, res, next) => {
if (hasValidB(req)) {
res.send('something secret')
} else {
next()
}
}
const validC = async (req, res, next) => {
if (hasValidC(req)) {
res.send('something secret')
} else {
next()
}
}
const validD = async (req, res, next) => {
if (hasValidD(req)) {
res.send('something secret')
} else {
next()
}
}
router.use(validA, validB, validC, validD)
router.get('*', (req, res) => {
res.send('something public')
})
Update
To achieve somewhat similiar result with OP's code, but only with single next():
const secretSender = async (req, res, next) => {
if (hasValidA(req) && hasValidB(req) && hasValidC(req) && hasValidD(req)) {
res.send('something secret')
} else {
next()
}
}
router.use(secretSender)
router.get('*', (req, res) => {
res.send('something public')
})
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)
}
});
I'm trying to unit test a simple piece of Express middleware, a cascading athenticator that checks first for a JWT token using a passport-jwt-strategy, and then if that fails, using a passport-openid-strategy. Each of the strategies is already well tested so what I am trying to test is their integration.
The module I am testing looks like this:
"use strict";
let passport = require('passport');
let Strategies = require('./strategies');
let setupDone = false;
// set up passport
let setup = function (app) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use('jwt', Strategies.jwt);
passport.use('openid', Strategies.openId);
app.use(passport.initialize());
app.use(passport.session());
setupDone = true;
};
let authenticate = function (req, res, next) {
if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
console.log(' cascadingAuthentication');
// first try the token option
passport.authenticate('jwt', function (jwterr, user, info) {
console.log(' jwt auth', jwterr, user, info);
if (jwterr || !user) {
passport.authenticate('openid', function (oautherr, user, info) {
if (oautherr || !user) {
return next(oautherr);
} else {
next();
}
});
} else {
req.user = user;
next();
}
});
};
module.exports = {
setup: setup,
authenticate: authenticate
}
My Jasmine test looks like this
"use strict";
let CascadingAuthentication = require('../../lib/middleware/cascadingAuthentication');
let TokenUtils = require('../support/tokenUtils');
let email = 'testing#test.tes';
describe('cascadingAuthentication', function () {
describe('when there is a token in the header', function () {
let req;
let res = {};
let app = {
use: function (used) { console.log('app.use called with', typeof used); }
};
beforeEach(function (done) {
let token = TokenUtils.makeJWT(email);
req = {
app: app,
header: {
Authorization: `Bearer ${token}`
}
}
CascadingAuthentication.setup(app);
CascadingAuthentication.authenticate(req, res, function () {
done();
});
});
it('populates req.user', function () {
expect(req.user).toEqual(jasmine.any(Object));
});
});
});
The issue I have is that, when I run the test, I see the first console.log(' cascadingAuthentication') but I never see the second console.log('jwt auth', err, user, info). The code just dies inside passport.authenticate without ever calling the callback, without raising an error, or without providing any kind of feedback at all.
I'm running my tests via gulp using Jasmine.
My questions are: in order,
Can you see anything obvious that I have done that I might have just missed?
Is there anything else I ought to mock out in my req, res, or app that might make this test work?
Is there any way to debug this interactively; stepping through the code under test as it runs, rather than just adding console.log statements (which seems a little 1980s to me).
Digging through passport's source I have worked out there were two problems with my code.
The first is that passport.authenticate returns a middleware function, it doesn't actually execute that function. So the solution was simply to call the returned function.
So my authenticate method now looks like:
let authenticate = function(req, res, next) {
if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
// first try the token option
passport.authenticate('jwt', function(jwterr, user, info) {
if (jwterr || !user) {
passport.authenticate('openid', function(autherr, user, info) {
if (autherr || !user) {
return next(autherr);
} else {
next();
}
})(req, res, next);
} else {
req.user = user;
next();
}
})(req, res, next);
};
(The above example is trimmed for use in the question)
The other issue was in my test I used header instead of headers in my mock req object, and also authorization ought to have had a lower case a.
With those two fixes the test now passes.
I fiddled with this for quite some time and eventually landed on the following setup (to test passport.authenticate('local', () => {})).
auth-router.js
const express = require('express');
const passport = require('passport');
const login = (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
next(err);
return;
}
if (!user) {
const error = new Error(info.message);
error.status = 404;
next(error);
return;
}
// Add the found user record to the request to
// allow other middlewares to access it.
req.user = user;
next();
})(req, res, next);
};
const router = express.Router();
router.post('/auth/login', login);
module.exports = {
login,
router
};
auth-router.spec.js
const passport = require('passport');
describe('login', () => {
it('should login and add the user to the request object', (done) => {
spyOn(passport, 'authenticate').and.callFake((strategy, callback) => {
const err = null;
const user = {};
const info = {};
callback(err, user, info);
return (req, res, next) => {};
});
const auth = require('./auth'); // my middleware function
const req = { body: {} };
const res = {};
const next = () => {
expect(req.user).toBeDefined();
done();
};
auth.login(req, res, next);
});
});
How do I make it so that if a response has been sent back, then no more responses should be sent? Actually, the issue is that if a response is sent back, then express (or nodejs) shouldn't continue running through the rest of the code.
I've tried doing next() but terminal throws the error of next() being undefined. res.end() doesn't seem to work either?
routing.js:
router.post('/user/create', function(req, res, next) {
user.createUser(req, res);
});
user.js createUser
user.prototype.createUser = function(req, res, next) {
var body = req.body;
checkAllInput(body, res, next);
// do some more checks then finally create user
}
user.js createUser
function checkAllInput(body, res, next) {
checkError.checkUsername(body, res, next);
checkError.checkPassword(body, res, next);
}
checkError.js
userClass.prototype.checkUsername = function(username, res) {
if (!username || username === "bob) {
res.status(406).send("username");
}
}
userClass.prototype.checkPassword = function(password, res) {
if (!password || password === "hello") {
res.status(406).send("password");
}
}
Call createUser in routing, which then calls checkAllInput which calls checkUsername but should stop if username sends a response back.
You need to return, so the code stops there. Otherwise it will keep on going.
userClass.prototype.checkUsername = function(username, res) {
if (!username || username === "bob) {
return res.status(406).send("username");
}
}
userClass.prototype.checkPassword = function(password, res) {
if (!password || password === "hello") {
return res.status(406).send("password");
}
}
next() isn't inherent in the code, it has to be defined somewhere, and even if it is, it still doesn't stop the code as it is asynchronous.
I'm assuming you're using Express. You might want to do this with middleware.
//middleWare.js
exports.checkUserModel = function (req, res, next) {
var body = req.body,
username = body.username,
password = body.password,
if (!username || username === "bob) {
return res.status(406).send("username required");
}
if (!password || password === "hello") {
return res.status(406).send("password required");
}
next();
}
//router.js
var middleWare = require('./middleWare');
var user = require('./controllers/users');
app.post('/user/createUser', middleWare.checkUserModel, user.create);
You want to use the express middleware like so :
checkUsername = function(req, res, next) {
if (checkUserNameIsValid) {
//check the password
next()
}
else{
///otherwise return
res.status(406).send("username");
}
}
checkPassword = function(req, res, next) {
if (checkIfPasswordIsValid) {
//create the user when password is valid too
next();
}
else {
//else return response
res.status(406).send("password required");
}
}
createUserIfPasswordAndUserNameIsOk = function(req, res, next){
//create the user here
}
Add in sequence you want to handle the request.
router.post('/user/create', checkUserName, checkPassword, createUserIfPasswordAndUserNameIsOk );
So what will happen the express router will first call checkUserName and if you don't call next() then it returns. So if you call next() in it it will call the next method in for the current requested resource which is checkPassword. And so on.
Take a look at Having a hard time trying to understand 'next/next()' in express.js thread.
Note that you don't have to use return. Also take a look at #Brian answer