Restify: Calling a middleware within a middleware - node.js

I have a middleware for authentication which decrypts the token in the header and put the userId from the token to req.userId. Otherwise it throws an error if token doesn't exists or is invalid.
I call it like this in routes where I need authentication:
server.get('/api/somecall', authMiddleware, callService.somecall);
Now I will also have some routes which will carry a user ID in its params, like this:
server.get('/api/somecall/:userId', callService.somecall);
Here :userId can be a Mongoose Object ID or just me.
So I want to write another middleware which will be called for all routes that looks for particular params like userId and/or adminId. And if their value equals me, I want to make sure that authMiddleware automatically comes to action, PLUS replace me with the logged in user's ID so the callService.somecall handles the logic inside as it received a Mongoose Object ID.
There are two problems I'm facing:
How do I get req.params.userId in a middleware which is called before server.get(...)?
Suppose if I get req.params.userId somehow, how do I make the middleware to:
perform some condition (check if req.params.userId === 'me'),
then call authMiddleware so that I get req.userId from token, and
then perform more actions (replace req.params.userId = req.userId
then call next() to continue.

Your question is a little confusing so I don't know if this will help, but why not do something like this:
server.get('/api/somecall/:userId', otherMiddleware, callService.somecall);
function otherMiddleware(req, res, next){
if(req.params.userId === 'me'){
authMiddleware(req, res, next);
}
// Do other stuff here.
next();
}

Related

passport.authorize() clearing req.user with multiple (same) strategies

I need two instances of a passport local strategy ("localA" and "localB"), one instance of this authenticates against a collection "colA" in "DbA" and is used in one route sequence (Route A), the other instance authenticates against another collection (ColB) in "DbB" and is used in a second route sequence (Route B).
In both cases, access to "req.user" is needed. In the first route, "req.user" has its expected defined value, however, in the second route, "req.user" is undefined. Here is an extract of what I believe to be the relevant code:
const userA = DbA.model(`colA`, userASchema);
passport.use(`localA`, new passportLocalStrategy({usernameField: `email`, passwordField: `password`}, userA.authenticate()));
passport.serializeUser(userA.serializeUser());
passport.deserializeUser(userA.deserializeUser());
const userB = DbB.model(`colB`, userBSchema);
passport.use(`localB`, new passportLocalStrategy({usernameField: `email`, passwordField: `password`}, userB.authenticate()));
passport.serializeUser(userB.serializeUser());
passport.deserializeUser(userB.deserializeUser());
//Route A
app.post('/routeA', passport.authenticate(`localA`), (req, res) => {
res.redirect(`/routeAA`);
});
app.get('/routeAA', function (req, res) {
res.render('routeA.ejs');
});
//Route B
app.post('/routeB', passport.authenticate(`localB`), (req, res) => {
res.redirect(`/routeBB`);
});
app.get('/routeBB', function (req, res) {
res.render('routeB.ejs');
});
It appears that this is not a new issue. Here are some related posts:
https://github.com/jaredhanson/passport/issues/803
Passport.js multiple local strategies and req.user
In post 803 user #nathan6am, states ....
I ran into the same problem, it's a bit of a hacky solution but I got
around it by using req.session?.passport?.user and deserializing the
user manually instead of using req.user in the callback.
I'm still struggling to understand how to manually de-serialize so as to force req.user to re-acquire correct values, but I did confirm that the contents of "req.session.passport.user" (for my schema) is the user's email address, so I saved that in a session variable, "req.session.email". My plan was then to write some middleware (in the next route) that would search my DB, using the contents of req.session.email, then use that DB record to extract the data that I would subsequently pass onto my rendered ejs file. It would have looked something like this:
//Route B
app.post('/routeB', passport.authenticate(`localB`), (req, res) => {
req.session.email = req.session.passport.user;
res.redirect(`/routeBB`);
});
app.get('/routeBB', hack, function (req, res) {
res.render('routeB.ejs' {key1: value1, key2: value2 });
});
function hack(req, res, next) {
// find user in DB using req.session.email
// extract need data from DB
// build object comprised of needed data
// key1: value1
// key2: value2
return next();
}
But then I realized that I have other middleware, for other routes, that rely on req.user for authorization (req.user.role=some role) ... so having req.user as undefined isn't something that can work. Is there anyone who can add some color to #nathan6am's post?
Thank you.
Tim.

make a final authentication middleware to return data and validate user?

hello there I'm trying to implement a method to handle returning data in my express project, I want to pass data in all my controllers to a final middleware which its job is to check whether the user has the authority to operate on the document and then return the data ,
req.document = order;
req.validateFields = [
order.senderId._id.toString(),
order.recieverId._id.toString(),
order.driverId._id.toString(),
];
next();
then I handle it in my final middleware like this
app.use(async (req, res, next) => {
const document = req.document;
if (!req.validateFields.includes(req.user._id.toString()))
console.log("not authorized");
await document.save();
res.status(200).json({
document,
});
});
first of all is this a bad practice ? since I save my document after passing the user validations so the data manipulations that have been done in the controllers will not be saved if the user was not authenticated , and then how to handle the other document methods like findByIdandDelete, findByIdAndUpdate which will save the document right away?
thanks.

Simple Express JS API token

I'm wonder how I can implement a simple API auth token without need for users? I just want one token that I can authenticate by adding it as a parameter when I call my API.
All the current examples on Google seem to be over engineered for my needs. I have some data stored in MongoDB and I simply serve this data like so:
app.get("/", (req, res) => {
Car.find((err, cars) => {
if(err){
throw err;
}
res.json({"cars": cars});
});
});
Is it possible to add some simple middleware that checks my environment file for an element with the name of api_token. Then check that the api_token in my env file matches the parameter that has been passed as a URL query.
Is there a way to do this? I'm aware that you can't use URL queries on a GET route so I am unsure how this would work.
Sure, use middleware: https://expressjs.com/en/guide/using-middleware.html
For your case, it can be as simple as the following:
// checkForApiToken.js
module.exports = (req, res, next) => {
const apiToken = req.header("api-token");
if (process.env.API_TOKEN !== apiToken) {
next(new Error("Unauthorized."));
return;
}
next();
}
The logic is simple:
Retrieve API-TOKEN value from the header.
Check it matches what I've defined in my env.
Does not match, throw an error by passing an error object into the next function.
Matches so I call next() with no error to proceed to the next request handler.
You would then use it like so:
app.get("/", checkForApiToken, async (req, res) => {
const cars = await Car.find().exec();
res.json({ cars });
});
Remember, Tokens are responsible for at least 2 API security mandatory things, authenticate and authorize. You don't need to authenticate users, but you need to be sure that the token you received is a Token and not a "HEADER".
If you use a static token,or anything else, first time i get your token your security is down. You need to specify AT LEAST when this token will die, and if it is a valid one based on some random assignment. You can't check for it's presence, you need to check if it is valid.
Javascript has an amazing convention background, whenever you have the opportunity, follow the convention. It is easier as it seems to implement a JWT based.
Just follow this : https://github.com/auth0/node-jsonwebtoken
and implement it in your middleware as you wishh.
Easily as this /
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
You can always redo the concepts by using a "Web HEADER" and calling it a "TOKEN". But as i said, it is a "Web Header" not an "WEB TOKEN".

I am wondering how to communicate between controllers

I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in the e-mail to implement the membership method using e-mail authentication.
//emailcontroller.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
request('http://localhost:8080/api/user', function(data) {
});
}
});
//usercontroller.js
router.post('/api/user', function(req, res) {
var user = new User();
user.userId = req.body.userId;
user.userPw = req.body.userPw;
user.save();
});
I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in email in order to implement membership method using email authentication.
As mentioned above, the email controller and the user controller are divided and each is routed. I want to modularize the code so that I want to call the existing user creation API to use it for general purpose rather than creating and exports common functions for a specific controller.
/*I do not want to implement it this way.*/
//emailController.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
userContoller.createUserFromEmail(userId, userPw);
}
});
//userController.js
exports.createUserFromEmail = function(userId, userPw) {
var user = new User();
user.userId = userId;
user.userPw = userPw;
user.save();
}
However, I have never seen communication between controllers in many examples. So I do not know if the way I thought was right. Rather, I think the cost of calling api internally on the server might be higher.
I want to know the correct pattern for communication between controllers. Please bear in mind that there is only a stack overflow when raising a question.
You got the right idea about exposing your API functionality as stand-alone functions (or classes). To avoid duplication, just call your internal methods from within your route handlers. So in your example:
router.post('/api/user', function(req, res) {
createUserFromEmail(req.body.userId, req.body.userPw);
});
In my own projects, I use classes to create my API. First I define a class with just the functionality and then I expose the methods in the route handlers:
export default class User {
read() {
}
create() {
}
update() {
}
delete() {
}
}
const user = new User();
router.get('/user/:id', (req, res) => user.read(req.params.id));
router.post('/user', (req, res) => user.create(req.body.data));
router.put('/user/:id', (req, res) => user.update(req.params.id, req.body.data));
router.delete('/user/:id', (req, res) => user.delete(req.params.id));
This should give you an idea of what you can do. You can write custom middleware and class decorators to reduce the boilerplate.
From your question what I understood:
You want to validate internally the token passed in query parameter, before doing anything else in the user controller.
I believe you are using express, and with express comes middlewares.
From docs:
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
What I usually do and a generally good practice is, pass the token in create user api and attach to email body.
for example:
api/user?token=somerandomstringloremispum
Route file:
router.post('/user', validateEmail, userController.create);
here validateEmail is a middleware function and will be invoked before create method of userController.
Now in your validateToken method, you can simply validate your token like:
function validateEmail (req, res, next) {
if(!valid(req.query.token)) {
//return with appropriate invalid token msg using res.json() or however you like
}
// if validated call `next middleware` like so:
next();
// this will allow `create` method of userController be invoked
}

How to handle authorization in a layered nodejs with passport app?

So I'm trying to build an app with nodejs, using express and passport, but as I try to do some kind of TDD, I want to decouple bussiness logic from controllers.
So I have a common scenario like this:
An authenticated user wants to delete an item, he sends a request to the api:
DELETE /api/item/1
The request is handled by the controller method, which passes the user that makes the request to the next layer (which doesn't seem like a good approach):
exports.delete = function (req, res, next) {
var itemId = req.params.id;
var userId = req.user._id;
itemService.delete(itemId, userId, function (err, item) {
if (err) next(err);
return res.json(item);
});
};
The service layer (or whatever you want to call it, the layer that has all the bussiness logic) then checks if the item is owned by that user, and then deletes it or returns an error otherwise.
So I was wondering if there is any way to get the current user from any layer without passing it from the controller.
You should ensure the user owns the item before even passing it to the controller, in the routes configuration:
app.del('/api/item/1', ensureUserOwnsItem, itemController.delete);
This will cause the function ensureUserOwnsItem to be called before calling the controller.
It should looks like this:
function ensureUserOwnsItem(req, res, next) {
if (/* user owns item */) {
next();
} else {
res.send(401, 'You can\'t delete an item you don\'t own');
}
}
You would be able to reuse it on the POST route:
app.post('/api/item/1', ensureUserOwnsItem, itemController.post);
I recommend you put this function inside an AuthController or something like that.

Resources