Log a user in and get their profile - node.js

I am attempting to log a user in to my DB. When I log the user in, it returns the first userId in the DB and not the user who logged in. I have been struggling with this for a while and really am at a dead end.
This is my POST route to log the user in:
// login
router.post("/login", async (req, res) => {
const user = await User.findOne({
email: req.body.email,
});
const secret = process.env.SECRET;
if (!user) {
return res.status(400).send("the user not found!");
}
if (user && bcrypt.compareSync(req.body.password, user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
secret,
{ expiresIn: "1d" }
);
res.status(200).send({ user: user.email, token: token });
} else {
res.status(400).send("password is wrong!");
}
});
The const user = await User.findOne({ email: req.body.email, }); this returns the wrong user.
When I query the endpoint get a users profile with the userId it gets the right information. So its got nothing to do with the DB.
This is the call in the app.
const handleSubmit = () => {
axios
.post(`${baseURL}users/login`, {
email: email,
passwordHash: password,
})
.then(res => {
console.log('USER ID TOKEN', res.data.token);
setbearerToken(res.data.token);
AsyncStorage.setItem('bearerToken', res.data.token);
const decoded = decode(res.data.token);
setTokenID(decoded.userId);
dispatch(setUser(res.data));
});
};
user.js model
const userSchema = mongoose.Schema({
contactName: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
phone: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
passwordHash: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
token: {
type: String,
},
isAdmin: {
type: Boolean,
default: false
},
clubName: {
type: String,
required: true,
},
clubAddress: {
type: String,
required: true,
},
clubEmail: {
type: String,
required: true,
},
clubPhone: {
type: String,
required: true,
},
clubWebsite: {
type: String,
required: true,
},
clubContact: {
type: String,
required: true,
},
})

Your schema doesn't have a field email to filter on.
const user = await User.findOne({
email: req.body.email,
});
Maybe you try clubEmail field. I reproduced the behavior and it looks like that mongoose ignores the filter if the field does not exist in the Schema an just returns the first document in the collection.
E.g.
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({name: "Superman"}, ...
Returns the user with name "Superman".
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({xname: "Superman"}, ...
But when using xname in the filter document which does not exist in my schema neither in the collection as field the query returns the first document in my test collection (its not Superman).
Also look here similar issue: Model.find Mongoose 6.012 always return all documents even though having filter
Issue reported: https://github.com/Automattic/mongoose/issues/10763
Migration Guide to Mongoose 6:
https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict

Related

MongoDB finding first user instead of the specified Email user

Im trying to find a user by their email in my MongoDB call through express and mongoose. Im getting it through a request body but at the moment it's only returning the first user in the collection or all the users in the collection, how do I find ONE user by their email address? I would obviously also like to then check their passwords...
User Schema looks like this
const users = mongoose.Schema({
Role: {
type: String,
default: 'Customer'
},
name: {
type: String,
required: true
},
password: {
type: String,
required: true
},
birthday:{
type: String,
required: true
},
displayName: String,
createdAt: {
type: Date,
default: Date.now
},
contact:{
email:{
type: String,
required: true
},
cellphone: String,
},
shippingAd:{
house:{
type: Number,
required: true,
},
road:{
type: String,
required: true,
},
complex: String,
city: {
type: String,
required: true,
},
province:{
type: String,
required: true,
},
postalCode:{
type: String,
required: true,
},
Country:{
type: String,
required: true,
},
},
newsletter:{
type: Boolean,
default: false
},
wishlist: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'products'}
]
});
users.pre('save', async function(next){
try {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(this.password, salt);
this.password = hashedPassword;
next();
} catch (error) {
next(error);
}
})
Express setup for the call
userRouter.post('/api/loginuser',async (req, res) =>{
const findUser = await userSchema.findOne({
email: req.body.email
});
if(findUser){
return res.json(findUser)
} else{
res.json(false)
}
});
Rest API call
const loginUser = (e) =>{
let payload = {
email: formValues.email,
password: formValues.password
}
axios.post('http://localhost:5001/api/loginuser', payload)
.then(res =>{
if(!res.data){
alert('There was no response from the database.')
} else{
if(res.data){
sessionStorage.setItem('user', res.data.user)
// navigate('/')
console.log(res.data)
}else{
alert('Something is wrong in the backend')
}
}
})
.catch(err =>{
console.log(err);
})
}
Your email field is nested within your contact info so in order to make a query to find a user by the email you have to search for that nest value like this.
const findUser = await userSchema.findOne({
"contact.email": req.body.email
});

How get user name from the userId to display in a post?

Hi everyone I am trying to add the user's name in the post that is being created by that user but I'm running into trouble.
This is the part of the post where the user's name should be displayed
<Link style={{ textDecoration: "none", color: "black" }}>
<h4
onClick={() => this.handleShowUserProfile(event.userId)}
className="host-name"
>
{getUser(event.userId).name}
</h4>
</Link>
This is where the user is being grabbed from the database
import http from "./httpService";
const apiEndPoint = "http://localhost:3100/api/users";
export function getUsers() {
return http.get(apiEndPoint);
}
export function getUser(userId) {
return http.get(apiEndPoint + "/" + userId);
}
in the backend this is how the user schema looks like
const jwt = require("jsonwebtoken");
const config = require("config");
const Joi = require("joi");
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
bio: {
type: String,
required: true,
minlength: 200,
maxlength: 400
},
interests: {
type: Array
},
email: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
password: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
isAdmin: Boolean
});
userSchema.methods.generateAuthToken = function() {
const token = jwt.sign(
{ _id: this._id, isAdmin: this.isAdmin },
config.get("jwtPrivateKey")
);
return token;
};
const User = mongoose.model("User", userSchema);
function validateUser(user) {
const schema = {
name: Joi.string()
.min(5)
.max(50)
.required(),
bio: Joi.string()
.required()
.min(200)
.max(400),
interests: Joi.array().required(),
email: Joi.string()
.min(5)
.max(255)
.required()
.email(),
password: Joi.string()
.min(5)
.max(255)
.required()
};
return Joi.validate(user, schema);
}
module.exports.User = User;
module.exports.validate = validateUser;
this is the event schema...
const Joi = require("joi");
const mongoose = require("mongoose");
const { categorySchema } = require("./category");
const { userSchema } = require("./user");
const Event = mongoose.model(
"Events",
new mongoose.Schema({
image: {
type: String
},
title: {
type: String,
required: true,
minlength: 5,
maxlength: 50,
trim: true
},
user: {
type: userSchema,
required: true
},
details: {
type: String,
required: true,
minlength: 200,
maxlength: 300,
trim: true
},
category: {
type: categorySchema,
required: true
},
numOfAttendies: {
type: Number,
required: true,
min: 3,
max: 10000
}
})
);
this is the handleShowUserProfile
handleShowUserProfile = id => {
this.setState({
showUserProfile: true,
shownUserID: id,
user: getUser(id)
});
};
handleHideUserProfile = () => {
this.setState({
showUserProfile: false
});
};
If I had to guess, getUser() likely returns a promise, not a value. You'll need to look into reading the name from props(state) and updating state instead of trying to do it all right there.
Try logging out that get request. See if the results it returns match up with what you're trying to get from it.
export function getUser(userId) {
const results = http.get(apiEndPoint + "/" + userId);
console.log("results of getUser ::: ", results)
return results
}
The getUser function is an asynchronous function so it will return a promise in most cases you need to use state and the lifecycle methods to fetch and set the username.
Can you update your handleShowUserProfile method like the following?
handleShowUserProfile = async (id) => {
const user = await getUser(id);
this.setState({
showUserProfile: true,
shownUserID: id,
user: user
});
};
I assume your api returns data for http://localhost:3100/api/users/:userId endpoint.
After you check your api and sure that it returns data, you need to check if you send a valid userId in this line.
onClick={() => this.handleShowUserProfile(event.userId)}
Lastly you should also change the following line since the user data is in the state.
{getUser(event.userId).name} => {this.state.user && this.state.user.name }

I have been trying to solve the duplicate key problem in nodeJS with Mongoose, but nothing works

I'm trying to build a user model, but I want to make sure that username and email are unique. When I created the first user everything was ok, but when I try to create the second user with the same information, I got the some error that I can handle in when I will save, but the duplicate key wasn't there to handle it.
This is my schema file code:
const UserSchema = new Schema({
// this username with SchemaType of string
username: {
type: String,
lowercase: true,
required: [true, "username is required"],
unique: true,
trim: true,
minlength: [4, "try to user longer name"],
maxlength: [60, "your name is way too long"],
},
// virtual name
name: {
// name have two properties
// first is first and refer to first-name
// second is last and refer to last-name
first: {
type: String,
lowercase: true,
trim: true,
minlength: 4,
maxlength: 20
},
last: {
type: String,
lowercase: true,
trim: true,
minlength: 4,
maxlength: 20
}
},
password: {
type: String,
required: [true, "password is required"]
},
email: {
type: String,
required: [true, "email is required"],
unique: true
},
active: {
type: Boolean,
default: true
},
admin: {
type: Boolean,
default: false
},
meta: {
update: {
type: Date,
default: Date.now()
},
timestamp: {
type: Date,
default: Date.now()
}
}
});
UserSchema.virtual("fullname").get(function () {
// return the concatenation of first and last
return this.name.first + " " + this.name.last;
});
// Create User Model
const User = mongoose.model("User", UserSchema);
module.exports = User;
And this is my router code where I tried to handle it:
router.post("/register", (request, response) => {
const user = {
username: request.body.username,
email: request.body.email,
password: request.body.password
};
if (!user.email && !user.username && !user.password) {
return response.json({
"message": "please fill the whole information"
});
}
// put user info in model
const newUser = new User({
username: user.username,
email: user.email,
password: user.password
})
newUser.validate((err) => {
console.log(err);
});
// save User in model
newUser.save()
// return response with info
return response.status(201).json(user);
})
I think the explanation here is quite a simple one. You are specifying the unique attribute in your schema for multiple fields, so mongo will not allow you to create multiple entries with the same information. This is quite obvious.
Also, I noticed a bit of irregularity in your code. The save method you are calling returns a promise, which means the event loop will not block your code and the response will be returned immediately. For this, you either need to handle your response inside the then block or use async await throughout your code.
I would suggest the following changes:
router.post("/register", (request, response) => {
const user = {
username: request.body.username,
email: request.body.email,
password: request.body.password
};
if (!user.email && !user.username && !user.password) {
return response.json({
"message": "please fill the whole information"
});
}
// put user info in model
const newUser = new User({
username: user.username,
email: user.email,
password: user.password
})
newUser.validate((err) => {
if(err) {
response.status(403).json({ message: 'Your custom error message' });
}
newUser.save().then(res => {
return response.status(201).json(user);
}).catch(e => {
return response.status(500).json({ message: 'Your custom error message' });
})
});
})

Mongoose unable to get _id from Schema

I 've a UserSchema that looks like:
export var UserSchema: Schema = new mongoose.Schema({
createdAt: Date,
email: {
type: String,
required: true,
trim: true,
unique: false,
},
firstName: {
type: String,
required: false,
trim: true
},
lastName: {
type: String,
required: false,
trim: true
},
password: {
type: String,
trim: true,
minlength: 6
},
tokens: [{
access: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}]
});
And I 've a instance method like:
UserSchema.methods.printThis = () => {
var user = this;
console.log("========>>> PRINTING USER NOW");
console.log(user);
};
The method printThis is being called from
router.post('/signup', (req, res) => {
var body = _.pick(req.body, ['email', 'password']);
var user = new User(body);
console.log("created user as: ", user);
user.printThis();
});
Below is the output:
created user as: { email: 'prsabodh.r#gmail.com',
password: '123456',
_id: 59be50683606a91647b7a738,
tokens: [] }
========>>> PRINTING USER NOW
{}
You can see that the user is getting created properly. However, when I call printThis method on User - I'm not able to print the same user back and an empty {} is printed. How to fix this?
You shouldn't use arrow functions (=>) if the calling function is explicitly setting a context (which is what Mongoose does):
UserSchema.methods.printThis = function() {
var user = this;
console.log("========>>> PRINTING USER NOW");
console.log(user);
};
More info on arrow functions and their handling of this here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#Arrow_functions
To get the _id value from the instance method can use _conditions that should work
UserSchema.methods.printThis = function(password) {
var user = this;
console.log(user._conditions['_id']);
};

Manually populating Mongodb field on document creation with Mongoose

Following the Mongoose documentation, I was able to create two docs, but am unable to populate one with the other.
Despite manually setting the 'account' value to reference the other document, my database doesn't seem to create the relation.
Below is the code I've used:
UserAuth.findOne({ email }, (err, user) => {
if (err) return done(err);
if (user) {
return done(null, false,
{ message: 'It appears that email address has already been used to sign up!' });
}
// Create the user account
const newAccount = new UserAccount({
name: {
first: req.body.firstName,
last: req.body.lastName,
},
});
newAccount.save((err) => {
if (err) throw err;
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount,
});
newAuth.password = newAuth.generateHash(password);
newAuth.save((err) => {
if (err) throw err;
return done(null, newAuth);
});
return done(null, newAccount);
});
});
Collections:
User Auth
const UserAuthSchema = new Schema({
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
account: {
type: Schema.Types.ObjectId,
ref: 'User',
},
});
module.exports = mongoose.model('UserAuth', UserAuthSchema);
User Account
const UserSchema = new Schema({
name: {
first: {
type: String,
required: true,
},
last: {
type: String,
required: true,
},
},
team: {
type: Schema.Types.ObjectId,
ref: 'Team',
},
image: {
type: String,
default: 'assets/default_user.png',
},
});
module.exports = mongoose.model('User', UserSchema);
It looks like the part:
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount,
});
should be:
// Create the user authentication
const newAuth = new UserAuth({
email,
account: newAccount._id,
});
And then, when you query the collection, you have to say which field should be populate, as shown in (Mongoose documentation)[http://mongoosejs.com/docs/populate.html]
Ad please check that the types of the 2 linked fields are the same as mentioned in the documentation.

Resources