User Roles and rules(permission) access in node.js
Asked Answered
L

2

7

I created a node.js application (Bus-ticket-booking app). MongoDB is the database system I'm using. I haven't yet finished the front end. I'm doing API queries with Postman.

For authentication, I'm using JWT. Now I want to add roles and rules for users such as the app's administrator, supervisor, and normal user.

1 -> A user can have many roles assigned to them (admin, supervisor).

2 -> Permissions can be assigned to a role ( Create, Update, delete etc...).

As a result, a user can have one or more roles, and each role can have one or more permissions. A user can use APIs for which he has rights, such as creating data, deleting data, updating data, and so on.

Here is the user schema:

const userSchema = new mongoose.Schema({
  firstname: {
    type: String,
    required: true,
  },
  lastname: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    unique: true,
    required: true,
    validate(value) {
      if (!validator.isEmail(value)) {
        throw new Error("Please provide the valid email address");
      }
    },
  },
  password: {
    type: String,
    required: true,
    trim: true,
    minLength: 8,
  },
  phone: {
    type: Number,
    required: true,
    unique: true
  },
  tokens:[{
    token: {
      type: String,
      required:true
    }
  }]
},{
  timestamps:true
});

I'm new to it and have very little knowledge about it.

Is there anyone who can assist me?

Lytton answered 6/6, 2022 at 13:49 Comment(1)
This gist Express.js role-based permissions middleware contains a sample approach: app.use("/api/private", permit("admin")); or app.use(["/api/foo", "/api/bar"], permit("manager", "employee"));Bosom
K
10

If you just need a couple different roles, I suggest you go with Sajawal Hassan's concept of simply adding a boolean field to determine user's access level.

However, if you are planning to create where there are multitude of roles to be added, and do not want field to be added for each role:

  1. Add permissions array field to data model and create a permission list (to better organize) to the user data model
  2. set up a middleware to add to your routers
  3. set up groups to allow the user of your routers, and pass them through routers

1a. I suggest you create a list of roles within the user model file. Possibly a dictionary.

.../models/user.js

const ROLES = {
    ADMIN: "ADMIN",
    SUPERVISOR: "SUPERVISOR"
}
...
module.exports = mongoose.model('User', UserSchema);
module.exports.ROLES = ROLES;

1b. add a array field to Users models which will have the roles as permissions

.../user.js

const userSchema = new mongoose.Schema({
    ...,
    permissions: [String],
    ...
});
  1. Set up a middleware or in this case you already have auth; add functional capabilities to the function in which it will check its parameter or attach it to options (if you are checking for token, or other auth params)

.../auth.js

module.exports = function (options) {
    ...
    // get user, validate token b4 here
    user.permissions.forEach(permission => {
        if (options.allowedGroup.indexOf(permission)){
            // if authenticated
            return next();
        }
    }
    // could not authenticate at this point
    return next(errorHandler) // throw a error or handle it way you want
}

3a. set up groups to determine which roles will have access to each router or a set of routers

.../routes/api_123.js

const mongoose = require('mongoose');
const User = mongoose.model('User');

const readGroup = [User.ROLES.SUPERVISOR, User.ROLES.ADMIN];
const writeGroup = [User.ROLES.ADMIN];

3b. pass the group you made as allowedGroup in param of middleware and set it up with a asyncHandler

...
const asyncHandler = require('express-async-handler');
...

router.get('/user/:id', auth({allowedGroup: readGroup}), asyncHandler(async(req, res, next) => {
    ... // your stuff here
    res.status(200).json(data);
})) 

router.post('/user/:id', auth({allowedGroup: writeGroup}), asyncHandler(async(req, res, next) => {
    ... // your stuff here
    res.status(200).json(data);
})) 
Kitchenware answered 15/6, 2022 at 15:23 Comment(0)
M
2

You should try to watch a full course on express and mongodb but you would have to add fields in the user schema that specifies if the user has permissions i.e admin: { type: booleen, default: false } then set the booleen to true if you want the user to be admin then create a route for something only admin sould be able to do lets say to delete a user so then in there check if the admin field in user schema is true. If so then user can delete otherwise throw err.

edit:

Do keep in mind im using mongodb atlas for the code

Add an admin field (or any role that you want im gonna go with admin here) so change

const userSchema = new mongoose.Schema({
  firstname: {
    type: String,
    required: true,
  },
  lastname: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    unique: true,
    required: true,
    validate(value) {
      if (!validator.isEmail(value)) {
        throw new Error("Please provide the valid email address");
      }
    },
  },
  password: {
    type: String,
    required: true,
    trim: true,
    minLength: 8,
  },
  phone: {
    type: Number,
    required: true,
    unique: true
  },
  tokens:[{
    token: {
      type: String,
      required:true
    }
  }]
},{
  timestamps:true
});

to this

const userSchema = new mongoose.Schema({
  admin: {
    type: Booleen,
    default: false,
  },
  firstname: {
    type: String,
    required: true,
  },
  lastname: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    unique: true,
    required: true,
    validate(value) {
      if (!validator.isEmail(value)) {
        throw new Error("Please provide the valid email address");
      }
    },
  },
  password: {
    type: String,
    required: true,
    trim: true,
    minLength: 8,
  },
  phone: {
    type: Number,
    required: true,
    unique: true
  },
  tokens:[{
    token: {
      type: String,
      required:true
    }
  }]
},{
  timestamps:true
});

I just added the admin field in the user schema

Then lets say you only want the admin to be able to delete users for that you would have to create a route like this

router.delete("/delete/:id", async (req, res) => {
  try {
    // First find the user admin wants to delete
    const user = await User.findById(req.params.id) // getting id from the id you put in url

    // Make sure the user who wants to delete another user is an admin
    if (user.admin) {
       await user.deleteOne() // This deletes the user
    } else {
       res.status(403).json("You are not allowed to do this action!")
    }
  } catch (error) {
    res.sendStatus(500);
  }
});
Mecham answered 6/6, 2022 at 14:9 Comment(7)
I have added the user model. Please go through this.Lytton
I have updated the original answer and again I would highly recommend watching a full course on express and mongodb atlasMecham
Is there a course on this topic that you know about? I looked for it but was unable to locate any.Lytton
To start out i would recommend you watch dev ed's rest api tutorial followed by understading jwt then jwt authentication tutorial: 1) rest api tutorial: youtube.com/watch?v=vjf774RKrLc&ab_channel=DevEd 2) jwt authentication understanding: youtube.com/… 3) authentication practical tutorial: youtube.com/…Mecham
I have tried it in the GET request but somehow it is not working. router.get("/bus", auth, async (req, res, next) => { try { const bus = await Bus.find(); if (User.admin) { res.send(bus) } next() } catch (e) { res.status(500).send(); } });. I have set one of the users admin role to true. What am I doing wrong?. Here is the GET request from postman 127.0.0.1:3000/admin/bus. admin is in the index file router. app.use("/admin",busRouter)Lytton
There are alot of things wrong with this. First off where your finding the bus you're not getting one bus but instead all the indexes or busses if you want to find a specific bus then use ``` const bus = await Bus.findById(req.params.id)``` where id is provided in url. Second off where you're checking is the user is admin you first need to find the user like this ``` const user = await User.findById(req.user._id)``` you need to get user._id from the middleware where your authenticating tokens and lastly why is the route to get bus a middleware? 1/2Mecham
It should be a route and then on the frontend you grab the bus before going to a protected route 2/2Mecham

© 2022 - 2024 — McMap. All rights reserved.