0

I am learning by building. I am building a blog CMS with Nodejs, reactjs, and mongodb. I have two roles: users and admin. I would like admin to be able to delete any user. I wrote codes that enabled a user to delete his/her own account. How do I go about making the admin to be able to delete any user by clicking a button next to that user?

Here are my codes so far: code for a user to delete him/her self. Once a user deletes him/her self, everything associated with the user also gets deleted. This is working fine.

//delete logic

 router.delete("/:id", async (req, res) =>{
 if(req.body.userId === req.params.id){//we checked if the user id matched

    try{
        const user = await User.findById(req.params.id)//get the user and assign it to user variable
        try{
            await Post.deleteMany({username: user._id})//deleting user posts once the username matches with the variable user object .username
            await Comment.deleteMany({author: user._id})//delete user's comment by checking the comment author's id.
            await Reply.deleteMany({author: user._id})//deletes user's replies
            await User.findByIdAndDelete(req.params.id)//delete the user
            res.status(200).json("User has been deleted")
        } catch(err){
            res.status(500).json(err) //this handles the error if there is one from the server
        }
    }catch(err){
        res.status(404).json("User not found")
    }

       
  } else{
   res.status(401).json("You can only update your account!")
  }

 });

How I tried to write the code for admin to be able to delete a user:

/delete a user by an admin

 router.delete("/:id", async (req, res) =>{
  if(req.body.userId === req.params.id){
     const user = await User.findOne({username: req.body.username})
   if(user && user.role === "admin"){
    try{
        const regUser = await User.findById(req.params.id)//get the user and assign it to  user variable
        try{
            await Post.deleteMany({username: regUser._id})//deleting user posts once the username matches with the variable user object .username
            await Comment.deleteMany({author: regUser._id})//delete user's comment by checking the comment author's id.
            await Reply.deleteMany({author: regUser._id})//deletes user's replies
            await User.findByIdAndDelete(req.params.id)//delete the user
            res.status(200).json("User has been deleted")
        } catch(err){
            res.status(500).json(err) //this handles the error if there is one from the server
        }
    }catch(err){
        res.status(404).json("User not found")
    }


  }else{
     res.status(401).json("You do not have the permission")
  }

   }

   
       


 });

When I tried this code on postman, it kept on loading and didn't deliver anything.

I know that I am not writing the function properly. Kindly provide me with any help to enable me achieve this. Thank you

kinhs
  • 175
  • 13

1 Answers1

1

I tried to reverse-engineer your request body of your API. And I think it's the following:

{
    body: {
      userId: string
      userName: string
    }
    params: {
      id: string
    }
}

So, trying to reverse engineer what each value is intended for:

  • the params-id obviously is just the parameter which is contained in the URL. So, that's the id of the user which you're trying to delete.

So, what are the userId and userName in your body ?

The issue of security

Judging from your code, the userName and/or userId refer to the user who's logged in and who's performing the operation. Surely, that can't be secure.

You are aware that every user can hit F12 in his webbrowser and see all in/out going requests. It's really easy to modify them and put in the ID of a different user. So, surely, you need more security than just that.

What you need is a "context" which keeps track of the logged in user. e.g. sometimes the entire user object of the logged in user is added on req.context.me.

I've searched for a tutorial that illustrates this, and found this one. It's not entirely what I meant, but it's similar. They store the userId on the req object. Making it available as req.userId.

Aside from security

Having written all that, what you were trying to do is probably the following.

router.delete("/:id", async(req, res) => {

  const loggedInUser = await User.findById(req.body.userId);
  if (loggedInUser && loggedInUser.role === "admin") {
    try {
      const regUser = await User.findById(req.params.id);
      if (regUser == null) {
        throw new Error("user not found");
      }
      
      await Post.deleteMany({
        username: regUser._id
      }) //deleting user posts once the username matches with the variable user object .username
      await Comment.deleteMany({
        author: regUser._id
      }) //delete user's comment by checking the comment author's id.
      await Reply.deleteMany({
        author: regUser._id
      }) //deletes user's replies
      await User.findByIdAndDelete(req.params.id) //delete the user
      res.status(200).json("User has been deleted")
    } catch (err) {
      res.status(500).json(err) //this handles the error if there is one from the server
    }
  } else {
    res.status(401).json("You do not have the permission")
  }
}

As you can see, you don't need the username.

DELETE does not support a body

Whether a DELETE can have a body or not is actually a point of discussion. Some clients/servers support it and some don't. You can find more about this here:

body is empty when parsing DELETE request with express and body-parser

Again, this means that you really shouldn't pass the logged in user through the body.

bvdb
  • 22,839
  • 10
  • 110
  • 123
  • Thank you very much. I sincerely and deeply appreciate the time and effort you invested, educating me on vital issue such as the security. I tried this code you wrote on postman but I keep getting "you do not have the permission". The url id is the id of the user I would like to delete. Then inside the body, I provided the id of the admin that want to delete. Did I do it rightly? – kinhs Oct 31 '21 at 22:55
  • @kinhs assuming that you used the modified code, which I posted above, that means that the `userId` is not properly sent in the body, or that it does not exist in the database, or that it has a different role. The easiest way to find out what is going on, is to add a line `console.log(req.body.userId)`. And just before the `if (loggedInUser &&` line, you should add a `console.log(loggedInUser ? 'role:' + loggedInUser.role ': 'user not found' )`. – bvdb Oct 31 '21 at 23:07
  • PS: added a link to a tutorial that shows how to have a `req.userId` at your disposal, deducted from a JWT token. – bvdb Oct 31 '21 at 23:18
  • Yes, I am using the code that you modified.. console.log(req.body.userId) is returning undefined. And from what you spoke about security, you mean I should avoid using using req.body.username and use req.body.userId instead, right? Because I have one other place where I used req.body.userId. I want to understand so that i can go back to the code to modify it. Thanks – kinhs Oct 31 '21 at 23:20
  • @kinhs ... in that case. You're probably running against the "DELETE does not support body" issue. (I've added a link). – bvdb Oct 31 '21 at 23:29
  • @kinhs about the security: sending the userId in the body is actually just as insecure. The point is that it's not difficult to pretend that you're somebody else. If you just modify the message and put in somebody else's ID, you could pretend to be an admin, and then remove other users from the system. - So, what you need is a login-flow, that creates a JWT token. And then for each request the client should send a token along with the request (in the header). Then express should validate that token, extract the userid from it, and should add it on the `req` object. (see linked tutorial above) – bvdb Oct 31 '21 at 23:32