MongoDB and Mongoose Abnormal behavior? - node.js

I apologize for the maybe trivial question. I am accessing mongo db to obtain information on a user, verify the password, and save some data in Redis if the validation was successful.
But I'm misbehaving (probably my mistake).
My simple backend is built in express 4.17.2, node 17.2.0, and redis4.0.1.
Server-side MongoDB on cloud MongoDB Atlas (Version 4.4.10) and RedisLab (Redis in cloud version 6.2.3).
Here are some code snippets:
app.post('/identity/login', (req, res) => {
let userHash = '';
let result = false;
const user = req.body;
userHash = crud.login(user.username);
userHash.then(h => {
result = bcrypt.compareSync(user.password, h.password);
console.log('All object ',h);
console.log('Access to roles array',h.roles);
let value = {name:h.name, surname:h.surname, roles:h.roles, username:h.username};
console.log('value', value);
client.set(h.password, JSON.stringify(value), { EX: 60, NX: true });
res.send(result);
});
});
Making the POST call to that endpoint, I get the following (unexpected) results, highlighted by the logs:
All object {
_id: new ObjectId("61d1d38d0f4bac12d8f504ee"),
username: 'jdoe',
password: '$2a$14$5GULZRmiN0DXQXMaLwQiEO9S/OlBll5U4ZwkBn2NYpju0VRDFUqVO',
name: 'John',
surname: 'Doe',
roles: [ 'Admin' ]
}
Access to roles array undefined
value { name: 'John', surname: 'Doe', roles: undefined, username: 'jdoe' }
As you can see, if I log h.roles I get that the array is not defined, while logging the whole object the array property 'roles' is correctly valued. So obviously, when I go to build the object to save in Redis (as h.roles), the array is not created.
Why? Could you please help me and say me what I am doing wrong? My mistake, a mongoose problem (see 6.4.1)?
Below, I show the code to access MongoDB:
const login = async (username) => {
connectDB();
let dbPwd;
const User = mongoose.model('Users', UserSchema);
return await User.findOne({ username: username }, 'password name surname roles username').exec();
}
Thanks so much!

Related

How to remove a certain position from an array of objects that matches a value in Mongoose?

Is the first time I am working with backend (Mongoose, express, nodeJs). I have a collection of users in MongoDB.
I am sending the favoriteId from frontend and getting it in the backend. I want to delete the position of the array that matches with the favorite Id I am passing to the function.
I have tried several ways to remove the favorite using the $pull operator and the favoriteId but none work. So I don't know how to do it.
I would appreciate if someone could help me.
This is structure in MongoDB:
_id: new ObjectId("63"),
username: 'test1',
email: 'test1#gmail.com',
favorites: [
{
id: '25',
version: 'version3',
team: 'team1',
},
{
id: '27',
version: 'version2',
team: 'team4',
}```
This is my controller function:
```export const removeFavorite = async (req, res) => {
//Here I have the user Id 63
const userId = req.params.userId;
//here I have the favorite Id I Want to remove : 25
const favoriteId = req.body.params.id;
// Here I select the user in the collection
const deleteFavorite = await User.findOne({ _id: userId });
// Here I want to remove the favorite that matches with favoriteId (25)
res.status(200).json('ok');
};```
Try this:
User.update({ _id: userId }, { $pull: { 'favorites': { 'id': favoriteId } } });

how to stop users from viewing and updating another user's data in node.js?

I am storing a parking detail with a merchant id in the mongoose schema since a parking belongs to a certain merchant user and it cannot be empty or null.
Here is the model:
const parkingSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Merchant",
},
//other details
})
merchant model is something like this:
const merchantSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Auth",
},
//other details
})
And finally the auth schema:
const authSchema = new mongoose.Schema({
accountType: {
type: String,
required: true,
trim: true,
default: "user",
enum: ["merchant", "user", "provider"],
},
//other details
})
If the original user wishes it, I simply want to update the parking data; otherwise, I want to throw an error.
I am using jsonwebtoken to authenticate users.
Here is the query to update the data:
exports.updateParking = async (req, res) => {
try {
const { parkingName, price, address, name, phoneNumber, about } = req.body;
const { parkingImage } = req.files;
const check_exist = await Auth.findById(req.data.id);
if (!check_exist) return res.status(404).json({ error: "User not found" });
console.log(req.data.id);
const updateData = await Parking.findByIdAndUpdate(
{ _id: req.params.id, merchantId: req.data.id }, // I think here is the problem
{
$set: {
parkingName,
price,
address,
...
},
}
);
return res.status(200).json({
success: true,
msg: "Parking has updated successfully",
});
} catch (error) {
return error.message;
}
};
However, the issue is that other users can now update another user's data which I want to stop
below is the query of middleware:
routing.patch("/parking/update/:id", middleware.authenticateToken, merchant.updateParking)
You should be showing each user only their parkings that they have created or belong to them.
const myParkings = async (req, res) => {
// always await code in try/catch block
const merchants = await Parkings.find({ user: req.user._id })
.. then populate the fields that you want to show
res.status(200).json({
success: true,
bookings,
});
};
you have to set this req.user._id when user logins. You could create a session.
I think what you're looking for is something like CASL Mongoose (or a similar package), and more specifically, the "conditions" section of the CASL docs.
What you're dealing with here is the distinction between 2 concepts:
AuthN (authentication) - determines who someone is and whether they are "authenticated" to make an API request
AuthZ (authorization) - determines what the authenticated user is allowed to do
In your app, middleware.authenticateToken is responsible for the AuthN piece of the equation. It makes sure that only users that have created an account are able to make requests to your API routes.
What you still need to solve for is the AuthZ piece, which can be done in a bunch of different ways, but one popular one is to use CASL, which is a Node AuthZ library that allows you to utilize your ORM's native query syntax to limit actions based on the authenticated (AuthN) user's attributes.
In other words, you can do something like, "Only allow user with ID 1 to update Parking entities that he/she owns". Below is generally what you're looking for (not tested for your use case, but the general idea is here):
const casl = require('#casl/ability');
// Define what a `Auth` (user) can do based on their database ID
function defineMerchantAbilities(merchantUser) {
const abilities = casl.defineAbility((allow, deny) => {
// Allow merchant to update a parking record that they own
allow('update', 'Parking', { merchantId: merchantUser.id })
})
return abilities
}
exports.updateParking = async (req, res) => {
const userId = req.data.id
const parkingId = req.params.id
// Find your merchant user in DB (see my comments at end of post)
const merchantUser = await Auth.findById(userId)
// Find your parking record
const parking = await Parking.findById(parkingId)
// Pass user to your ability function
const ability = defineMerchantAbilities(merchantUser)
// This will throw an error if a user who does not own this Parking record
// tries to update it
casl.ForbiddenError
.from(ability)
.throwUnlessCan('update', casl.subject('Parking', parking))
// If you make it here, you know this user is authorized to make the change
Parking.findByIdAndUpdate( ...your code here )
}
Additional comments/notes:
I would recommend removing your try/catch handler and using an Express default error handler as it will reduce the boilerplate you have to write for each route.
I would also recommend writing a middleware that finds a user by ID in the database and attaches it to a custom property called req.user so you always have req.user available to you in your authenticated routes.

Node JS Sequelize: running the same query again is retrieving the previous result

I am building a Web API using Node JS. For database operation, I am using the Sequalize package. In my test when I run the same query that gets a record after updating the record again, it is giving me the old/previous result.
Here is my code:
let testUser = await User.findOne({
where: {
email: {
[Op.eq]: testHelper.testUser.email
}
}
})
let now = new Date();
//genrate the token in the database before making the request.
let verificationToken = await VerificationToken.create({
userId: testUser.id,
verificationToken: "testing",
expiresAt: date.addSeconds(now, 40000)
})
let requestBody = {
email: testUser.email,
password: "Newpassword123",
confirm_password: "Newpassword123",
token: "testing"
}
const res = await request(app).put("/api/auth/reset-password").send(requestBody);
expect(res.statusCode).toBe(200);
expect(res.body.error).toBe(false);
//refresh the user instance by fetching the new record from the database.
let user = await User.findOne({
where: {
email: {
[Op.eq]: testHelper.testUser.email
}
}
})
// here user.password and testUser.password are the same even the record is actually updated
As you can see in the comment, the password of user object is still the same as the password of testUser object even though the password is updated in the database. How can I fix it to get the latest record?

How to save schema data to another collection from route function

Hello I have less idea in express route as I am new in backend with mongodb.
In the route below I am verifying email by resetting a schema value to true. Now I want to copy the new schema details to another existing collection. How can I do that ?
router.get('/:adminId/verifyAdmin',function(req,res){
console.log('request recieved');
Admin.findOne( {_id: req.params.adminId })
.exec()
.then(admin => {
const Thing = mongoose.model(admin.companyName);
const emailTokenn = req.query.id;
//console.log(emailTokenn);
Thing.updateOne( { emailResetTokenn: emailTokenn },{ $set: { verified: true }},(err) =>{
if(!err){
return res.redirect('https://localhost:3000/fw18/index.html');
}
else{
throw err;
}
});
});
});
Here I want to pass/copy/save Thingcollection details to existing collection name users in my db.
EDIT:- Tried this but getting error export :- const User = mongoose.model('User');
Thing.updateOne( { emailResetTokenn: emailTokenn },{ $set: { verified: true }},(err) =>{
if(!err){
//add Thing Schema to Users collection
Thing = mongoose.model(admin);
var copy = mongoose.model('admin', admin,'User');
admin.save(function(err){});
return res.redirect('https://s3.ap-south-1.amazonaws.com/fw18/index.html');
}
Error:-
MissingSchemaError: Schema hasn't been registered for model correct me .
Http is a stateless protocol. To maintain state of the application you can use
1) session
2) cookies and
3) query string.
On your case, you can handle using session.
Store information to the session and get stored information from different routes.

Why is property of user object in mongoose showing as undefined?

This is my Schema:
var UserSchema = new Schema ({
username: String,
password: String,
nativeLanguage: {
type: String,
default: "English"
},
This route prints out the user object to the console.
router.get('/add', isLoggedIn, async (req, res) => {
const Users = await User.find({_id: req.params._id});
console.log(req.user);
res.render('add', {User});
});
What I am trying to do is render specific properties of the user on the add page, to test that I am first trying to access them in the console. Unfortunately User.nativeLanguage comes up as undefined and I don't know why.
User is the model, Users as you named it is the user record.
Lets suppose you export your model.
exports.User = UserSchema;
Then in your route you need.
var user = require('../userModel');
user.find({ _id: req.params._id }, (err, doc) => (
if (!err && doc){
res.render('add', { User: doc});
}
))
Also consider using findOne instead of find if you plan to get only one record.

Resources