Express.js middleware extra (fourth) argument - javascript

On the optional arguments to an Express.js middleware , probably a fourth one apart from being an error handling middleware, I have a use case where it comes useful. It may be achieved by a different way, but anyways.
I want to make some api routes permission checked, the middleware I wish to write should look up database for a requesting user for how many reputation points (integer) he/she has. I defined an object to hold privileges and reputation points required as key value pairs. Later, middleware should look up this privileges object to see if the user has greater than or equal reputation points for a corresponding action. I want to pass this corresponding action name which is a key in the privileges object as a different string for each route. This can be achieved by sending the string actionNames via requests to the routes, but I find it less secure (data in request can be tampered to have an action name that a malicious user has access to and to have required data fields for another wished but not permitted action).
All the case is just like that of SE's.
By the way, apparently I also need route based (not Router based) middleware mounting, I am not sure if Express.js supports, but this is another story.
Maybe one can describe and ask this use case as can I parametrize a middleware function with my parameters, not just by incoming req and res objects?
How can I achieve this use case with Express.js middleware? Or should I use another mechanism?
/// Privilege Check middleware
// used per route with corresponding actionName
// signature function (request, response, next, actionNameOneOfKeysInPrevilegesObject::String)
var privilegeCheck = function (req, res, next, actionName) {
db.one(
`
SELECT reputation FROM users WHERE id = $(id)
`,
{id: req.user.id} // remember req.user was set by jwt.sign(data) during login or signup with demanded data; here it is {id:, name:, username:,}
)
.then(function (data) {
if(data >= privileges[actionName]) {
next();
}
else {
res.status(403).json({errorMessage: "You need to have " + privileges.questionUpvote + " reputation to upvote."});
}
})
.catch(function (error) {
console.error(error);
})
};
// reputations needed for privileged actions
var privileges =
{
questionAsk: 5,
answer: 15,
acceptAnswer: 0,
comment: 5,
questionEdit: 20,
answerEdit: 20,
commentsEdit: 0,
postsUpvote: 30,
postsDownvote: 30,
commentsUpvote: 5,
questionUpvote: 10,
questionDownvote: 125,
}

Use route-specific middleware like so:
function privilegeCheck(actionName) {
return function (req, res, next) {
db.one(
`
SELECT reputation FROM users WHERE id = $(id)
`,
{id: req.user.id} // remember req.user was set by jwt.sign(data) during login or signup with demanded data; here it is {id:, name:, username:,}
)
.then(function (data) {
if(data >= privileges[actionName]) {
next();
}
else {
res.status(403).json({errorMessage: "You need to have " + privileges.questionUpvote + " reputation to upvote."});
}
})
.catch(function (error) {
console.error(error);
})
;
}
};
// Then, for each of your routes, invoke your privilegeCheck() function
// as an "in-between" argument between your path and your route handler
// function.
app.get('/my/route', privilegeCheck("myActionName"), (req, res) => {
res.status(200).send('handled /my/route');
});
See the "Application-level middleware" section at http://expressjs.com/en/guide/using-middleware.html, starting from "This example shows a middleware sub-stack that handles GET requests to the /user/:id path."
But the documentation there does not show that you can chain functions in the app.get(). You can see that at
https://scotch.io/tutorials/route-middleware-to-check-if-a-user-is-authenticated-in-node-js
In fact, you can have as many "middleware" (ie three-argument function) arguments to any of Express's route handling functions (get(), put(), post(), delete()) as you need.

Unfortunately, Express is coded so that if your middleware function has exactly four parameters, it is considered error handling middleware. IMO this was not a good design decision.
To do what you want to do, the canonical way to do is to attach data to the request stream (req).
For example:
app.use(function(req,res,next){
req.data = {foo:'bar'};
});
app.use(function(req,res,next){
const data = req.data;
}):
Coming from languages like Java, I thought this was some of the craziest coding ever, but it works because req is unique to that request + JS is "single-threaded".

Related

Middleware OR other middleware validation

I need to check if a user is an administrator OR if the user is the same as the one requested. I tried but in one case it doesn't work.
router.get('/:id', isSigned, isSameUser || isAdmin, getUser)
Is there a way to do what I want without creating a dedicated function?
You could write a different middleware to avoid unnecessary middleware chaining, which you can call Auth or AuthMiddleware, the name you want is up to you. In this middleware you could do the logic for checking the user's authentication status, something similar to this:
function AuthMiddleware(request, response, next) {
if(isSigned) {
// User is signed in, so we can proceed doing the next check
if(isSameUser || isAdmin || getUser) {
// Validation checks out, go to next middleware without warnings.
return next();
} else {
// User failed to show some ID and cannot access the page.
return next(new Error("Unauthorized."));
}
} else {
// The user has not signed in to your application
return next(new Error("Unauthenticated"));
}
}
router.get('/:id', AuthMiddleWare, (request, response) => {
// DO LOGIC
});
Place this middleware inside a new file, and every time you need it, you can just import it rather than copy and paste the same code over and over.
! Note that this is a rough sketch code just to put you on the right track, so you most likely have to adjust this snippet to your own needs and logic.
The benefits of having a single authentication middleware, is so you can use it on every route, so you don't have to use so many chaining in your requests.

How to correctly send an email after an API request?

I'm building an express api with node.js. I'd like to send an email after a user creates an account. I've tried a couple of ways of doing that, but they don't seem to fit my needs, here's some pseudo code:
1. Pass the function that sends the email "at the end" of the route.
Basically, just call the function that sends the email at the end of the route that creates the user account:
app.post("/users", (req, res) => {
const { email, password } = req.body;
// create user...
sendEmailTo(email);
}
My concern with this approach is that the /POST users route is now no longer only for creating a user, but every time it creates one it also sends an email, which may be bad for future use cases where we'd like to create a user separately from the user signup form.
2. Have a unique route specifically for sending emails. In this case, we'd have to make two request to the api, one for creating a user, and another one for sending the email:
app.post("/send", (req, res) => {
const { recipientEmail } = req.body;
sendEmailTo(recipientEmail);
}
What I don't like about this approach is that my api is not an "email" api, I'd like to leave it free for all users to explore and use, I don't want users to just be able to send emails in behalf of the api by requesting this route.
I'm sure there must be other ways of doing this like using some type of callbacks or queues, yet I have found nothing researching for hours, everything is basically a rehearsal of the two options above. Taking into account the need for separation of concerns, how would you go on about sending an email after a request? do I only have these two options?
As I mentioned in the comments, you could just split your route handler in more fine-grained middlewares that would be reusable across all of your routes.
A route-level middleware is very similar to a route handler, except for the fact that instead of producing a response, it actually just does some more focused job and then forwards the request currently being processed to the next middleware or route handler in line. It does this with the extra next function parameter that is injected by express.
In your scenario, you could do something like this:
app.post(
"/users",
createUser,
sendEmail,
send201Response
)
async function createUser(req, res, next) {
// Create user...
​next();
}
​async function sendEmail(req, res, next) {
// Send email...
​next();
​}
function send201Response(req, res) {
​res.sendStatus(201);
}
Then if another use case happens to involve creating a user in a similar way for instance, you could do:
app.post(
"/otherendpoint",
createUser,
// Add other middleware/route handlers...
)
If you are concerned with people using the "/send" route, then you probably want the first option. In the future if you want to create a user but not send an email, then you can just add conditionals to not send the email in certain cases. For example, you could add another property to the body in order to determine if the email should be sent.
app.post("/users", (req, res) => {
const { email, password, sendEmail } = req.body;
// create user...
if(sendEmail === true) sendEmailTo(email);
}

how to allow pass through in express handlers?

I have anPOST api endpoint lets say /users to fetch the list of users.
It is POST because body of the request is very huge and might not fit in url for GET request.
suppose the body of user POST have a key called age , which should give me user of certain age ie kind of filtering
now in express i have route like
app.post('/users', function(r,res){
// function body
})
and i cant actually put any code inside that function body
so i was able to intercept the request by using one more handler for /users and putting it before the original handler but obviously it intercepts all /users requests and breaks earlier functionality
how can i intercept only the request with particular age and then pass through other requests to the original handler, so that original functionality keeps working?
I want to know how can i do this using route handlers and not middlewares ?
i cant mess with the url or request body also
First off, this sounds like a really bad design so really the better way to fix things is to just fix the URL design so you don't have this conflict between code you can and can't modify. I say this because it sounds like you're trying to "hack" into something rather than make a proper design.
If your code is using the regular body-parser middleware, then the body of the post will already be parsed and in req.body. So, you can look for the desired parameter in req.body.age and check its value.
If it meets your criteria, then you can process the request and you're done. If it doesn't meet your request, then you call next() to continue processing to other request handlers.
// make sure this is defined BEFORE other /users request handlers
app.post('/users', function(req, res, next) {
// test some condition here
if (+req.body.age > 30) {
// process the request and send a response
res.send("You're too old");
} else {
// continue processing to other request handlers
next();
}
})
The way I deal with this is if I have a route that works, and I need something else, I add another route that is similar. This way you leave the original alone - which provides a working service. This is what I think you re describing.
You can call routes anything you like. If you want a list of users you can pass a variable like this:
$.get('/contactCard/'+qry);
app.get('/contactCard/:sort', function(req, res) {
var cId = req.params.sort;
console.log('cId: ' + cId);
then you set up your search query and go get the data a bit like this:
let params = {
TableName: ddbTable,
ProjectionExpression : "cEmail,Forename,Surname",
KeyConditionExpression: "ID = :e ",
ExpressionAttributeValues: {
":e" : cId
}
};
console.log("params", JSON.stringify(params, null, 2));
docClient.query(params, function(err, data) {
then you check for error or success:
if (err) {
console.log("Error:", JSON.stringify(err, null, 2));
} else {
console.log("Success", JSON.stringify(data, null, 2));
let contacts = data;
then here you render to the page you want and pass the data as you wish.
res.render('members/contactcard', {
contacts:contacts,
static_path: '/static'
});
}
});

ExpressJS + JWT. What's the proper way to get auth data?

Let's jump to an example. I'll skip some parts like imports, exports.
I have a controller called controllers/book.js with one route:
router.get('/my-books', function(req, res) {
if(Auth.authenticated(req,res)) {
Book.getMyBooks(function(rows) {
response.operationSucceed(req, res, rows);
});
}
});
Then, in a model called models/book.js I have that function:
function getMyBooks(successCallback) {
db.query('SELECT * FROM book b WHERE b.id_user=?', [Auth.getLoggedUID()], function(rows) {
successCallback(rows);
});
}
My question is about Auth.getLoggedUID function.
Assuming that I have a JWT authentication and assuming that I have an UID in payload (is that even acceptable?), what's the best option to retrieve it? Is there any, EXCEPT passing the req every time to every function where I need auth data?
I may have a function execution inside a function, do I need to pass the req through both of them to get the user ID? Like this?:
function getBooks(req) {
getMyBooks(req);
getCriminalBooks(req);
getEvenOtherBooksByAuthor(req, authorId);
}
Honestly I wouldn't like that.
Maybe my whole concept is wrong and I should be doing things differently?
Can someone point me the right direction in scenarios like this?
You can pass UID in header and retrieve it inside your controller as:
var uid =req.header('UID');
Then pass this UID where ever you want there is no need to carryforward whole req object everywhere.
You can use a middleware function. Let's say that every request that hits your endpoints, will have a token which you should check and possibly decode it. After that, you can set the decoded content to the req object. So something like this:
app.use(function(req, res, next) {
// get the token from the request headers most likely.
// verify and decode the token
// set the decoded content to the request
var payload = ..
req.payload = payload;
});
After this you can access the payload in every single endpoint you have. So for example in some controller you can do:
app.get('/hey', function(req, res) {
var payload = req.payload;
});

Passport js authenticate by url

I'm using Express JS and Passport JS for my app.
I want to give a new user the opportunity to automatically login, once, by a specific URL. I can get the user from the database with the information from the URL, so I have an User object (with id, email, hashed password etc.) but I don't know how I can use passport to authenticate the user and login.
I tried executing below function with the user object I got from the database:
req.login(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + req.user.username);
});
source: http://passportjs.org/guide/login/
But that didn't work. Guess it's just because the user object contains the hashed password...
Anyone who ever tried this before and can tell me how it works?
Maybe https://github.com/yarax/passport-url strategy will be useful for you
Base logic is getting argument from url
UrlStrategy.prototype.authenticate = function(req, options) {
var self = this;
function verified(err, user, info) {
if (err) { return self.redirect(self.failRedirect); } // redirect in fail
self.success(user, info); // done callback
}
this._verify(req.query[this.varName], verified);
};
Full example here https://github.com/yarax/passport-url/blob/master/index.js
Heyo, so while #Rax Wunter is totally right, I just saw this question and wanted to say it is NOT A GOOD IDEA to do what you're doing here. You should never be passing a hashed password in a URL string ever. This is a really bad security concern.
What you should do instead is use something like a JSON Web Token (JWT). There are lots of libraries to help with this, but the basic flow goes something like this:
Wherever you are generating your URL, you'll instead generate a JWT that contains the user ID in it.
You'll then build a URL that looks like: https://somesite.com/?token=
On your https://somesite.com endpoint, you'll read in the token, validate it using the JWT library (and a shared secret variable), and this will confirm this token was unmodified (eg: you KNOW this user is who they claim to be).
This strategy above is really great because it means you can safely log someone in, in a trusted way, without compromising security or leaking a password hash at all.
There is not need of any additional module or passport-strategy for this. Adjust below code according to your use case;
router.get('/url/:token', (req, res) => {
User.findOne({token: req.params.token}, (err, user) => {
req.login(user, {}, function(err) {
if (err) { console.error(err); }
else return res.redirect("/home.html");
});
});
});

Categories

Resources