Hi please can anyone help me. I want user to be able to access only what they are permitted to access.
I have been looking at several Access Control List packages. I have not made a final decision.
A restaurant which would have several levels of permission.
The customer can place several orders and can see what foods he has ordered
He can also modify the order only within a specified time period e.g. before the order is being processed.
The customer can only view his own order and the stage which the order is.
A staff can only check the order than is under his menu and state how much the order would cost and how long the order would take.
Another staff would be in charge of the stores and how things goes in and goes out.
A Staff can be in charge of a department and at the same time allow input to a menu which is under another department.
I have been looking at how I can go about putting this into Express.js and mongodb
I have looked at the following
https://github.com/optimalbits/node_acl main focus
https://www.npmjs.com/package/acl
https://www.npmjs.com/package/express-acl
But I have not got the granularity and the mix which I stated above.
The permission would be based majorly on data. It has been a little confusing as to how I can go about that.
Any help will be useful
I use mongoose as my driver
As I said in my comments, this design has a bit of business logic that might make it not the best fit for regular ACL-type security controls. On the surface, it seems like the easier to figure out solution would be to just implement your business rules in your Mongoose models or Controller code, depending on your preference. That said, a key piece of doing any of this with an ACL-like approach comes down to your URL design. For example, it's tempting to make your API such that all orders are available through /api/orders and maybe a person would query their own orders via /api/orders?userId=12345. But that makes most ACL-based approaches fail. Instead you have to think about the API in terms of the hierarchy as you want it secured (regardless of if all orders are stored in the Orders Mongoose model, and persist in the orders collection).
So using your first requirement as an example
The customer can place several orders and can see what foods he has ordered
The focus here is that you are securing things by the customer 'owner' of the orders, so to secure it that way you need to setup your route that way, eg (assuming you're using the first middleware you asked about):
app.post('/api/customer/:customerId/orders', acl.middleware(), (req, res, next) => {
const order = new Order(req.body); // TODO: whitelist what info you take in here
order.customerId = req.user.id; // assuming you have a logged-in user that does this
order.save(e => {
if (e) return next(e);
return res.status(201).send(order);
});
});
To support this, you'd register your ACL info as such:
acl.allow('12345', '/api/customer/12345/orders', ['post']);
Minimally, you'd do that. You would likely provide more options such as 'get', etc. As you can guess, this means that you'll need to register permissions for individual users whenever you create them (to support the concept of 'ownership').
For your second requirement,
He can also modify the order only within a specified time period e.g. before the order is being processed.
Despite what I said before, you could arguably do this in an ACL if you really wanted to. For example, you could make the URL account for the status, like '/api/customers/12345/orders/modifiable/6789', but that becomes hard to maintain in my experience. You're better off putting that logic in the controller or the Mongoose logic. It's probably simpler to do it in the controller, unless you plan on using your Mongoose models outside of the Express app. Something like this (note, not using the ACL in this case, though you could if you wanted):
app.param('orderId', (req, res, next, id) => {
Order.findById(id, (err, order) => {
if (err) return next(err);
if (order) {
req.order = order;
return next();
}
const notFound = new Error('Order not found');
notFound.status = 404;
return next(notFound);
});
});
app.put('/api/orders/:orderId', (req, res, next) => {
if (req.order.status !== 'pending') {// or whatever your code setup is
const notProcessable = new Error('Cannot modify an order in process');
notProcessable.status = 422;
return next(notProcessable);
}
// handle the modification and save stuff
});
Related
I have a Node, Express app with EJS at the front end.
I have this middleware function with passport that runs before all create, edit, delete routes.
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) {
if (req.user._id != "12345") {
res.redirect("/error");
}
return next();
}
res.redirect("/error");
}
The best I could come up with to verify that my admin user is the one trying to access the route is to check by user id in mongo db with req.user._id
Is there a better way to handle admin user access to routes and html components?
This looks essentially correct to me. It is important to note there are two layers: authentication and authorization.
Authentication is effectively a boolean: is the user authenticated? You have your function there req.isAuthenticated(). This might logically return a boolean, true or false, for whether or not the user is authenticated (ie: logged in).
Authorization could perhaps take many forms, but is effectively again a boolean: does this user meet the criteria to access this resource.
Authentication is usually well-served in a middleware, somewhere central that runs before "the endpoint", but authorization is not as simple because any endpoint could allow an operation or deny it, or it could respond differently depending on the user's privilege.
This whole conversation is perhaps quite deep in a roles & permissions discussion.
I think the answer is dependant on the app. Your app has two requirements: one, the user must be authenticated, and two, the user might need to be an admin.
The answer will be somewhere around: what is the most simple way to accomplish this?
In my opinion, you would consider the SOLID principles and note that you have one middleware, so it should have one responsibility: to check if the user is authenticated. Next, maybe you should have another middleware called isAdmin that runs for every endpoint that requires this extra condition. That's really all it is--an extra check. You shouldn't pollute your isLoggedIn middleware with that extra stuff because it makes that middleware less reuseable and less composable.
An isAdmin middleware would be a good idea, but it could also be a good idea to simply have it as a function inside every endpoint that requires that admin-check. Which way is better? Well first, which way is simpler. Which is less code but that is also still simple to understand.
Because this is roles and permissions, is there maybe a more robust way to keep track of which users are admins? If you have code that runs like if (req.user._id === 12345) {}, it requires special knowledge to remember this place in the code, so it is kind of brittle and "more likely" to fail. Maybe it would be a good idea to add a column to your users table for is_admin which could be null or 0 for every user except your user which could have 1. Then you could check if (req.user.is_admin) {}.
That might lead us to a middleware function like:
function isAdmin(req, res, next) {
if (req.isAuthenticated() && (req.user.is_admin === 1)) {
return next();
}
return res.redirect(403, "/error");
}
You could also do something like change that is_admin database column to instead something like role which could be 1 for every user except your admin users which could have maybe 2. That would allow you to do something like:
function hasAuthorization(req, res, next) {
if (req.isAuthenticated() && (req.user.role >= 2)) {
return next();
}
return res.redirect(403, "/error");
}
Such logic there can allow you to have increasing privilege roles: maybe 1 is regular, 2 is manager, 3 is admin, 4 is super-admin. If the user's role is less than 4, they don't have permission.
In my opinion, this idea of increasing privilege is great except the critical flaw might come later when you refactor your routes or your roles. You'd have to remember everywhere you had > 3 and change it to > 4. If you forget any, that is kind of a security flaw immediately, so I trust you understand my argument there.
Rather than seeing operators like < and >. I would rather see checks for specific roles, like:
if ((req.user.role === 'ADMIN') || (req.user.role === 'MANAGER')) {}
We have to keep coming back to the idea: what is the most simple? Is it simpler to make an isAdmin middleware and then group all your admin routes under the middleware? or is it simpler to put the authorization-check inside each route?
Check this example here:
import isAdmin from '../auth/isAdmin.js'
app.get('/admin', (req, res) => {
if (!isAdmin(req.user)) {
return res.redirect(403, '/error')
}
return res.render('admin')
})
This might be more work, but it's also potentially more fine-grained, so you have more control.
app.get('/foobars', (req, res) => {
if (isAdmin(req.user)) {
return res.json(/* all foobar records from all accounts */)
}
if (isManager(req.user)) {
return res.json(/* all foobar records from the user's account */)
}
return res.json({ error: 'Insufficient privileges for this operation' })
})
My final thought is that, you should have two functions: one checks if the user is authenticated, and one checks if the user is authorized. Then you can stack them together either in a middleware or in two middlewares, or in a route.
I also think you should find a more robust way to check if the user is your self. If you move your app from one computer to another, the user ID might change next time you populate your users table, so id isn't a strong way to latch onto your user.
I am building an application using node.js and socket.io. I would like to create a table of users who are actively browsing the site at any given moment, which will update dynamically.
I am setting a cookie to give each browser a unique ID, and have a mysql database of all users (whether online or not); however, I'm not sure how best to use these two pieces of information to determine who is, and who isn't, actively browsing right now.
The simplest way would seem to be to store the cookie & socket IDs in an array, but I have read that global variables (which presumably this would have to be) are generally bad, and to be avoided.
Alternatively I could create a new database table, where IDs are inserted and deleted when a socket connects/disconnects; but I'm not sure whether this would be overkill.
Is one of these methods any better than the other, or is there a way of tracking this information which I haven't thought of yet?
You can keep track of active users in memory without it being a global variable. It can simply be a module level variable. This is one of the advantages of the nodejs module system.
The reasons to put it in a database instead of memory are:
You have multiple servers so you need a centralized place to put the data
You want the data stored persistently so if the server is restarted (normally or abnormally) you will have the recent data
The reasons for not putting it directly in a database:
It's a significant load of new database operations since you have to update the data on every single incoming request.
You can sometimes get the persistence without directly using a database by logging the access to a log file and then running chron jobs that parse the logs and do bulk addition of data to the database. This has a downside in that it's not as easy to query live data (since the most recent data is sitting in databases and hasn't been parsed yet).
For an in-memory store, you could do something like this:
// middleware that keeps track of user access
let userAccessMap = new Map();
app.use((req, res, next) => {
// get userId from the cookie (substitute your own cookie logic here)
let id = id: req.cookie.userID;
let lastAccess = Date.now();
// if you want to keep track of more than just lastAccess,
// you can store an object of data here instead of just the lastAccess time
// To update it, you would get the previous object, update some properties
// in it, and then set it back in the userAccessMap
userAccessMap.set(id, lastAccess);
next();
});
// routinely clean up the userAccessMap to remove old access times
// so it doesn't just grow forever
const cleanupFrequency = 30 * 60 * 1000; // run cleanup every 30 minutes
const cleanupTarget = 24 * 60 * 60 * 1000; // clean out users who haven't been here in the last day
setInterval(() => {
let now = Date.now();
for (let [id, lastAccess] of userAccessMap.entries()) {
if (now - lastAccess > cleanupTarget) {
// delete users who haven't been here in a long time
userAccessMap.delete(id);
}
}
}, cleanupFrequncy);
// Then, create some sort of adminstrative interface (probably with some sort of access protection)
// that gives you access to the user access info
// This might even be available in a separate web server on a separate port that isn't open to the general publoic
app.get("/userAccessData", (req, res) => {
// perhaps convert this to a human readable user name by looking up the user id
// also may want to sort the data by recentAccess
res.json(Array.from(userAccessMap));
});
I have several types of projects, each project handles a different code.
I want to make a route handler for every project.
For example
router.use('/projects/dog_name', dog)
router.use('/projects/cat_name', cat)
The project name is dynamic and comes from retrieving data from the database and is updated from time to time.
What can I do?
If it's going to be code that decides how to treat the router, then you probably want one generic route that can then use code logic to distribute the handling of the route dynamically based on some sort of dynamic lookup:
let dynamicRoutes = new Map([["golden", dog], ["persian", cat]]);
router.use("/projects/:name", (req, res, next) => {
let target = dynamicRoutes.get(req.params.name);
if (target) {
target(req, res, next);
} else {
next();
}
});
Then, you can add/remove items from the dynamicRoutes data structure to add/remove new dynamic routes at any time. If you have logic involving a database lookup, you can use that instead of the map.get(). Or, maybe for performance reasons, you don't query the database on every route hit, but rather query the database when something changes and update the dynamicRoutes data structure (like a cache) whenever you know something has changed in the database.
I'm using koa2 and koa-router together with sequelize on top. I want to be able to control user access based on their roles in the database, and it's been working somewhat so far. I made my own RBAC implementation, but I'm having some trouble.
I need to quit execution BEFORE any endpoint is hit if the user doesn't have access, considering endpoints can do any action (like inserting a new item etc.). This makes perfect sense, I realize I could potentially use transactions with Sequelize, but I find that would add more overhead and deadline is closing in.
My implementation so far looks somewhat like the following:
// initialize.js
initalizeRoutes()
initializeServerMiddleware()
Server middleware is registered after routes.
// function initializeRoutes
app.router = require('koa-router')
app.router.use('*', access_control(app))
require('./routes_init')
routes_init just runs a function which recursively parses a folder and imports all middleware definitions.
// function initializeServerMiddleware
// blah blah bunch of middleware
app.server.use(app.router.routes()).use(app.router.allowedMethods())
This is just regular koa-router.
However, the issue arises in access_control.
I have one file (access_control_definitions.js) where I specify named routes, their respective sequelize model name, and what rules exists for the route. (e.g. what role, if the owner is able to access their own resource...) I calculate whether the requester owns a resource by a route param (e.g. resource ID is ctx.params.id). However, in this implementation, params don't seem to be parsed. I don't think it's right that I have to manually parse the params before koa-router does it. Is anyone able to identify a better way based on this that would solve ctx.params not being filled with the actual named parameter?
edit: I also created a GitHub issue for this, considering it seems to me like there's some funny business going on.
So if you look at router.js
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures);
ctx.params = layer.params(path, ctx.captures, ctx.params);
ctx.routerName = layer.name;
return next();
});
return memo.concat(layer.stack);
}, []);
return compose(layerChain)(ctx, next);
What it does is that for every route function that you have, it add its own capturing layer to generate the params
Now this actually does make sense because you can have two middleware for same url with different parameters
router.use('/abc/:did', (ctx, next) => {
// ctx.router available
console.log('my request came here too', ctx.params.did)
if (next)
next();
});
router.get('/abc/:id', (ctx, next) => {
console.log('my request came here', ctx.params.id)
});
Now for the first handler a parameter id makes no sense and for the second one parameter did doesn't make any sense. Which means these parameters are specific to a handler and only make sense inside the handler. That is why it makes sense to not have the params that you expect to be there. I don't think it is a bug
And since you already found the workaround
const fromRouteId = pathToRegexp(ctx._matchedRoute).exec(ctx.captures[0])
You should use the same. Or a better one might be
var lastMatch = ctx.matched[ctx.matched.length-1];
params = lastMatch.params(ctx.originalUrl, lastMatch.captures(ctx.originalUrl), {})
I've been evaluating Strongloop (& Loopback) over the past couple of days. I've written a couple of helper endpoints for me to get information about my models in order to generate a CMS frontend for the REST API.
I've create a boot script that returns the public models that are in use, and a bit about them, as well as a couple of custom fields to do with the display of models etc. Here it is:
module.exports = function mountModelDiscoveryService(server) {
server.get('/api/RemoteModules', function(req, res) {
var models = server.models();
var modelObject = [];
models.forEach(function(Model) {
if(Model.shared) {
modelObject.push({
name: Model.modelName,
plural: (Model.settings.plural || Model.modelName),
attributes: Model.definition.rawProperties,
uri: (Model.settings.plural || Model.modelName).toLowerCase(),
displaySettings: (Model.settings.display || {list: ["id", "title"]})
})
}
});
return res.send(modelObject);
})
}
This is working well and I've been pleased with the progress. However, I'd like to be able to get a list of models (+ endpoints) that I have access to as the currently logged in user.
I've implemented the ACL stuff as the tutorial describes, and this correctly allows me or denies me access based on my current permission level, but this is only at the point of making the call - I was essentially wondering if anyone had tried to use the permissions system on the frontend - I'd like to hide certain elements if a user doesn't have access to create new objects, for example.
Do you think I should add some sort of property to this object that just returns a cut-down version of the ACL object with a complete list of what everyone can do? Is there an internal Loopback method I can use to achieve this result?
Thanks a lot for your time.