Node.js resource based ACL - node.js

I am implementing a simple Access Control system in Node, and I am wondering what can be the best approach for what I am doing.
I am using Node ACL and it is not clear to me how to block on a per-resource basis.
Let's take the following example:
USER ->* PROJECT ->* ENTRY. Users can have multiple projects which contains many entries. Users can be ADMIN or USER.
I created an endpoint /entry/{ID} where user can access an entry detail. The endpoint is accessible to everyone, ADMINs can see all entries, but for User I need to do something similar:
app.get('/entry/{id}', (req, res) => {
if (user.admin) {
// Return eveything
}
else {
if (entry.project == user.project) {
// return it
}
else {
// Unathorized
}
}
})
Is there a better approach/pattern to implement this checks on ownership on a resource?

It's a very broad question so I'll try to give you a couple hints as my answer, but
Is there an ACL pattern in javascript?
There's a number of solutions but I wouldn't call any of those a pattern. I'll be very subjective now, but the ways of passport.js and similar modules are non-transparent to say the least - and it's not really an ACL...
Someone may say - hey, it's node.js, there must be module to do that and make your node_modules heavier but searching for a good acl module in npm, I only found some outdated ones and tightly bound with express. Since your question wasn't which is the best npm module for acl I gave up looking for such at page 3, which doesn't mean there ain't something ready so you may want to look more closely.
I think your implementation could be considered acceptable, with some minor corrections or hints as I mentioned:
Separate your request logic from access control logic
In your code everything happens in one callback - that's definitely very efficient, but also very hard to support in longer term. You see, it'll end up in the same code in lots of those if's above in all the callbacks. It's very simple to separate the logic - simply implement the same path in two callbacks (they'll be run in the order they were defined), so:
app.all('/entry/{id}', (req, res, next) => {
const {user, entry} = extractFromRequest(req);
if (user.admin || entry.project === user.project) {
next();
} else {
res.status(403).send("Forbidden");
}
});
app.get('/entry/{id}', (req, res) => {
// simply respond here
})
This way the first callback checks if the user has access and this won't affect the logic of the response. The usage of the next() is specific to express-like frameworks which I assumed you use looking at your code - when you call it the next handler will be executed, otherwise no other handlers will be run.
See Express.js app.all documentation for an acl example.
Use a service wide acl
It's much more secure to keep a basic ACL in a single place and not to define it per path unless necessary. This way you won't omit one path and won't leave a security hole somewhere in middle of request. For this we need to split the ACL into parts:
URL access check (if path is public/open for all users)
User and session validity check (user is logged in, session is not expired)
Admin/user check (so permission level)
Otherwise we don't allow anything.
app.all('*', (req, res, next) => {
if (path.isPublic) next(); // public paths can be unlogged
else if (user.valid && user.expires > Date.now()) next(); // session and user must be valid
else if (user.admin) next(); // admin can go anywhere
else if (path.isOpen && user.valid) next(); // paths for logged in users may also pass
else throw new Error("Forbidden");
});
This check is not very restrictive but we won't need to repeat ourselves. Also notice the throw Error at the bottom - we'll handle this in an error handler:
app.use(function (err, req, res, next) {
if (err.message === "Forbidden") res.status(403).send("Forbidden");
else res.status(500).send("Something broke");
})
Any handler with 4 arguments will be considered an error handler by Express.js.
On the specific path level if there's any need for ACL's, simply throw an error to the handler:
app.all('/entry/{id}', (req, res, next) => {
if (!user.admin && user.project !== entry.project) throw new Error("Forbidden");
// then respond...
});
Which reminds me of another hint...
Don't use user.admin
Ok, fine, use it if you like. I don't. The first attempt to hack your code will be by trying to set admin on any object that has properties. It's a common name in a common security check so it's like leaving your WiFI AP login at factory defaults.
I'd recommend using roles and permissions. A role contains a set of permissions, a user has some roles (or one role which is simpler but gives you less options). Roles may be also assigned to project.
It's easily a whole article about this so here's some further reading on Role-based ACL.
Use standard HTTP responses
Some of this mentioned above, but it's a good practice to simply use one of standard 4xx HTTP code status as response - this will be meaningful for the client. In essence reply 401 when the user is not logged in (or session expired), 403 when there's no sufficient priviledge, 429 when use limits are exceeded. more codes and what to do when the request is a teapot in Wikipedia.
As to implementation itself I do like to create a simple AuthError class and use it to throw errors from the app.
class AuthError extends Error {
constructor(status, message = "Access denied") {
super(message);
this.status = status;
}
}
It's really easy to both handle and throw such an error in the code, like this:
app.all('*', (req, res, next) => {
// check if all good, but be more talkative otherwise
if (!path.isOpen && !user.valid) throw new AuthError(401, "Unauthenticated");
throw new AuthError(403);
});
function checkRoles(user, entry) {
// do some checks or...
throw new AuthError(403, "Insufficient Priviledges");
}
app.get('/entry/{id}', (req, res) => {
checkRoles(user, entry); // throws AuthError
// or respond...
})
And in your error handler you send your status/message as caught from your code:
app.use(function (err, req, res, next) {
if (err instanceof AuthError) res.send(err.status).send(err.message);
else res.status(500).send('Something broke!')
})
Don't reply immediately
Finally - this is more of a security feature and a safety feature at the same time. Every time you respond with an error message, why not sleep a couple seconds? This will hurt you in terms of memory, but it will hurt just a little and it'll hurt a possible attacker a lot because they wait for the outcome longer. Moreover it's super simple to implement in just one place:
app.use(function (err, req, res, next) {
// some errors from the app can be handled here - you can respond immediately if
// you think it's better.
if (err instanceof AppError) return res.send(err.status).send(err.message);
setTimeout(() => {
if (err instanceof AuthError) res.send(err.status).send(err.message);
else res.status(500).send('Something broke!')
}, 3000);
})
Phew... I don't think this list is exhaustive, but in my view it's a sensible start.

Related

Handle POST request from multiple routes all at once

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
}

Hows should I structure middleware to verify an ID exists in an external service in a Node/Express app?

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

500 TypeError: listener must be a function at ServerResponse.addListener

Based upon the voted answer events.js:130 throw TypeError('listener must be a function')
I am trying to implement Logging wherein we save every action in DB. To cater this, I was planning to listen to finish or close event of response stream in app.js
I understand that I need to put this code in Express frameworks root folder.Can you please specify where exactly so that I can access listen to this event and access requested URL as well.
Below is the code snippet
app.use(function (req, res,next) {
function afterResponse (req,res,next){
console.log("Response finished from the path "+ req.url);
}
res.on('finish', afterResponse(req,res,next));
});
While the 'finish' event emitted by the response object is not in the documentation, it does exist. However, as an undocumented feature, you may want to avoid it since it could be removed anytime. Without more information about exactly what you are trying to accomplish, it's hard to say whether there might be a better alternative, although I suspect there is. With that caveat, here is the answer to your question.
If you want to capture every response, you need to define a middleware function near the top of your app, before any response has been sent (order matters in Express 4). The example you have above is too complicated; the 'finish' event listener doesn't take parameters. Instead, you can simply do this:
// This example uses ES2015 syntax
app.use((req, res, next) => {
res.on('finish', () => {
console.log(`Response finished from the path ${req.url}`);
});
next();
});
The example above adds the listener before any request is sent, but the listener will only be called after the request is sent. Make sure the listener itself takes no parameters; if you pass req as a parameter as in your example, it will be undefined, which is exactly what you don't want. Just define your litsener in the middleware function itself, and you can access req or res as much as you like.
Alternatively, if you don't want the listener to be redefined on every request, you can access req through the response object itself, which is available as the this context within the listener:
// This example uses ES2015 syntax
app.use((req, res, next) => {
res.on('finish', onFinish);
next();
});
function onFinish() {
console.log(`Response finished from the path ${this.req.url}`);
}

ExpressJS authorizing users with middleware?

Let's say I want to restrict the access of a resource to a certain group of people who meet some conditions. What I'm doing right now is defining an authorization middleware that checks if the req.user is meeting those conditions.
module.exports.requiresCondition = function(req, res, next){
Model.findOne({condition: condition}, function(err, model){
//check if various conditions are met. If not, return 401
res.locals.model = model;
return next();
}
}
The problem I have with this is that I can't choose what data to project because the routes that come after might use different parts of the model. This means I have to get the whole model every time, which becomes inefficient as the documents get larger and larger. Of course, I can just query once in the middleware with the keys I need to authorize and query again in the actual controller, but that doesn't seem particularly efficient either. Is there a better way to authorize users?
If different routes require different data you can modularize your middleware layer. composable-middelware https://www.npmjs.com/package/composable-middleware is perfect for that.
So let's say you have couple of routes:
import compose from 'composable-middleware';
router.get('/type1/:id', compose().use(Auth.hasAppAccess()).use(TypeMiddleware.getType1()), ctrl.doType1);
router.get('/type2/:id', compose().use(Auth.hasAppAccess()).use(TypeMiddleware.getType2()).use(TypeMiddleware.evenMoreOperations()), ctrl.doType2);
Then your middleware itself
import compose from 'composable-middleware';
export function isAuthenticated() {
return compose()
.use(function(req, res, next) {
//do your stuff here
})
}
//similarly for other middelware functions

nodejs express profile property in request

I got very confused for one usage:
In the route file:
app.param('userId', users.load);
And the users.load function:
exports.load = function (req, res, next, id) {
var options = {
criteria: { _id : id }
};
User.load(options, function (err, user) {
if (err) return next(err);
if (!user) return next(new Error('Failed to load User ' + id));
req.profile = user;
next();
});
};
Here, route should have the userId to response but why does the author use req.profile here. profile is not a property.
Anyone can help?
Thanks.
What the code does is this: for routes that have a userId parameter (that is, routes that look similar to this: /user/:userId), Express will call the load() function before the route handler is called.
The load function loads the user profile belonging to the userId from the database, and adds it to req as a newly created property req.profile.
The .profile property name is arbitrarily named by the author and demonstrates the fact that it's perfectly valid to add properties to req (or res, for that matter, but convention is to add these properties to req).
In the route handler, you can then use req.profile. It's basically a way of propagating data from middleware and app.param() implementations to other parts of the route handling.
the line req.profile = users; think of it this way, 'i want to take all the powers of the users and paste them to req.profile' why? remember this part is sort of a middleware if you want to target any of the read, update and delete code it has to pass through here, it only makes sense if it involves the req, because you are practically requesting to access the said pages (read, edit and delete or any other:userId page) now the profile name doesn't matter you could use any name but its sort of a convention in the community to use the profile name.

Resources