how to return the user email - node.js

I have created post route to store posts in the database. It's a protected route so user can store post only after entering the login details. When I post in postman, I've seen that the user email is not returned in the object. Even in the mongodb collection, I don't see the email associated with the post. How do I include the email as well with the post object. I don't want the user to enter the email again and again when posting because they have already logged in. So I kinda want to store the email automatically with the post. Hope I make sense. Can someone help me with this?
Right now the object is kinda stored like this in the posts collection in mongodb
_id: ObjectId("5f1a99d3ea3ac2afe5"),
text: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. ",
user:ObjectId("5f1a99d3eac2c82afe5"),
age:20,
country:"India",
gender:"male",
date:2020-07-24T08:23:35.349+00:00,
__v:0
I want the email too in the above object.
Post model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema ({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
email: {
type: String
}
,
age: {
type: Number,
required: true
},
gender: {
type: String,
required: true
},
country: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = Post = mongoose.model('post', PostSchema)
post route
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth')
const { check, validationResult} = require('express-validator');
const User = require('../../models/User')
const Post = require('../../models/Post')
router.post('/', [auth, [
check('text', 'Text is required').not().isEmpty()
]], async (req,res)=>{
const errors = validationResult(req);
if(!errors.isEmpty()){
return res.status(400).json({errors: errors.array()})
}
try {
const user = await (await User.findById(req.user.id)).isSelected('-password')
const newPost = new Post({
text: req.body.text,
name: user.name,
user: req.user.id,
age: req.body.age,
country: req.body.country,
gender: req.body.gender,
email: req.user.email // this email is not stored with the post and I want this to be automatically posted in the collection without the user having to type it again to save the post
})
const post = await newPost.save();
res.json(post);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error')
}
})
module.exports = router;
User model
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = User = mongoose.model('user', UserSchema);

Change isSelected to select
const user = await (await User.findById(req.user.id)).isSelected(password')

What I potentially see the problem here is, once you have grabed the object of user, you're still referring to req.user.email instead of user.email.
If that does not solve your problem, try to console.log the user returned from after User.findById
Update:
You can see here that isSelected returns boolean. So you're essentialy getting true for having password field in user. Also instead of req.user.email use user.email

Related

Bcryptjs Compare method always returns false?

Bit stumped.. my code is identical to a tutorial I'm following for this section. However, bcryptjs.compare is always returning false.
Database is mongodb and string length limit is set to 16mb from what I read so I dont think it has to do with that.
userModel.js
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
isAdmin: {
type: Boolean,
required: true,
default: false
}
},{
timestamps: true
})
userSchema.methods.comparePW = async function(password) {
console.log(await bcrypt.compare(password, this.password))
return await bcrypt.compare(password, this.password)
}
module.exports = mongoose.model('User', userSchema)
userController.js
const userModel = require('../models/userModel')
const asyncHandler = require('express-async-handler')
const userAuth = asyncHandler(async(req, res) => {
const { email, password } = req.body
// check if reqbody pw and email matches userModel pw/email
const user = await userModel.findOne({ email })
if (user && (await user.comparePW(password))) {
res.send('match')
} else {
res.send('no match')
}
})
module.exports = { userAuth }
dummy user filler data in the database
const bcrypt = require('bcryptjs')
const users = [
{
name: 'Admin',
email: 'admin#test.com',
password: bcrypt.hashSync('admin123, 10'),
isAdmin: 'true',
},
{
name: 'Max Smith',
email: 'Max#test.com',
password: bcrypt.hashSync('admin123, 10'),
},
{
name: 'Jennifer Garnett',
email: 'Jen#test.com',
password: bcrypt.hashSync('admin123, 10'),
},
]
module.exports = users
using console.log, the bcrypt.compare method always returns false.
Strange as this is how the tutorial has it and it seems to be working for the instructor.
Using Postman when I run a post request with email "admin#test.com" and password: "admin123" it is return false every time.
I tried reimporting the dummy data and also reloading data on mongodb compass.
Not sure what to do at this point to fix this issue? Thoughts?
While hashing the password you are combining the password and salt strength into a string like this bcrypt.hashSync('admin123, 10') which should be like this bcrypt.hashSync('admin123', 10). If you want to work with the current situation then u need to enter password "admin123, 10" instead of "admin123".

how to reference a model in another model using mongoose?

I'm pretty new to node and mongoose, still learning a lot. Basically I am trying to create a forum page. I have a forumpost schema and I have recently added in a new field that I would like to show which user posted it. I have read other questions on this online and I was able to follow the code on there however mine is still not working. When i check my data in atlas it is still missing the new 'submitted by' field that I added. I have already deleted the 'collection' and have started over but it is still missing. Any help would be appreciated. Heres my models below as well as a screencap of how the data is being posted to the db.
**Post Form Schema**
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
required: true,
},
submittedBy: { *(this is where I would like to get the user who submitted the form)*
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
extraInfo: {
type: String,
default: 'Other info goes here',
}
})
const Post = mongoose.model('Post', PostSchema);
module.exports = Post;
**Users Form Schema**
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
EDIT: heres my newpost route
const express = require('express');
const Post = require('../models/post');
const router = express.Router();
const {ensureAuthenticated} = require("../config/auth.js");
router.get('/', ensureAuthenticated, (req, res) => {
res.render('newPost')
})
router.post('/', ensureAuthenticated, (req, res) => {
const post = new Post(req.body);
console.log(req.body)
post.save()
.then((result) => {
res.redirect('/dashboard')
})
.catch((err) => {
console.log(err)
})
})
module.exports = router;
If I'm not mistaken, you validate if is authenticated with the "ensureAuthenticated" middleware (the user ID should be there) but when creating the "Post" you only do it with the body data.
It is something like this ( you should replace "userId" with your property name):
const post = new Post({ ...req.body, submittedBy: userId })

MongoDB/Mongoose some of the model properties undefined in app.js

I am working on an Authentication/Session using Express, NodeJS, and MongoDB.
The Mongoose Schema looks like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: { type: String, required: true, },
email: { type: String, required: true, unique: true, },
password: { type: String, required: true, },
SignUpDate: { type: { type: Date, default: Date.now } },
LastLogin: { type: { type: Date, default: Date.now } },
loggedin: { type: Boolean, required: false, },
attempts: { type: Number },
});
module.exports = mongoose.model("User", userSchema);
The Signup form only takes username, email, password, but I would like to save sign update, last login, failed login attempts, etc.
In the controller.js file, I have the routes, this is the problematic one.
exports.register_post = async (req, res) => {
const { username, email, password } = req.body;
let user = await User.findOne({ email });
if (user) {
req.session.error = "User already exists";
return res.redirect("/register");
}
const hasdPsw = await bcrypt.hash(password, 12);
user = new User({
username,
email,
password: hasdPsw,
SignUpDate,
loggedin: true
});
await user.save();
console.log(user)
res.redirect("/login");
};
And in App.JS I have this
app.post("/register", appController.register_post);
If I only use username, email, and password in the Schema, it all works, saves to the database.
But as above, I get
"UnhandledPromiseRejectionWarning: ReferenceError: SignUpDate is not
defined"
if I submit the signup button on the /register route. Another question, if I want to get a timestamp with Mongoose, do I have to call Date.now() and where?
Or do I have to define and add/push the properties that are not user provided via Signup Post ( SignUpDate,LastLogin:,loggedin:,attempts ) to the Schema after the users sign up? I am new to using Mongoose, going through the docs and cant seem to find how to ad a timestamp.
A little update, if I comment out the SignUpDate,LastLogin variables in the post function, I get "Object, Object" in MongoDBcompass and the object is collapsible, it saved the values in the database but crashed the app. The change that was necessary was simply
SignUpDate: { type: { type: Date, default: Date.now } },
LastLogin: { type: { type: Date, default: Date.now } },
to
SignUpDate: {
type: Date, default: Date.now(),
},
LastLogin: {
type: Date, default: Date.now(),
}
This is how it looks in the database, and it gets saved and the app doesn't crash. but as soon I uncomment "SignUpDate" in the route function, I get the same undefined error again.
I can live with this, but would rather not. Besides, how do I convert "type: Date, default: Date.now()" to a nice out put like ""Sun May 10 2015 19:50:08 GMT-0600 (MDT)"? If I change it in the Schema, it don't work, if I change it in the route function, it will not let me chain the functions and I don't know where to declare the var for the nice formatted output.
Remove "SignUpDate":
const user = new User({
username,
email,
password: hasdPsw,
loggedin: true
});
If you specified a default value, you don't need to specify it when you create new object.
If you want to accumulate the number of unsuccessful attempts, you need to get the users from the base, increase the counter by one and update it in the base. Smth like this:
let userAttempts = await User.findOne({ username });
await User.update({ username }, { $set: { attempts: userAttempts.attempts + 1 } });

Node, Mongoose - Referencing Document not working as expected

In my app, users can create posts. I want to assign a user to each post by referencing the users ObjectId.
Post model and schema:
const postSchema = new mongoose.Schema({
postImage: {
type: String,
required: true
},
caption: {
type: String
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
date: {
type: Date,
default: Date.now()
}
})
const Post = mongoose.model('Post', postSchema)
User model and Schema:
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
minlength: 4,
maxlength: 50
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 75
}
})
userSchema.methods.generateAuthToken = () => {
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'))
return token
}
const User = mongoose.model('User', userSchema)
In my post route, I apply the following middleware (auth middleware):
module.exports = (req, res, next) => {
const token = req.header('x-auth-token')
if (!token) return res.status(401).send('No token provided')
try {
const decoded = jwt.verify(token, config.get('jwtPrivateKey'))
req.user = decoded
next()
}
catch (ex) {
res.status(400).send('Invalid token')
}
}
Post route:
router.post('/', [auth, upload.single('postImage')], async (req, res) => {
const post = new Post({
postImage: `http://localhost:3000/${req.file.path}`,
caption: req.body.caption,
user: req.user._id
})
await post.save()
res.send(post)
})
When I make a request to the route, I set x-auth-token, and I get a response of the post object created. However in the database the user property on the post object has not been set . Only the following properties have been set on the post object created. _id, date, postImage, caption, __v. Does anybody know why the user is not being set as a property? Thanks.
UPDATE:
This is how I generate the jwt token, with _id attached:
userSchema.methods.generateAuthToken = () => {
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'))
return token
}

Validation with MongooseJS causing NodeJS app to crash

I'm trying to find a better way of performing this validation. I have the user schmea setup and I'm trying to get the age validation working properly as to not cause the app to crash. You'll have to forgive me as I'm still relatively new to the language, so I may not be explaining it 100%. However, here is the User schema I created.
const mongoose = require('mongoose')
const validator = require('validator')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
age: {
type: Number,
default: 0,
validate(value) {
if(value < 13){
throw new Error('You must be over the age of 13 to register for this site!')
}
}
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value){
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
trim: true,
minlength: 7,
validate(value){
if (value.toLowerCase().includes('password')) {
throw new Error('Password cannot contain "password"')
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.virtual('tasks', {
ref: 'Task',
localField: '_id',
foreignField: 'owner'
})
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, 'thisismynewcourse')
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
//Hash the plain text password before saving
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
userSchema.methods.toJSON = function () {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.tokens
return userObject
}
const User = mongoose.model('User', userSchema)
module.exports = User
The exact area that I'm trying to hone in on is in the age section, I'm trying to validate ages 13 or older, and when I run a test user creation through post man it performs the validation correctly, but it stops the application with the following:
UnhandledPromiseRejectionWarning: ValidationError: User validation failed: age: You must be over the age of 13 to register
Is there a way that I can prevent the application from crashing or should I perform the validation else where? Thanks in advance.
Normally the validation is performed in another file. This can be considered to be a service. But it should pass through a controller first if you want to do it properly. Here is an example of a simple blog post schema I made. You can see the function at the bottom runs every time before I send it to the database.
This is how it looks like in my schema file looks like which is located in folder called models.
// Requiring modules
const mongoose = require('mongoose');
// Initializing Schema
var Schema = mongoose.Schema;
// Creating a data model
const schema = new Schema({
shopname : {type: String, required:true},
address : {type: String, required:true},
review : {type: String, required:false},
image : {type: String, required:false},
originalname: {type: String, required:false},
filename: {type: String, required:false},
mimetype: {type: String, required:false},
size : {type: String, required:false},
updatedAt: {type: Date, required:false},
createdAt: {type: Date, required:false}
})
// Settings up the process before the data is sent to mongoDB.
// This process is call everytime 'save' is called.
// it sets the data for createdAt and updatedAt.
schema.pre('save', function(next){
if (!this.createdAt){
this.createdAt = new Date();
}else{
this.updatedAt = new Date();
}
next();
})
// Exports module
module.exports = mongoose.model("Blog", schema);

Resources