I have a bug which has made me realize I clearly missing something important about express middlewares. I have something like this:
const checkAuth = ({ allowedRoles = [], targetUserId }) => {
return async (req, res, next) => {
console.log(targetUserId);
//Do checks
if(!allowedRoles.includes(req.user.role) return
targetUserId = req.params[targetUserId]
if(!allowedUsers.includes(targetUserId) return
return next();
};
};
app.get(
"/activity/:id",
checkAuth({ allowedRoles: ["locationAdmin", "orgAdmin"], targetUserId: "id" }),
async function (req, res, next) {
//Do things
});
});
The idea behind this middleware is the 'targetUserId'variable tells it which request parameter to look in for a value, then it can choose to authorize the request with the value it finds there. The middleware actually exists in it's own file/module if that makes any difference.
Here's what I expect to happen:
User loads url /activity/23 > "id" is logged
User loads url /activity/23 > "id" is logged
and so on until the end of time...
What actually happens:
User loads url /activity/23 > "id" is logged
User loads url /activity/23 > 23 is logged
User loads url /activity/23 > undefined is logged
User loads url /activity/23 > null is logged
item 4 will then repeat forever
My expectation is that each time checkAuth is called, there should be nothing in memory from it's last call. It's taken me a couple of hours to get my head around this, but what seems to be happening, is after the value of targetUserId is set within the middleware, the variable remains set in memory, and isn't changed when it gets called again with "id". This explains why the value logged to the console changes 3 times on repeat requests for the same page.
But why does it do this and how can I fix it?
The way your code is structured, checkAuth() is called once when your code is first run. That captures the arguments you passed it and then makes those available to the middleware function that checkAuth() returns and that middleware function is passed to Express where it is registered as middleware for this request handler.
That middleware function you return is then called by Express for each incoming request that matches this request handler. The arguments you passed to checkAuth() are available to that middleware function each time it is called.
If some code changes those arguments (like code inside the middleware itself changes an object or property passed), then those changes are persistent (in a closure) and future middleware will see those changes. So, if you need to modify those arguments for the purposes of one request, then you should make a copy of the data or make the modifications in a variable that is local to the internal middleware function.
Related
I want to put user's password into request object for accessing it next request object. Is it secure?
For example :
We have a route like "http://localhost/my_bank".
And there are two middlewares before accessing to route.
First middleware checks user's password and if it is true puts password into request object and continues for second middleware.
Second middleware gets user password with request.password and continues the process.
If you're talking about the request object that is passed to a middleware handler such as in Express like this:
app.use((req, res, next) => {
// do something here
req.password = someData;
next();
});
Then, there's nothing particularly unsafe about that. In fact, if you were using express.json() or express.urlencoded() to parse an incoming form that contains the password, then that middleware already put the password into req.body.password or some similarly named field. Thus, it's already in the req object and this is a common usage.
So, there's nothing inherently unsafe about putting a password in the req object as long as you aren't passing the req object to functions/code that shouldn't have access to the password.
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've currently written middleware to verify an ID exists in an external services (Salesforce). I initially wrote it when it was a single use app, but now I'm trying to make it work with different routes, so I want it to be fairly generic.
I don't even know if middleware is the right way to go, or if I should just call the function before saving the specific form.
I've got a form where someone puts in some information about a project, and the salesforce ID. For background, the salesforce ID is actually an auto-increment number, and I need to convert that to the actual salesforce system ID before I use jsForce to create a new object linked to that ID.
My route looks like this:
router.post('/invoice/add', ensureLoggedIn, invoiceController.validateInvoice, catchErrors(sfdc.validateSFID), catchErrors(invoiceController.saveInvoice))
So, I've got a middleware that does this:
exports.validateSFID = async(req, res, next) => {
const salesforceProjectNumber = req.body.SF_Opportunity
const sfResult = await conn.search(`FIND ... long query`, (err, result) => {
if (err || result.searchRecords.length !== 1) {
req.flash('error', 'Unable to find a Salesforce Job with that ID number.')
console.error(`ERROR: ${req.user.displayName} errored when looking up job number ${salesforceProjectNumber}.`)
return result
}
})
if (sfResult.searchRecords.length > 0) {
req.body.salesforce_Opportunity_id = sfResult.searchRecords[0].Id //Create a generic variable to hold the salesforce opportunity so it works regardless of the custom object name
res.locals.Opportunity_Clean_Name = sfResult.searchRecords[0].Name
}
next()
}
The query rarely throws an error, but in this case, an error is basically returning !1 records.
When that happens, I want to flash a message on the screen saying the ID wasn't found, but keep the form filled in.
When an ID is found, I want to proceed to save it and NOT display the form fields anymore.
This middleware needs to work regardless of the form I'm using, I want to be able to pipe in the middleware from any form that might require a user to enter a salesforce job as a field.
Any thoughts on how best to handle it all?
You can use your middleware by using app.use() function
app.use((req, res, next) => {
// Every time a request has made, this middleware will fire
console.log('Howdy');
})
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
})
};
Is it possible with expressjs to have multiple routes calling the same resource, something like that:
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
I would like to be able to call users.getOne whichever params (:user_id or :username) is used in the get request.
In the users.getOne function, how can I determine wich one was used and build my query according to it?
exports.getOne = function(req, res){
var queryParams = ? // I need help here
Users
.find(queryParams)
...
Thanks!
Possibly related: express.js - single routing handler for multiple routes in a single line
From express's view, both of those routes will match the same set of request URLs. You only need one of them and you can name it to make more sense:
app.get('/users/:key', users.getOne);
//...
// http://stackoverflow.com/a/20988824/266795
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
exports.getOne = function(req, res) {
var conditions = {_id: req.params.key};
if (!OBJECT_ID_RE.test(req.params.key)) {
conditions = {username: req.params.key};
}
Users.find(conditions)...
If you end up wanting this pattern in many routes throughout your code base, you can extract it into a /users/:user param and use app.param as per #alex's answer, but encapsulate the code to locate the user and stick it on to req.user so the actual route handler can just assume the user has been properly found and loaded by the time it executes, and 404 handling can be centralized as well.
Those are in fact, from express's view, the same route.
No, they are not. One route has :user_id parameter, another one has :username.
This would be a proper solution:
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
app.param('user_id', function(req, res, next, value, name) {
if (OBJECT_ID_RE.test(value)) {
next()
} else {
next('route')
}
})
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
app.param set the prerequisite for the route to be called. This way when user_id matches a pattern, first route gets called, otherwise second one.