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
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.
Related
I wanted to implement a feature in my app. Where an Admin can delete the user. So basically the delete is working fine but somehow i cannot logout the logged in user. Let me explain it more briefly, Suppose there is a User A which is currently using my app and the admin decided to remove that user from the app so they can't no longer access the features of the app. To remove the user i can call an API and delete that user but if i completely delete the user it loses all the access to the API's call coz user with the certain ID isn't available anymore and the app breaks coz the API call will fail for that deleted User. So I was wondering is there anyway to logout the user after admin deletes it.
The Frontend is on ReactJs and Backend is on NodeJs. And i am using JWT for authentication. Any help will be appreciated and if this question isn't clear enough please let me know so i can explain it more.
In backend in every protected route you should verify the token and token should contain user id or email using that you will verify the token. After deleting the user throw error with no user found and in frontend make sure if there are the error no user found then it will delete the JWT token.
What comes into my mind is to put a middleware between your requests and server. By doing so, instead of trying to log out from all devices, we will not allow any action if user does not exist; in this very example, we will prevent the user to delete a place and toast a message on the front end. I will share an example of that, but you need to tweak the code according to your needs.
Http Error Model
class HttpError extends Error {
constructor(message, errorCode) {
super(message);
this.code = errorCode;
}
}
module.exports = HttpError;
Middleware
const HttpError = require('../models/http-error');
module.exports = (req, res, next) => {
try {
// Check user if exists
User.findById(req.userData.userId).exec(function (error, user) {
if (error) {
throw new Error('Authentication failed!');
}
else {
return next();
}
});
}
catch (error) {
return next(new HttpError('Authentication failed!', 403));
}
};
Route
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
router.use(checkAuth);
// Put after any routes that you want the user to be logged in
router.delete('/:placeId', placesControllers.deletePlace); //e.x.
...
module.exports = router;
E.x. controller (with MongoDB)
const deletePlace = async (req, res, next) => {
const placeId = req.params.placeId;
let foundPlace;
try {
foundPlace = await Place.findById(placeId).populate('userId').exec();
}
catch (error) {
return next(new HttpError('Could not find the place, please try again', 500));
}
// Delete place
res.status(200).json({message: 'Deleted place'});
};
FRONT END PART
import toastr from 'toastr';
....
try {
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
}
catch(error) {
// handle the error, user not found
console.log(error.message);
toastr.error(error.message, 'Error', {
closeButton: true,
positionClass: 'toast-top-right',
timeOut: 2000,
extendedTimeOut: 1,
});
}
I am a beginner in Nodejs and Mongo dB. I am creating a signup/login system with authentication and also I have to make a profile page. I have built a login API here is the code
router.post('/login',async(req,res)=>{
try {
const email = req.body.email;
const password = req.body.password;
const usermail = await UserSignup.findOne({email:email});
const isMatch = bcrypt.compare(password,usermail.password);
if(isMatch){
const token = generatetoken(usermail._id)
res.setHeader('JWT_TOKEN',token);
// res.status(201).json({
// message:"User loged in",
// userdetail:{
// name:usermail.name,
// email:usermail.email,
// Phone:usermail.mobilenumber,
// Id:usermail._Id
// }
// })
res.redirect('/api/user/welcome',{useremail:email})
}
else{
res.status(400).send("Invalid Credentials")
}
} catch (error) {
console.log(error);
res.status(500).send("Internal server error")
}
})
I have redirected the User after logging in to the welcome page.
so In the welcome page I have to display user name email. how can I do that?
router.get('/welcome',auth,(req,res)=>{
})
what should I write in the welcome API to get information about the logged user?
I have passed information of the user in 2nd argument of redirect is it correct or not because I can not able to access it in the welcome route.
You can use query strings. Modify you login route to redirect to:
res.redirect('/api/user/welcome?email=' + email)
And that you can access the query in the welcome route like so:
let email = req.query.email;
I am new to authentication with node.js and am struggling to implement the following:
I currently have a middleware function that checks the access token sent with the request and pulls the user relating to that token, then appends that user onto the request so I can use their details. This works completely fine for my Users collection, however I am wanting to add a new collection for a completely different type of user, called Owners.
Based on the function I currently have, I cannot seem to find a way to have it check both collections - this is my current function that works with my one Users collection.
//
// Middleware that authenticates token and appends user to request
//
module.exports.required = function (req, res, next) {
const auth_header = req.header("authorization").split(" ");
const auth_type = auth_header[0];
const auth_token = auth_header[1] || null;
// Check token
if (auth_type !== "Bearer" || !auth_token) {
return next(HttpError(401, "Token is invalid."));
}
// Find user matching access token
return User.findOne({ access_token: auth_token })
.orFail(HttpError(401, "Token does not exist."))
.then((user) => {
try {
// Check if token has no expired
decodeToken(auth_token);
} catch (err) {
if (err.name !== "TokenExpiredError") return next(err);
// Refresh token
user.generateAccessToken();
// Save and return new user
return user.save();
}
return user;
})
.then((user) => {
// Append user object to the incoming request
req.user = user;
return next();
})
.catch(next);
};
Can anyone help me understand out how I would check both collections (Users & Owners)?
i'm trying to delete a user from firebase auth using a cloud function which is triggered when i delete the document of the user. this is a workaround to enable one client "admin" permissions to delete other users without admin SDK. in the document i store the users email. how do i delete the user from auth using the email?
it should be something like this:
exports.sendDelVolunteer = functions.firestore.document('Users/{messageId}').onDelete((snap, context) => {
const doc = snap.data();
const user = admin.auth().getUserByEmail(doc.email).then(function(userRecord) {
return admin.auth().deleteUser(userRecord.uid).then(function() {
console.log('Successfully deleted user');
})
.catch(function(error) {
console.log('Error deleting user:', error);
});
}).catch(function(error) {
console.log('Error fetching user data:', error);
});
});
currently i get the following error: "error Each then() should return a value or throw"
thanks!!!
enter image description here
You're missing a top-level return statement.
exports.sendDelVolunteer = functions.firestore.document('Users/{messageId}').onDelete((snap, context) => {
const doc = snap.data();
return admin.auth().getUserByEmail(doc.email).then(function(userRecord) {
return admin.auth().deleteUser(userRecord.uid)
});
});
Aside from that this looks like a good approach. Just make sure that only your administrator(s( can delete these documents, as otherwise you still have a security hole.
I am pretty new to loopback and here is what I am doing:
I am using standard login route provided by the loopback to log in the users - extended base Users to my own model say orgadmin.
With prebuilt route /api/orgadmin/login, I can easily login.
Now, I have a flag in orgadmins say 'status' which can be either 'active' or 'inactive' based on which I have to defer user login.
I was thinking something with remote hooks like beforeRemote as below but it doesn't work:
//this file is in the boot directory
module.exports = function(orgadmin) {
orgadmin.beforeRemote('login', function(context, user, next) {
console.log(user)
// context.args.data.date = Date.now();
// context.args.data.publisherId = context.req.accessToken.userId;
next();
});
};
So what is the best way to accomplish this?
The user attribute will only be available if the request is coming with a valid access token. The attribute is unused for unauthenticated requests, which login is.
Here's a possible alternative:
module.exports = (OrgAdmin) => {
OrgAdmin.on('dataSourceAttached', () => {
const { login } = OrgAdmin;
OrgAdmin.login = async (credentials, include) => {
const accessToken = await login.call(OrgAdmin, credentials, include);
const orgAdmin = await OrgAdmin.findById(accessToken.userId);
if (orgAdmin.status !== 'active') {
OrgAdmin.logout(accessToken);
const err = new Error('Your account has not been activated');
err.code = 'NOT_ACTIVE_USER';
err.statusCode = 403;
throw err
}
return accessToken;
};
});
};
The above code overrides the login method and does the following:
Login the user, using loopback's built-in login
Take the response of login, which is an access token, and use it to get the user.
If the user is active, return the access token, satisfying the expected successful response of login.
If the user is not active, remove the access token that was created (which is what logout does), and throw an error.