I'm handling a client access by IP ACL.
function localAcl (options) {
let {allowedMethods} = options
if (!Array.isArray(allowedMethods)) {
allowedMethods = []
}
return function(req, res, next) {
if (allowedMethods.includes(req.method)) {
next()
} else {
(ipAccessControl(options))(req, res, next)
}
}
}
app.use('/api/v1', localAcl(options), apiV1Router)
The above reads a config file and decides whether to accept or not.
Now, I want to add a new middleware which reads another config file from a web file and decides the permission.
function newAcl () {
...
switch(result) {
case SUCCESS:
next();
break;
case FAIL:
next(new Error('Access Denied'));
break;
}
}
//something like this..
I want to check the permission with the localAcl first.
If localAcl accepts, I don't want the newAcl to be checked and the request will be handled.
If localAcl denies, I want the newAcl to check once more and if the newAcl accepts, the request should be handled.
How can I connect these two?
I see two options
Use next('route') to skip the rest of the middleware functions
Handle programmatically the authorization to let the last middleware decide
The first option seems like a better option for what you're describing
To skip the rest of the middleware functions from a router middleware
stack, call next('route') to pass control to the next route.
NOTE:
next('route') will work only in middleware functions that were loaded
by using the app.METHOD() or router.METHOD() functions.
Source
This means that you should probably call next('route') in your ipAccessControl function.
Related
I am submitting a simple contact form in my website's footer (in footer.pug):
form(method="POST" action="contact_form")
input(type='email' name='ct_email' data-name='ct_email' required)
textarea(type='text' name='ct_message' data-name='ct_message' required)
button(type='submit') Send
Since the form is in a template, and the footer template is used throughout the site, the form can be submitted from various routes:
/contact_form
/route1/contact_form
/route1/de/contact_form
and so on...
So now it seems I have to create a handler for all the possible routes:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
How can I easily handle POST requests from all the routes they may be coming from without writing a handler for each?
You can use absolute path reference in your form and it will always submit to the same route even though the form is in different pages.
Try this
form(method="POST" action="/contact_form")
Notice the action changed from contact_form to /contact_form. When you add /, you start referencing the path as an absolute path to the domain. So now, from all pages, the form will be submitted to http://your-domain/contact-form.
Not entirely sure if this is what you mean, but the first argument to ExpressJS's router (I assume that's what router is doing here) can be an array. So instead of:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
You can just do:
router.post(['/contact_form','route1/contact_form'],function(req,res,next){
//some fancy logic to handle both routes.
})
Of course, this requires that you keep a list of these possible routes. On the other hand, you can follow Dinesh Pandiyan's advice, and just use an absolute path. So instead of page1.html, page2.html, page3.html, etc. all having their own own router (or own entry in your router array), you'd essentially be saying "Go to the domain route, then go to this address".
Each request should be handled in separated functions because each request has its own logic. However if you want
function request(req, res, next) {
// Your logic
}
router.post('/contact_form', request) {
// ...
}
router.post('/route1/contact_form', request) {
// ...
}
Right now, I don't have a way to test this code, but I think that will help you.
Here is yet another potential solution - use an independent function as a route handler.
router.post('/a', handlePost);
router.post('/b', handlePost);
router.post('/c', handlePost);
function handlePost(req, res, next){
// use req.path here to figure out what url was called
}
In my website's routes file, I have a function like this:
router.post('/', ctrl1.validate, ctrl2.doSomething)
With the validate function looking like this:
function(req,res,next){
var errors = validator.checkForm('myForm')
if(errors){
res.redirect("/")
}else{
next()
}
}
If I want to pass parameters into the validator function (like the name of forms I want to validate) besides the implied req,res,next, how is that done? I have tried ctrl1.validate(formName) and ctrl1.validate(formName, req,res,next) with function(formName, req,res,next) in the controller, and neither work.
The ideal solution would be to identify what form you're working on from the data passed with the request in the first place. You don't show what that is, so we don't know exactly what to recommend or if that is feasible in this case.
If you can't do that and want to have a generic function that you can use as a request handler in multiple places and you want to pass a parameter to it that is different in each of the different places you use it, then you need to create a function that returns a function.
router.post('/', ctrl1.validate("someFormName"), ctrl2.doSomething)
// definition of ctrl1.validate
validate: function(formName) {
// return a request handler that will knkow which formName to use
return function(req,res,next){
var errors = validator.checkForm(formName)
if(errors){
res.redirect("/")
} else {
next()
}
}
}
When you first call this method, it returns another function that is your actual request handler. That inside function then has access to both req, res, next from the request and has access to the formName that was originally passed in.
I inherited a codebase where it looks like they run middleware in node with the following pattern for Oauth2 passport strategy
module.exports = function (router) {
router.get('/', function (req, res, next) {
passport.authenticate('type', object, function(err, info) {
//pass info object to next middleware
})(req,res,next) <---where does this go?!?
})
}
From my current understanding of the code base, this is actually the last function call in the middleware chain, so could I just add a piece of middleware to the bottom?
Does this sound like the right idea?
And just to clarify what I'm trying to do:
pass data from Oauth callback through middleware function by attaching it to the req
perform DB business logic (create or lookup account)
login with JWT
redirect
This appears to be the "custom callback" method of using passport's authenticate function. If you look at the documentation you can see how they expect it to be used. That said, I don't know what that second argument is supposed to be doing (the object) - it looks like a variable, but I don't see it defined anywhere, and I'm not sure the authenticate method takes arguments in that manner. Also, the custom callback takes three arguments: err, user, and then info... which might trip you up.
Okay, so now to your actual question of "could I just add a piece of middleware to the bottom?" Sort of? The fact is, you're in a routing middleware at that point. If it matches and auth is successful, then you should do whatever code for that route is required inside the custom callback. That's the point of this way of doing things. Alternatively you could use passport.authenticate as a piece of middleware itself (it returns a middleware function usable in the CommonJS pattern.
If you don't want to change up the code, then you could just do this:
module.exports = function (router) {
router.get('/', function (req, res, next) {
passport.authenticate('PICK A VALID TYPE', function(err, user, info) {
// this custom callback will be executed once auth completes
// (either successfully or not
// put code in here to perform DB business logic, login, and redirect
})(req,res,next); <--- this executes the passport.authenticate middleware
})
};
Here is relevant portion of my code, simplified for narrowing the issue:
app.use(middleware1);
app.use(middleware2);
function middleware1(req,res,next) {
...//get extension of request URL
switch (extension)
{
case 'js' :
..
case 'html': res.sendFile(res.originalUrl,function(err) {});
break; //break1
case 'njm' : break; //break2
default : console.log('default');
break;
}
}
function middleware2(req,res,next) {
console.log("I am in middleware2");
}
Question is this: In case extension is html, for example, I would not expect middleware2 to be called at all but it does!
It appears that sendFile initiates the sending of the file and control execution falls thru before the sendFile's callback is called. If I replace break1 by next() or return next() that would be equally flawed - Control will go to next middleware2 before sendFile's callback is executed. How do I stop middleware2 from getting called for the first set of extensions? Also, if extension is 'njm', even without a next(), middleware2 is called. Why?
Please do not suggest using Express static middleware because I have some logic involved in serving different file types which is more complex then the simplified scenario given above.
res.sendFile() is a bit unique. If you don't pass it a completion callback, then it will call next() for you. See details later in this answer.
What you are reporting is opposite of how Express says it works so I think there must be something that is not quite happening the way you report it.
The whole point of the Express middleware is that any given middleware call gets a chance to field the request and then it either handles the request by generating a response or if it wants the middleware chain to continue, then it calls next(). If next() is not called, then the middleware chain stops and nothing else is called in the current middleware chain. If this is application level middleware (with app.use()), then there should be no further app level middleware processing if you do not call next() from your middleware.
Here's a quote from the Express middleware page:
If the current middleware does not end the request-response cycle, it
must call next() to pass control to the next middleware, otherwise the
request will be left hanging.
This is a pretty good article about Express middleware: Express Middleware Demystified which helps explain a lot more of the details. It also confirms that if you don't call next() then no more handlers will be called in the middleware chain.
There is one special case with res.sendFile(). If you don't pass it a completion callback, then it will call next() itself. If you pass it the completion callback, then it will not call next(). This does not appear to be well documented, but if you look at the res.sendFile() code here, you can see how it works.
One thing to watch out for with your debugging is that sometimes the browser issues more requests than you may realize. For example, when you first hit a homepage of a site, the browser may ask for the website favicon which causes an extra request to hit your web server. So, I'm wondering if your console.log() debugging is confusing you because perhaps there is more than one request coming in, not a single request that is going through both pieces of middleware. Also, a cross origin Ajax call may request AJAX options before requesting the actual Ajax call too.
You can differentiate multiple requests like this and more accurately see whether it is actually going from middleware1 to middleware2 on the same request:
var reqCntr = 1;
app.use(middleware1);
app.use(middleware2);
function middleware1(req,res,next) {
if (!req.reqCntr) {
req.reqCntr = reqCntr++;
}
console.log("middleware1: " + req.reqCntr);
...//get extension of request URL
switch (extension)
{
case 'js' :
..
case 'html': res.sendFile(res.originalUrl,function(err) {});
// return here because the request is now handled
return;
case 'njm' : break; //break2
default : console.log('default');
break;
}
// the request was not handled so call the next link in the middleware chain
next();
}
function middleware2(req,res,next) {
if (!req.reqCntr) {
req.reqCntr = reqCntr++;
}
console.log("middleware2: " + req.reqCntr);
}
Also, it seems like the middleware1 cases where you are not handling the request should call next() so I've modified the above middleware1 to do that. If you handle the request in the switch statement, then return. If not, it will fall through to a call to next().
Once you write app.use(middleware2), middleware2 will be used for all routes on app once middleware1 is completely executed.
As you want to use middleware2 conditionally I would suggest you to use the following method:
app.use(middleware1);
function middleware1(req,res,next) {
...//get extension of request URL
switch (extension)
{
case 'js' : middleware2(req, res, next);
break;
case 'html': res.sendFile(res.originalUrl,function(err) {});
break;
case 'njm' : middleware2(req, res, next);
break;
default : middleware2(req, res, next);
break;
}
}
function middleware2(req,res,next) {
console.log("I am in middleware2");
}
app.get('/users/:userId/profile', ProfileHandler);
app.get('/page/:userId/profile', ProfileHandler);
app.get('/photo/:userId/profile', ProfileHandler);
If I have the above 3 routes, how can I capture the first part so that the handler knows what is being requested? I'd like to have users or page or photo sent to the handler as part of the request object.
Ideally I'd like to avoid making this a single route with a regex as this is just a dumbed down example of my real use case.
If you know ahead of time due to your bindings, why not just pass the info in there?
app.get('/users/:userId/profile', ProfileHandler.bind(null, 'users'));
function ProfileHandler(pageRoot, req, res, next){
switch (pageRoot){
case 'users':
break;
case 'page':
break;
}
});
Based on the pattern you are using , ProfileHandler will be passed a req and res object. req has a url property that you can then split and switch-case:
app.get('/users/:userId/profile', ProfileHandler);
app.get('/page/:userId/profile', ProfileHandler);
app.get('/photo/:userId/profile', ProfileHandler);
function ProfileHandler(req,res){
var reqType = req.url.split('/')[1];
switch(reqType){
case 'users':
//DO SOMETHING COOL
break;
}
}
Alternatively, you could add middleware that set that value on the request.
app.use(function (req, res, next) {
var reqType = req.url.split('/')[1];
req.handlerTarget = reqType;
});
function ProfileHandler(req,res){
switch(req.handlerTarget){
case 'users':
//DO SOMETHING COOL
break;
}
}