Middleware OR other middleware validation - javascript

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.

Related

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);
}

Express.js middleware extra (fourth) argument

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".

Express user authentication middleware, how much should it do?

I'm trying to learn Express session and authentication handling.
For example:
app.post('/login', authCredentials, function(req, res) {
console.log("second")
});
function authCredentials(req, res, next) {
//this happens first
console.log(req.body) // => { username: etc, password: etc }
next();
}
My question is just how much should my authCredentials function do?
For example if the credentials are correct, I can do something like
res.redirect('/index'). Once I do that, however, what purpose does the second function have?
Other questions:
How would I handle invalid credentials?
If I make authCredentials just return true or false depending on the credentials, doesn't that break the middleware flow because it would never invoke next()?
Is it possible to access anything in authCredentials in the anonymous callback after it? Basically in the function(req, res) { }?
The answer depends on your authentication strategy i.e. are you using session identifiers, access tokens, etc.
In either case I suggest that you break out the credential exchange (aka login) from the authentication.
function usernamePasswordExchange(req,res,next){
var username = req.body.username;
var password = req.body.password;
callToAuthService(username,password,function(err,user){
if(err){
next(err); // bad password, user doesn’t exist, etc
}else{
/*
this part depends on your application. do you use
sessions or access tokens? you need to send the user
something that they can use for authentication on
subsequent requests
*/
res.end(/* send something */);
}
});
}
function authenticate(req,res,next){
/*
read the cookie, access token, etc.
verify that it is legit and then find
the user that it’s associated with
*/
validateRequestAndGetUser(req,function(err,user){
if(err){
next(err); // session expired, tampered, revoked
}else{
req.user = user;
next();
}
});
}
app.post('/login',usernamePasswordExchange);
app.get('/protected-resource',authenticate,function(req,res,next){
/*
If we are here we know the user is authenticated and we
can know who the user is by referencing req.user
*/
});
Disclaimer: I work at Stormpath and we spend a lot of time writing
authentication code :) I just wrote our newest library, stormpath-sdk-express,
which has a concrete implementation of my suggestions
You want to add your authCredentials middleware to every end point that needs authentication. app.post('/login') usually does not need any as you want to access this end point to actually get credentials in the first place.
When credentials are correct/valid you simply invoke next() and the workflow will jump to the next middleware or the actual end point. If there was an error, invoke next() with an error object like next(new Error('could not authenticate'); for instance. Add an error route to your general routing and the error will be handled there:
app.use(function(err, req, res, next) {
res.render('error', err);
});
Should be answered by now.
A middleware does not return a value. It either calls next() or ends the process differently by calling res.send().
There are different approaches to pass variables from one middleware to another. The most common is probably to attach the desired value to the req parameter.
authenticate is an asychronous function in the following example:
function authCredentials(req, res, next) {
authenticate(req.body, function(err, user) {
if (err) {
return next(err);
}
req.user = user;
next();
});
}

Node.js preresponse with Express

So I'm doing a web page with Node.js and Express framework. I already have registration and login (I'm holding users id in a session). Next step is to render different page whenever a user is authenticated.
Some of those pages require a User object which is just a mapping of a user from my db. So whenever an authenticated request comes I need to retrieve the user from my db. But writing this code every time seems to be a bad way to do this. So here's the question: is it possible (and if it, then how?) to do, say preresponse, so I can automaticaly retrieve User object whenever I know that the user is authenticated and THEN do the main response?
Middleware is what you are referring to. Middleware is just a function that gets called sequentially when the route is triggered. So to have a loadUser function:
function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
}
app.get('/user/:id', loadUser, function(req, res){
res.send('Viewing user ' + req.user.name);
});
You can define as many middleware functions as your need. Just be sure to call next() at the end to pass the route handling on to the next function.
This EXACT example is covered in the express.js route middleware docs. Go read it and you'll see the pattern of using middleware to factor out common functionality that you need at many route paths in your app.

Passing error message to template through redirect in Express/Node.js

In my Node.js application, I have a function (routed by Express) which presents a form to the user:
app.get('/register', function (req, res) {
res.render('form');
});
I have another function, routed to the same URL, but which handles POST requests, which receives the data submitted by the previous form. If the form does not validate, it redirects the user back to the form; otherwise, it does what should be done:
app.post('/register', function (req, res) {
if (validate(req.registerForm)) return res.redirect('back');
persistStuff(req.registerForm, function (err, data) {
// Do error verification etc.
res.redirect('back')
});
});
What I want to do is to send a error message to be presented, in the line:
if (validate(req.registerForm)) return res.redirect('back');
To write something like
if (validate(req.registerForm)) return res.render('form', {msg:'invalid'});
is unacceptable because I want to follow the POST-REDIRECT-GET pattern. I could do something like
if (validate(req.registerForm)) return res.redirect('/register?msg=invalid');
but it would hardcode an URL in my code and I'd prefer to avoid it. Is there another way to do it?
You need to use flash notifications, and it is built into express.
You'll add a message like so: req.flash("error", "Invalid form...");
You'll need a dynamic handler to add the messages to your rendered template, or you can check out the ones TJ has made for express. (express-messages)
You could simply have it redirect as res.redirect('..?error=1')
the ? tag tells the browser that it is a set of optional parameters and the .. is just a pathname relative recall (like calling cd .. on terminal to move back one directory)
and you're browser will direct to the appropriate page with that tag at the end: http://.....?error=1
then you can simply pull the error on the appropriate page by doing a:
if (req.param("error" == 1)) {
// do stuff bassed off that error match
};
you can hardcode in several different error values and have it respond appropriately depending on what error occurred

Categories

Resources