I am making a rapid prototype of a MERN application, I have a backend question: I have a User model and a Category model, when a user sign up, I need to fill the category model with some basic informations exported from an object the user can edit later. I would like to assign every category a ref to the just created account id. The problem is I don't understand how I can retrieve the just created user id.
Here is my route (yes is not refactored, sorry):
// #route POST api/users/register
// #desc Register user
// #access Public
router.post('/register', (req, res)=>{
//Validate req.body
const {errors, isValid} = validateRegisterInput(req.body);
//Check validation
if(!isValid){
return res.status(400).json(errors);
}
User.findOne({ email: req.body.email })
.then(user => {
if(user){
errors.email = 'Email already exists';
return res.status(400).json(errors)
} else {
const avatar = gravatar.url(req.body.email, {
s: '200', //Size
r: 'pg', //Rating
d: 'mm' //Default
});
const newUser = new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
});
//Hash the password and save
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(newUser.password, salt, (err, hash)=>{
if(err) throw err;
newUser.password = hash;
newUser.save()
.then(user => res.json(user))
.catch(err => console.log(err))
})
});
//Fill user categories with default categories
defaultIcons.map((icon) => {
const newCategory = new Category ({
name: icon.name,
type: icon.type,
icon: icon.icon
})
newCategory.save();
});
}
})
});
And this is the Category Schema:
//Create Schema
const CategorySchema = new Schema({
//Every account is associated with actual User schema by id
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
name: {
type: String,
required: true
},
type: {
type: String,
required: true
},
icon: {
type: String,
required: true
}
})
What would be the best solution for this? Is it better to have a separated schema for the categories like I am doing or I can implement an array of objects field in the User schema?
The part where you do
if(err) throw err;
newUser.password = hash;
newUser.save()
.then(user => res.json(user))
.catch(err => console.log(err))
you have the user in your resolved promise. you can just
const newCreatedUserID = user._id
to get the just created user ID.
Related
Edit: [how to handle case of jwt expiration ]
I have read some article on how to implement email verification for your web application and each one follow up:
Creating a unique string, saving it in db with reference to user being verified and sending that unique string as a link for verification. When user visits that link, unique string is run against db and refernced user is validated.
But, I tried it in a different way, that user model contains verify status and will be false by default and when new user sign_up then a jwt token is created and that is sent to user as verification link and when the link is visited, jwt token is verified and user verify status is changed to true.
Above implementation worked for me and removes the use of creating and storing token in separate db but I am afraid this approach might have problems which I might not be aware of. here's the code for above.
passport configuration for auth(config-passport.js)
const bcrypt = require('bcrypt')
const LocalStrategy = require('passport-local').Strategy
const { User } = require('./models/user');
module.exports = (passport) => {
// passport local strategy
const authUser = (email, password, done) => {
User.findOne({ email: email }, function(err, user){
if(err) return done(err);
if(!user || !user.verify) return done(null, false);
if(user.verify){
bcrypt.compare(password, user.password, (err, isValid) => {
if (err) {
return done(err)
}
if (!isValid) {
return done(null, false)
}
return done(null, user)
})
}
})
}
passport.serializeUser((user, done) => {
done(null, user.id)
});
passport.deserializeUser((id, done) => {
User.findOne({ _id: id }, function(err, user){
done(err, user)
});
});
passport.use(new LocalStrategy({
usernameField: 'email'
}, authUser));
}
user model
'use strict';
const mongoose = require('mongoose');
const bcrypt = require('bcrypt')
const Joi = require('joi');
const Schema = mongoose.Schema;
//any changes done to userSchema will need changes done to userValidation.js
const userSchema = new Schema({
username: {type: String, required: true, maxlength: 100},
email: {type: String, unique: true, lowercase: true, required: true},
mobile: {type: Number, unique: true, required: true},
password: {type: String, required: true},
verify: { type: Boolean, enum: [false, true], default: false },
lib: [{ type: Schema.Types.ObjectId, ref: 'Book' }],
book_id: [{ type: Schema.Types.ObjectId, ref: 'Book' }]
});
const JoiValidUser = Joi.object({
username: Joi.string().min(3).max(50).required(),
email: Joi.string().email().min(5).max(50).required(),
mobile: Joi.string().regex(/^[0-9]{10}$/).required().messages({ 'string.pattern.base': `Phone number must have 10 digits.` }),
password: Joi.string().min(5).max(255).required()
});
userSchema.pre('save', async function(next){
const user = this;
const hash = await bcrypt.hash(user.password, 10);
this.password = hash;
next();
})
userSchema.methods.isValidPassword = async function(password) {
const user = this;
const compare = await bcrypt.compare(password, user.password);
return compare;
}
const User = mongoose.model('User', userSchema);
module.exports = { User, JoiValidUser };
user creation controller(userCreate.js)
const { User, JoiValidUser } = require('../models/user');
const mailer = require('../controller/mailHandler')
//takes data posted and form it in a readable format
//then validate/sanitize it against schema
//if error arises or user already exists a msg is passed on
//else user creation process is executed
module.exports = async function(req, res){
let user = {
username: req.body.username,
email: req.body.email,
mobile: req.body.mobile,
password: req.body.password
}
try{
JoiValidUser.validate(user);
const ExistUser = await User.findOne({
$or: [
{ email: req.body.email },
{ mobile: req.body.mobile }
]
});
if(ExistUser)
throw new Error("Email/Mobile Number already Registered");
await (new User(user)).save();
mailer(user.username, user.email);
res.send({ msg: "A Verification link is sent to mail" });
} catch(err) {
res.render('error', { message: err.message })
}
}
user verification route (verify.js)
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const config = require('dotenv').config().parsed
const { User } = require('../models/user')
const routePlan = require('../route_plan');
router.get('/:token', async(req, res) => {
const { email } = jwt.verify(req.params.token, config.SECRET);
await User.findOneAndUpdate({ email: email }, {
$set: { verify: true }
});
res.send("Welcome ...")
})
module.exports = router;
EDIT:
Thank you all for your feedback but there is another problem I want to be clear of on how to handle case when jwt token expires because link will be invalid and user cannot try to sign up again because his info is already in db and he cannot register again
I have one model is user in that model I was added email, username, password and name , when I have insert this data using node JS with the help of rest API, so that condition all 4 records are stored in one table
but I want email and name is stored in registration table and username and password stored in login table ,when I put login request using postman it with username name and password credentials it gives the successful response.
I am new to Node
My controller is
exports.user_signup = (req, res, next) => {
User.find({ username: req.body.username })
.exec()
.then(user => {
if (user.length >= 1) {
return res.status(409).json({
message: "Mail exists"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).json({
error: err
});
} else {
const user = new User({
_id: new mongoose.Types.ObjectId(),
username: req.body.username,
password: hash,
email: req.body.email,
contact: req.body.contact,
});
user
.save()
.then(result => {
// console.log(result);
res.status(201).json({
message: "User created"
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
}
});
}
});
};
My Postman post method is in JSON form
{
"username":"tene",
"password":"tene",
"email":"tene#gmail.com",
"contact":1234567890
}
You can try this:
import mongoose from 'mongoose'
const { Schema } = mongoose
const userSchema = new Schema(
{
registrationTable : {
email: { type: String, required: true },
mobileNo: { type: String, required: true }
},
loginTable: {
username: { type: String, required: true },
password: { type: String, required: true }
}
},
{ timestamps: true }
)
const UserModel = mongoose.model('User', userSchema)
It will depend on you if you wanna make registration and login table as an object or array, but this will sure help.
required: true will be for, you need that value necessary, if you dont want some value just remove this.
I am trying to create a user in mongoose and return it after User.create query without password field. I set "select: false" on password field in model schema but it keeps returning me password in response after User.create.
// models/user.js
const userSchema = new mongoose.Schema({
// ...
password: {
type: String,
required: true,
minlength: 5,
select: false,
},
});
// routes/index.js
routes.post(
"/sign-up",
celebrate({
body: Joi.object().keys({
name: Joi.string().min(2).max(30),
about: Joi.string().min(2).max(30),
avatar: Joi.string().pattern(RegExp(urlChecker)),
email: Joi.string().required().email(),
password: Joi.string().required().min(5),
}),
}),
usersController.createUser,
);
// controllers/user.js
const User = require("../models/user");
exports.createUser = (req, res, next) => {
const {
name,
about,
avatar,
email,
password,
} = req.body;
bcrypt
.hash(password, 10)
.then((hash) => User.create({
name,
about,
avatar,
email,
password: hash,
}))
.then((user) => {
if (!user) {
throw new InvalidInputError("Invalid data");
}
res.send(user); // response includes password field
})
.catch((err) => next(err));
};
However, if I add User.findById query after User.create, I get a response without password field.
// controllers/user.js
// ...
.then((user) => {
if (!user) {
throw new InvalidInputError("Invalid data");
}
return User.findById(user._id);
})
.then((user) => {
if (!user) {
throw new NotFoundError("User not found");
}
res.send(user); // works fine!
})
Am I right that {select: false} works only on find queries in mongoose? Are there any other workarounds for not returning password field after User.create method?
the result of save is a object model, you should convert it to a object and delete password key, like this:
user = user.toObject()
delete user.password
res.send(user); // response includes password field
I'm making post request on registration,But I want error to pop up if username is already taken.
Any suggestions?
Here is my post route:
app.post('/addUser', (req,res) => {
const addUser = new User({username: req.body.username, password: req.body.password})
addUser.save().then(result => res.status(200).json(result)).catch((err) => console.log(err))
})
Alternate method, depending on the error style you want.
const users = new mongoose.Schema(
{
username: {type: String, unique: 'That username is already taken'}
},
{ timestamps: true }
)
Now mongo will index usernames and check it before the insertion. An error will be thrown if it's not unique.
You can use findOne method of mongoose
app.post('/addUser', async (req,res) => {
//validation
var { username, password } = req.body;
//checking username exists
const existUsername = await User.findOne({ username: req.body.username});
if (existUsername) {
console.log('username taken');
}
});
I am trying to update user data in the settings page. Where he/she can change all details like name, last name, birthday and so on. Here is the auth controller:
module.exports = {
async CreateUser(req, res) {
const schema = Joi.object().keys({
username: Joi.string()
.min(4)
.max(10)
.required(),
email: Joi.string()
.email()
.required(),
firstName: Joi.string()
.required(),
lastName: Joi.string()
.required(),
position: Joi.string()
.required(),
password: Joi.string()
.min(5)
.required(),
});
const { error, value } = Joi.validate(req.body, schema);
if (error && error.details) {
return res.status(HttpStatus.BAD_REQUEST).json({ msg: error.details })
}
const userEmail = await User.findOne({
email: Helpers.lowerCase(req.body.email)
});
if (userEmail) {
return res
.status(HttpStatus.CONFLICT)
.json({ message: 'Email already exist' });
}
const userName = await User.findOne({
username: Helpers.firstUpper(req.body.username)
});
if (userName) {
return res
.status(HttpStatus.CONFLICT)
.json({ message: 'Username already exist' });
}
return bcrypt.hash(value.password, 10, (err, hash) => {
if (err) {
return res
.status(HttpStatus.BAD_REQUEST)
.json({ message: 'Error hashing password' });
}
const age = moment().diff(moment([value.byear, value.bmonth - 1, value.bday]), 'years');
const body = {
username: Helpers.firstUpper(value.username),
email: Helpers.lowerCase(value.email),
firstName: value.firstName,
lastName: value.lastName,
position: value.position,
password: hash,
};
User.create(body)
.then(user => {
const token = jwt.sign({ data: user }, dbConfig.secret, {
expiresIn: '5h'
});
res.cookie('auth', token);
res
.status(HttpStatus.CREATED)
.json({ message: 'User created successfully', user, token });
})
.catch(err => {
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error occured' });
});
});
},
User model
const userSchema = mongoose.Schema({
username: { type: String },
email: { type: String },
isVerified: { type: Boolean, default: false },
firstName: { type: String },
lastName: { type: String },
position: { type: String },
password: { type: String },
I guess I shoud have a route like this:
router.post('/user/settings', AuthHelper.VerifyToken, user.editUser);
How should it look like editUser controller according to above CreateUser function? I am using Angular in the front-end. But I think it doesn't matter. I assume 90 percent should be the same as CreateUser but what exactly should be changed so the user can update his/her details in settings form and change data in the model?
So you want to update some of user's fields (such as firstName, lastName and etc.), not replacing the whole information. Then you might want to get the current user's data first and then update only those allowed fields.
Please find the sample code below.
/**
* User router
*/
router.put('/user/:userId', AuthHelper.VerifyToken, user.editUser);
// This function will be triggered when Express finds matching route parameter
router.param('userId', function (req, res, next, id) {
User.findOne(id, function (err, user) {
if (err) {
next(err);
} else if (user) {
// When it finds user information, bind that to request object, which will be used in the other middlewares.
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
/**
* User controller
*/
exports.editUser = (req, res, next) => {
let { user } = req;
// You pick only allowed fields from submitted body
const allowedFields = { firstName: req.body.firstName, lastName: req.body.lastName, birthday: req.body.birthday };
// Override the current user data with new one
user = Object.assign(user, allowedFields);
user.save((err, savedUser) => {
if (err) {
return next(err);
}
res.json(savedUser.toJSON());
});
};