how to protect a route from being used until after they verified? - node.js

Assume the user wants to go to their settings page where they could change their personal info (e.g. first name). I want to trigger an OTP sent to their phone to verify it is actually the user going to this page before they could actually update their info. We use a third party service that handles the sms part (expiration, error message etc). I have the whole sending + updating part figured out, I'm just unsure on how to protect the update route only after they're verified.
Does anyone know what the best practice with this use case? i would be grateful for any help.
/* verifies if the access token (which user gets upon login) is valid */
const auth = (req, res, next) => {
const token = req.header('access-token');
if (!token) return res.status(401).json('Not Authorized');
try {
const payload = jwt.verify(token, 'secret');
req.userId = payload.userId
next();
} catch (ex) {
res.status(400).json('Invalid.');
}
};
/* sends one time password to user */
router.post(
'/update/user-check/send',
auth,
async (req, res) => {
try {
// gets user from db
const curUser = await getUser({
userId: req.userId,
});
// sends otp
await SMSService.sendOneTimePasswordThroughPhone(curUser.phoneNumber)
res.status(200).json("sent one time password")
} catch (e) {
console.log(e);
return res.status(401).json(e);
}
},
);
/* verifies one time password to user */
router.post(
'/update/user-check/verify',
auth,
async (req, res) => {
try {
const oneTimePassword = req.body.oneTimePassword
// gets user from db
const curUser = await getUser({
userId: req.userId,
});
// checks otp
const result = await SMSService.checkOTP(oneTimePassword, curUser.phoneNumber);
res.status(200).json("verified")
} catch (e) {
return res.status(401).json(e);
}
},
);
/* updates user's first name */
router.put(
'/update/first-name',
auth,
async (req, res) => {
try {
// updates first name
const curUser = await updateFirstName({
userId: req.userId,
});
res.status(200).json("updated first name")
} catch (e) {
return res.status(401).json(e);
}
},
);
There is one big flaw with my implementation. I have no way to ensure '/update/first-name' route is protected only after they are validated. How do I go about this ? For example, currently if they use the put route and they haven't verified otp yet, then it would still allow them to use that route

Related

Why does req.params.id return "undefined"?

Learning about the concept of microservices in Nodejs, I have set up two microservices auth and users, both standalone and running on different ports.
The auth service handles user creation and log-in users using a username and password. This works as expected. I've used jwt to generate the tokens. I can easily create a user, create a session token and verify its validity.
My second service, users, I intend to use to show greetings to users and fetch a user's detail. I need to use the auth service to know when a user is logged in in this setting.
However, with my current workings, when I try to go to an endpoint, /users/:id/sayhello with a valid user id and a valid token passed in the headers, I get the following errors:
TypeError: Cannot read properties of undefined (reading 'id') at /path/auth/verifyToken.js:23:21 .
And then this; from jwt:
{
"name": "JsonWebTokenError",
"message": "secret or public key must be provided"
}
Let's look at my setup now.
Here's my verifyToken.js file from the auth service:
const verifyToken = (req, res, next)=>{
const authHeader = req.headers.token
// split the header, and get the token "Bearer token"
const token = authHeader.split(" ")[1];
if (authHeader) {
jwt.verify(token, process.env.JWT_SEC, (err, user)=>{
if (err) res.status(403).json(err);
req.user = user
next();
})
} else {
return res.status(401).json("You are not authenticated")
}
}
const verifyTokenAndAuthorization = (req, res, next) =>{
verifyToken(req, res, ()=>{
if(req.user.id === req.params.id){ // error traced back to this line
next();
}else{
res.status(403).json("Permission denied!");
}
})
}
From my users service, here's the code that uses the auth service to know when the user is logged in then say hello.
app.get("/users/:id/sayhello", verifyTokenAndAuthorization, async (req, res) => {
try {
const user = await User.findById(req.params.id);
console.log(req.params.id) // undefined
res.status(200).json(`Hello ${user.username}`);
} catch (error) {
res.status(500).json(error);
}
});
I've with no success sought any leads from similar posts like A,B and C
I'm not sure of what's not right. I'll appreciate possible suggestions and leads towards a fix.
console.log(process.env.JWT_SEC)
The authentication process got failed, so the user property was unset on the req object, so req.user is null.
Ensure the integrity of your inputs.
I think in the Headers convention of using Authorization or authorization key will not dissappoint as its the most preferred way of doing this have something like
I have done a rolebased approach in tackling this so check the implementation as of the question rolebased structure.
What to check for
Check if the Authorization header is available
If it does not exist just throw an exception or a response with some error
Check if the token is present in the Bearer token
If the token is not there just throw an exception to temnate the excecution
If the AuthHeader and token are present then now you can be certain that you have the token, thus you can just return the jwt.verify(...args:[])
Depending on validity everything here is on check
If the jwt is valid then the JWT payload is there thn we can pass it to the request object to carry it through to the other middlewares
If we want to now have Athorization then we override the next parameter with a function to execute on it`s behalf
From here now you can check on the user Roles and return next based on what permissions they have.
import RoleModel from "../models/RoleModel"
import UserModel from "../models/UserModel"
class AuthMiddleware {
constructor(role:typeof Model, user:typeof Model) {
this.role=role
this.user=user
}
verifyJwt = async (req, res, next) => {
try {
const AuthHeader = req.headers["authorization"]
if (!AuthHeader) {
return res.status(401).json("Please provide an auth token")
}
const token = AuthHeader.split(" ")[1]
if (!token) {
return res.status(401).json("Please provide an auth token")
}
return jwt.verify(token, SECRET_KEY, async (error, payload) => {
if (error) {
return res.status(401).redirect("/auth/login")
}
const decodedPayload = payload as JWTPayloadType
req.user = decodedPayload
return next()
})
} catch (error) {
return next(error)
}
}
loginRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.USER)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
adminRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.ADMIN)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
}
export default new AuthMiddleware(RoleModel, UserModel)
Applying this to a middleware
import auth from "../middlewares/AuthMiddleware"
/**
* ************* UPDATE USER PROFILE ********
*/
router
.route("/update/profile/:id")
.put(
auth.loginRequired,
imageUpload.single("profile"),
uController.updateUserDetails,
uMiddleware.uploadProfilePic,
)
Assuming you supply the middlewares to the given route its easy to abstract away the verify jwt and have a login_required based on the roles you want achieved.
Full implementation of this I have on this Github repo Github link

api call getting affected by another api call's validation

not really sure if my title is correct but my problem is that I have this reset password token checker in my api that seems to get affected by another api that finds a specific user, this api has user validation.
Here is what they look like:
//get specific user
router.get('/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});
//reset-password token check
router.get('/reset-pass', async (req, res) => {
await User.findOne({
where: {
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: {
[Op.gt]: Date.now()
}
}
}).then(user => {
if(!user) {
res.status(401).json({ error: 'Password reset link is invalid or has expired.'})
} else {
res.status(200).send({
username: user.username,
message: 'Password reset link Ok!'
});
}
});
});
then here is the validateToken
const validateToken = (req, res, next) => {
const accessToken = req.cookies['access-token'];
if (!accessToken)
return res.status(401).json({error: 'User not authenticated!'});
try {
const validToken = verify(accessToken, JWT_SECRET)
req.user = validToken;
if(validToken) {
req.authenticated = true;
return next();
}
} catch(err) {
res.clearCookie('access-token')
return res.status(400).json({error: err}).redirect('/');
}
};
when I comment out the get specific user api the reset password token check works. If I remove validateToken it returns null instead of giving me the username and message.
One of the things I notice is the route param "/:id", that means that literally everything would be processed by get specific user because all routes start with "/", only use params in routes with a prefix like "/user/:id" that way only the routes that starts with "/user" will execute that code.
Change your code to:
//get specific user
router.get('/user/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});

Switching Profile routes based on user role in registration (Express.js)

A bit of a long read
I am currently building the backend for a MERN project, with quite an interesting structure (I would be changing the specifics because it is a private project).
Database: There are 4 database schemas at the moment, 1 user schema with 3 different roles: student, teacher, and sponsor.
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema({
username : {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
enum: ['student', 'teacher', 'sponsor'],
required: true,
},
dateCreated: {
type: Date,
default: Date.now
}
})
module.exports = User = mongoose.model('user', UserSchema)
**The 3 types of user roles have their own unique but quite similar profile schema (TeacherProfile, StudentProfile e.t.c) which all reference the user shcema by ID **.
const mongoose = require('mongoose');
const studentProfileSchema = new mongoose.Schema({
user: {
// create a reference to the user schema
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},.........
I have an authentication middleware that takes care of the jwt logic.
Now things get interesting at the routes
I have a user route that takes care of user registration
An auth route for login and authentication,
And 3 routes for the profiles
What I desire to build is a middleware logic that would switch between the 3 project routes once a user registers, so he/she would be returned the profile that desribes choosen role during registration.
keep in mind that there are calls to the database, which i have to wrap inside of an async block
This is an example of one of such routes:
const express = require('express');
const router = express.Router();
const config = require('config');
// validator
const {check, validationResult} = require('express-validator');
// auth middleware
const auth = require('../../middleware/authMiddleware');
// db collections
const SponsorProfile = require('../../models/SponsorProfile');
const User = require('../../models/User');
// #route GET api/SponsorProfile/me
// #desc GET current user profile
// #access Private
router.get('/me', auth, async (req, res) => {
try {
// fetch profile object
const sponsorProfile = await SponsorProfile.findOne({user:req.user.id});
// check if profile exists
if(!sponsorProfile) {
return res.status(400).json({msg: 'Hello Sponsor, You have not created a profile'})
}
res.json(sponsorProfile)
} catch (error) {
console.error(error.message);
res.status(500).json({msg:'This is our fault not yours'})
}
})
module.exports= router;
Now this is what I tried:
I built a master router that uses all the profile routers as sub-routers starting from the student to the sponsor.
const express = require('express');
const profilesRouter = express.Router();
profilesRouter.use('/', require('./studentProfile'));
profilesRouter.use('/', require('./teacherProfile'));
profilesRouter.use('/', require('./sponsorProfile'));
module.exports = profilesRouter;
It is then called in server.js like this:
app.use('/api/profilesRouter', require('./routes/api/profilesRouter'));
The appraoch was to place a middleware function in the first 2 routers and leave the third one empty so there will be a switch, if the criterias in the first two passes.
async function shouldRouterChange(req, res, next) {
let userRole = await User.findOne({user:req.role}).select('-password');
console.log(userRole)
if ( userRole === 'mentor') {
return next('router');
}
return next();
}
function shouldRouterChange(req, res, next) {
if (req.user.role === 'teacher') {
return next('router');
}
return next();
}
// #route GET api/studentProfile/me
// #desc GET current user profile
// #access Private
router.get('/me', [auth, shouldRouterChange], async (req, res) => {
try {
// check if profile exists
const studentProfile = await StudentProfile.findOne({user:req.user.id}).populate('user', ['role']);
if(!studentProfile) {
return res.status(400).json({msg: 'Hello student, You have not created a profile'})
}
res.json(studentProfile)
} catch (error) {
console.error(error.message);
res.status(500).json({msg:'This is our fault not yours'})
}
})
module.exports= router;
Obviously that did not work
Then I tried the middleware function like this
async function shouldRouterChange(req, res, next) {
let userRole = await User.findOne({user:req.role}).select('-password');
console.log(userRole)
if ( userRole === 'mentor') {
return next('router');
}
return next();
}
No Way
Then this:
async function shouldRouterChange(req, res, next) {
try {
let userRole = await studentProfile.findOne({user:req.user.id}).populate('user', ['role']);
// conditional
console.log(userRole)
if (userRole.role==='mentor') {
return next('router')
}
return next()
} catch (error) {
console.error(error.message)
res.status(500).json({msg: 'Server Error'})
}
}
I debugged as best as I could and realized that:
The whole switching structure actually works nicely
The problem lies in the middleware structure
The first middleware structure might be corerct, apart from the conditional.
The conditional equates to null, or undefined (it does not get the user role properly).
The whole middleware logic might have to be called inside the router.get() logic that returns the profile.
My question is how can i make the conditional correct, and consequently structure the middleware to work properly (maybe without doing too much change on my app structure, won't mind anyways)
I have solved the error, and it works like a charm
To use router level middleware for switching routers (can also switch single routes)
Build a master router for all those routers (now sub-routers):
const express = require('express');
const profilesRouter = express.Router();
profilesRouter.use('/', require('./studentProfile'));
profilesRouter.use('/', require('./teacherProfile'));
profilesRouter.use('/', require('./sponsorProfile'));
module.exports = profilesRouter;
Add the master router path to server.js:
app.use('/api/profileRouter', require('./routes/api/profileRouter'));
Build the middleware logic, to switch the routers conditionally:
Middleware to switch from student to teacher router file:
const jwt = require('jsonwebtoken');
const config = require('config');
// Check user profile
function teacherSwitch(req, res, next) {
const token = req.header('x-auth-token');
// if no token is returned
if (!token) {
return res.status(401).json({ msg: 'No token, permission denied' });
}
try {
// decode token
const decoded = jwt.verify(token, config.get('jwtSecret'));
// set role to the same value as in the request
req.user.role = decoded.user.role;
// check if role is teacher
if (decoded.user.role !== 'student') {
return next('router');
}
return next();
} catch (error) {
res.status(401).json({ msg: 'Wrong token, authentication failed' });
}
}
module.exports = teacherSwitch;
Our logic here is to switch based on the role chosen by the user when registering. This means that role is a DB field in the user schema.
All we have to do is to add the role in our authentication payload.
A token is created on user registration, and that token uses the user id as payload, so if we add the role to that payload, we can now access it in the "req" object and build our conditional around it.
This payload is located in my auth route using the auth middleware:
// create jwt payload
const payload = await {
user: {
id: user.id,
role: user.role,
},
};
// if user login is successful, return token
jwt.sign(payload, config.get('jwtSecret'),
{ expiresIn: 36000 }, (error, token) => {
if (error) throw error;
return res.json({ token });
});
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
Also, the "next(router)" in the middleware function is the magic that switches to the next router in the router stack found in profilesRouter.
It initiates a switch which is then called when we finally return next.
return next().
Call the teacherSwitch middleware in the first sub-router (student router)
router.get('/me', [auth, teacherSwitch], async (req, res) => {
try {
// check if profile exists
const studentProfile = await StudentProfile.findOne({ user: req.user.id });
if (!teacherProfile) {
return res.status(400).json({ msg: 'Hello student, You have not created a profile' });
}
return res.json(studentProfile);
} catch (error) {
console.error(error.message);
return res.status(500).json({ msg: 'This is our fault not yours' });
}
});
Create another middleware to switch to the 3rd sub-router conditionally
function sponsorSwitch(req, res, next) {
const token = req.header('x-auth-token');
// if no token is returned
if (!token) {
return res.status(401).json({ msg: 'No token, permission denied' });
}
try {
// decode token
const decoded = jwt.verify(token, config.get('jwtSecret'));
// set role to the same value as in the request
req.user.role = decoded.user.role;
// check if role is partner
if (decoded.user.role !== 'teacher') {
return next('router');
}
return next();
} catch (error) {
res.status(401).json({ msg: 'Wrong token, authentication failed' });
}
}
6 Call it in the second sub-router.
router.get('/me', [auth, partnerSwitch], async (req, res) => ...
And that is it, as long as the condition is not met, it will switch and switch until the condition is passed.
Works just like a regular switch statement.
What I was missing here was that to be able to use the role to switch, I had to call it in as part of the auth payload, this way it becomes part of the request object in the req, res cycle, and hence can be manipulated as you like.
We never need to call another middleware in the last router, because logically the switching only gets there when everything fails, just like the default in a regular switch statement.
N.B: It is actually important to call the middleware function in the last router, as this would make this work perfectly in the frontend (I used react), not doing this caused bug where react could not return the last route if it had to, which is not what we want.
Got some very valuable help from John
An used this site to manually decode my JWT tokens, to know exactly what was going on.

Express routing and jsonwebtoken, staying logged in after token creation

I'm having a hard time connecting the last dots building a role based access control api in Express.
Following this tutorial and implementing onto my existing program, but I think I am missing the last step and after countless tutorials analysis paralysis has set in. I have since scaled back all my necessary code to what I think is the bare minimum.
Currently I am able to create a new user and save them to the mongoose database. I can see the hash by bcrypt is doing its thing and I can see the token being generated in the response after signing up. However as soon as I navigate to a new page after signup or login, for eg the users own id page/user/:userId as per tutorial, I keep getting You need to be logged in. I know I need to check for a token on every request but my question is, why doesn't it seem like the middleware is checking for the token or something is holding it back?
Since the token is shown in the json reponse surely I should be able to check for the tokens existence with the next get request at for eg the /user/:userId page? Isn't that the idea? Or is the browser just showing the response but I still need to actually store it? I don't understand where it goes to so to speak..
Any advice? Or is this a session thing? I know its a bit hard without all the code but if anyone could spot anything relevant so that I could research my next steps I would much appreciate it!
First this middleware in app.js
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use('/', async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
console.log('token verified'); // not printing to console
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use('/', userRoutes);
I have built the roles using the module access-control which is required
const AccessControl = require('accesscontrol');
const ac = new AccessControl();
exports.roles = (function() {
ac.grant('basic')
.readOwn('profile')
.updateOwn('profile');
ac.grant('supervisor')
.extend('basic')
.readAny('profile');
ac.grant('admin')
.extend('basic')
.extend('supervisor')
.updateAny('profile')
.deleteAny('profile');
return ac;
})();
routes examples as per tutorial.
router.get('/signup', (req, res, next) => {
res.render('signup', {
viewTitle: 'User SignUp',
});
});
router.post('/signup', userController.signup);
router.get('/login', (req, res, next) => {
res.render('login', {
viewTitle: 'User Login - WTCT OPS',
});
});
router.post('/login', userController.login );
router.get('/add', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.add);
router.get('/users', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.getUsers);
router.get('/user/:userId', userController.allowIfLoggedin, userController.getUser);
router.put('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('updateAny', 'profile'), userController.updateUser);
router.delete('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('deleteAny', 'profile'), userController.deleteUser);
relevant part of controller
async function hashPassword(password) {
return await bcrypt.hash(password, 10);
}
async function validatePassword(plainPassword, hashedPassword) {
return await bcrypt.compare(plainPassword, hashedPassword);
}
// grant access depending on useraccess role
exports.grantAccess = function(action, resource) {
return async (req, res, next) => {
try {
const permission = roles.can(req.user.role)[action](resource);
if (!permission.granted) {
return res.status(401).json({
error: 'You don\'t have enough permission to perform this action',
});
}
next();
} catch (error) {
next(error);
}
};
};
// allow actions if logged in
exports.allowIfLoggedin = async (req, res, next) => {
try {
const user = res.locals.loggedInUser;
if (!user) {
return res.status(401).json({
error: 'You need to be logged in to access this route',
});
}
req.user = user;
next();
} catch (error) {
next(error);
}
};
// sign up
exports.signup = async (req, res, next) => {
try {
const {role, email, password} = req.body;
const hashedPassword = await hashPassword(password);
const newUser = new User({email, password: hashedPassword, role: role || 'basic'});
const accessToken = jwt.sign({userId: newUser._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
newUser.accessToken = accessToken;
await newUser.save();
res.send({
data: newUser,
message: 'You have signed up successfully',
});
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
try {
const {email, password} = req.body;
const user = await User.findOne({email});
if (!user) return next(new Error('Email does not exist'));
const validPassword = await validatePassword(password, user.password);
if (!validPassword) return next(new Error('Password is not correct'));
const accessToken = jwt.sign({userId: user._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
await User.findByIdAndUpdate(user._id, {accessToken});
res.status(200).json({
data: {email: user.email, role: user.role},
accessToken,
});
} catch (error) {
next(error);
}
};
// get one user
exports.getUser = async (req, res, next) => {
try {
const userId = req.params.userId;
const user = await User.findById(userId);
if (!user) return next(new Error('User does not exist'));
// console.log(req.params);
res.send(200).json({
data: user,
});
} catch (error) {
next(error);
}
};
Why when trying to post to the endpoint /user/:userId is the middleware not checking for the token?
Thank you for any advice!
Update:
So far I have tried to removed the / from app.use. I saw I made that mistake now, but also tried removing it from the app.use(userRoutes); middleware to make it apply to all http requests but no luck.
app.use(async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
// console.log('Time:', Date.now());
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use(userRoutes);
I also thought that maybe because my server makes http requests in the backend maybe that was causing a problem in setting the x-access-token header? So I tried to change the x-access-token mw to use router.use on all routes but still nothing. I don't understand what I am missing. And just to be sure I'm not missing something fundamental, since I am using the JWT I do not need to use local storage or cookies to allow for browsing between pages while logged in since I can use the token set in the header, correct?
Thanks again for any advice!
That's because your middleware is only tied to the / route. Remove it if you want it to be used for every route. Take a look at the ExpressJS Docs regarding middleware.

Why does "res.send" giving me an older version of a Mongoose document?

I have an express API endpoint that deletes a Post. I have attached a mongoose pre-hook that, in turn, deletes also references to the Comment model and references from the User model:
PostSchema.pre("remove", async function() {
await Comment.remove({ _postId: this._id }).exec();
await User.update({ $pull: { _posts: this._id } }).exec();
});
This hook successfully removes all references to/from Comment and User models when the Post is removed. However, when I send the User model back to the user as a response (res.send(user)), I am still getting the reference to the Post. I know it's an older version that is being sent because when I manually query the database, I see that the Post reference was actually removed.
This is how the API looks like:
app.delete(
"/api/posts/:postId",
requireAuth,
async (req, res, next) => {
try {
const post = await Post.findById(req.params.postId);
if (post._userId.equals(req.user._id)) {
await post.remove();
const user = await req.user.save();
res.send(req.user);
} else {
res.send("Error:", "This post does not belong to you");
}
} catch (err) {
next(err);
}
}
);
(Note that requireAuth is a middleware that uses passportjs to get the jwt and deserialize the user)
In requireAuth you get User from database and store it in req.user
await post.remove(); you removed Post and Post reference from User, all ok
const user = await req.user.save(); - You are saving old User (grabbed from no1.) to database. Not good. You must update User object stored in req.user before any other action. req.user still store old version of User.
In my opinion instead of
const user = await req.user.save();
You should get fresh user from database (find), assign fresh user to req.user and finaly pass it to res.send(user)
app.delete(
"/api/posts/:postId",
requireAuth,
async (req, res, next) => {
try {
const post = await Post.findById(req.params.postId);
if (post._userId.equals(req.user._id)) {
await post.remove();
const user = await User.findById(req.user._id).exec();
req.user = user;
res.send(req.user);
} else {
res.send("Error:", "This post does not belong to you");
}
} catch (err) {
next(err);
}
}
);

Resources