I'm trying to perform user validation using if statements but on testing using post man, I keep on getting only the first return statement 'Email is required ' even after adding a valid email address and also an invalid email address. I have attached the user model, user controller logic and a picture of postman's response
user schema model
const mongoose = require('mongoose');
const { Schema } = mongoose
const userSchema = new Schema({
firstName: {
type: String,
required: true,
min: 3,
max: 20
},
lastName: {
type: String,
required: true,
min: 3,
max: 20
},
email: {
type: String,
required: true,
unique: true,
},
phoneNumber: {
type: String,
required: true,
},
password: {
type: String,
required: true,
min: 5
},
confirmPassword: {
type: String,
required: true,
min: 5
}
});
const User = mongoose.model('User', userSchema);
module.exports = User;
user.controller.js
module.exports.users = async(req, res) => {
try {
const email = await user.findOne({ email: req.body.email });
const firstName = await user.find({ firstName: req.body.firstName });
const lastName = await user.find({ lastName: req.body.lastName });
const password = await user.find({ password: req.body.password });
const confirmPassword = await user.find({ confirmPassword: req.body.confirmPassword });
const phoneNumber = await user.find({ phoneNumber: req.body.phoneNumber });
if (!email) return res.send('Email is required')
const filterEmail = /^([a-zA-Z0-9_\.\-])+\#(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
if (!filterEmail.test(email.value)) return
res.send('Please provide a valid email address');
if (email) return res.send('Email already exists');
if (!firstName) return res.send('First Name is required');
if (firstName.length < 3 || firstName.length > 20) return
res.send('First name must be at least 3 characters and less than 20 characters');;
if (!lastName) return res.send('Last Name is required');
if (lastName.length < 3 || lastName.length > 20) return
res.send('Last name must be at least 3 characters and less than 20 characters')
if (!password) return res.send('PassWord is required');
const filterPassword = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{5,15}$/;
if (!filterPassword.test(password.value)) return
res.send('Password must include at least one lowercase letter, one uppercase letter, one digit, and one special character');
if (!confirmPassword) return res.send(' Please confirm password');
if (password.value !== confirmPassword.value) return res.send('Passwords do not match');
if (!phoneNumber) return res.send('Phone Number is required');
phone(phoneNumber.value);
let User = new user(_.pick(req.body, ['firstName', 'lastName', 'email', 'phoneNumber', 'password']));
bcrypt.genSalt(10, async(err, salt) => {
if (err) throw err;
return user.password = await bcrypt.hash(user.password, salt);
});
await User.save();
} catch (err) {
res.status(500).send('Something went wrong');
console.warn(err);
}
}
Your controller is not good. You need to get the email, lastName and firstName from req.body.
const {email, lastName} = req.body
And then do the validation, which by the way will already happen in mongoose.
const email = await user.findOne({ email: req.body.email });
In this line you are looking in your DB for a user with email = req.body.email. But since there is no such a user it return your if statement
if (!email) return res.send('Email is required')
to get your user you only need to compare with one value if you set it to be unique in your schema, for instead user email.
const user = await user.findOne({ email });
In this case email was already destructed and if there is no user, you can create one.
you can use await user.create() and pass your values and hash the password with pre("save") in your schema.
UserSchema.pre("save", async function () {
if (!this.isModified("password")) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
You can also validate user email in the schema
email: {
type: String,
required: [true, "Please provide email"],
match: [
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please provide a valid email",
],
unique: true,
},
You can simply install the validator package npm i validator then call it in in the user model const { isEmail } = require('validator'); and finally type this in your schema instead of hardcoding the verification code in the function.
email: { type: String, required: true, validate: [isEmail], unique: true },
Instead of writing custom logic for validation Please add a validation layer after performing security checks
you can use Express validator, to perform all your validations in a smoother way, It will eliminate unnecessary logic and code lines and add structured code to validate all types inputs, nested objects, DB call for existing data handling. It will handle the response and response message on its own without hitting the controller
just hit
npm install --save express-validator
and you are ready to validate requests very easily and effectively
Beside other answers i thought one thing which is missing but yet it is very strong validation method in Node.JS is JOI package, you can define your validation schema and get rid of all other pain full if and else, example is following
const Joi = require('joi');
const schema = Joi.object().keys({
firstName: Joi.string().alphanum().min(3).max(20).required(),
lastName: Joi.string().alphanum().min(3).max(20).required(),
email: Joi.email().required(),
phoneNumber: Joi.string().length(10).pattern(/^[0-9]+$/).required(),
password: Joi.string().min(5).required(),
confirmPassword: Joi.any().valid(Joi.ref('password')).required()
});
const dataToValidate = {
firstName: 'chris',
lastName: 'John',
email: 'test#test.com'
}
const result = Joi.validate(dataToValidate, schema);
// result.error == null means valid
Without node script you can always test your JOI schema and your data object on this webpage
Related
It's a simple signup route to store credentials in a mongoDB database but I miss something because the 2 else if won't work properly. I suspect it is my find().
The first else if returns me in Postman "error": "E11000 duplicate key error collection: vinted.users index: email_1 dup key: { email: \"jean#dpont.com\" }" and the second give me "email already exists".
Thanks in advance for your help
const express = require("express");
const router = express.Router();
const SHA256 = require("crypto-js/sha256");
const encBase64 = require("crypto-js/enc-base64");
const uid2 = require("uid2");
const User = require("../models/User");
router.post("/user/signup", async (req, res) => {
try {
const email = req.fields.email;
const username = req.fields.username;
const phone = req.fields.phone;
const password = req.fields.password;
const token = uid2(64);
const salt = uid2(16);
const hash = SHA256(password + salt).toString(encBase64);
const emailSearch = await User.find({ email: email });
if (!emailSearch || username !== null) {
const newUser = new User({
email: email,
account: {
username: username,
phone: phone,
},
password: password,
token: token,
hash: hash,
salt: salt,
});
await newUser.save();
res.status(200).json({
_id: newUser._id,
token: newUser.token,
account: newUser.account,
});
}
//problem under
else if (emailSearch) {
res.status(404).json({ message: "email already exists" });
} else if (username === null) {
res.status(404).json({ message: "please type a username" });
}
} catch (error) {
res.status(404).json({
error: error.message,
});
}
});
It looks like the issue is that if the username in the request body is not null, it's going to attempt to create a new User with that username regardless of whether a User exists with the same email - if (!emailSearch || username !== null).
It's generally best-practice to do as much input validation as you can before you start looking for records or creating new ones, as you will be able to avoid more Mongo errors and database actions if you can stop invalid actions before they're attempted. So in this case, check that the username is valid before looking for existing Users.
To solve this problem, I would move that last else-if to before you check whether a User exists with the same email. That way, once you determine whether the username is valid, then the only thing that you need to consider is existing Users before creating a new one. Something like this:
if (username === null) {
res.status(400).send({ message: "Error: Please provide a 'username'." });
}
const existingUserWithEmail = await User.find({ email: email });
if (!existingUserWithEmail) {
// Create the new User
} else {
res.status(400).send({ message: "Error: An account already exists with this email." });
}
I wrote a method to update a user in my application and everything works correctly there. In addition, I wrote some code that fires before the document is saved and it isn't functioning correctly.
The point of the code is to determine if the user modified their password. If they didn't, simply call next(). If they did, bcrypt will hash the password.
Here's the code in my controller where I'm doing the update:
// #desc Update user by ID
// #route PUT /api/users/:id
// #access Private
const updateUserById = asyncHandler(async (req, res) => {
// Destructure body content from request
const {
firstName,
lastName,
username,
email,
role,
manager,
learningStyle,
departments,
facility,
company,
isActive,
} = req.body;
// Search the database for the user
const user = await User.findById(req.params.id);
// Check to ensure the user was found. Else, respond with 404 error
if (user) {
// Update information accordingly
user.firstName = firstName || user.firstName;
user.lastName = lastName || user.lastName;
user.username = username || user.username;
user.email = email || user.email;
user.role = role || user.role;
user.manager = manager || user.manager;
user.learningStyle = learningStyle || user.learningStyle;
user.departments = departments || user.departments;
user.facility = facility || user.facility;
user.company = company || user.company;
user.isActive = isActive === undefined ? user.isActive : isActive;
// Save user to the database with updated information
const updatedUser = await user.save();
// Send the updated user to the client
res.json(updatedUser);
} else {
res.status(404);
throw new Error("User not found");
}
});
Here's my User model code:
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
const userSchema = mongoose.Schema(
{
firstName: {
// The user's first name
required: true,
type: String,
trim: true,
},
lastName: {
// The user's last name
required: true,
type: String,
trim: true,
},
username: {
// The user's username - user can create their own
required: true,
type: String,
unique: true,
lowercase: true,
},
email: {
// The user's email
required: false,
trim: true,
lowercase: true,
unique: true,
type: String,
},
password: {
// The user's encrypted password (bcrypt hash)
required: true,
type: String,
},
role: {
// The user's role - drives what they're able to do within the application
required: true,
type: mongoose.Schema.Types.ObjectId,
ref: "Role",
},
manager: {
// The user's manager
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "User",
},
learningStyle: {
// The user's learning style (after assessment is taken)
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "LearningStyle",
},
departments: [
// The departments the user belongs to (used to drive what the user sees)
// Example: Finishing, Shipping, Printing
{
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "Department",
},
],
facility: {
// The facility the user works at. Example: Austin Facility
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "Facility",
},
company: {
// The company the user works for. Example: Microsoft
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Company",
},
isActive: {
required: true,
type: Boolean,
default: true,
},
},
{ timestamps: true }
);
// Match user's password using bcrypt
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
// Generate user's encrypted password on save
userSchema.pre("save", async function (next) {
// Check to see if password is modified. If it is, encrypt it. If not, execute next();
if (!this.isModified("password")) {
console.log("Does this run?");
next();
}
console.log("Does this run as well?");
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
export default User;
I have:
Added two console logs, both of which fire (see userSchema.pre("save")) in model.
Tried to prevent the encryption from firing by checking if password is modified
Tried dropping the entire users collection and starting over
Another application from a course with the exact same approach working fine
Notice in my controller I am NOT updating the password at all. Yet, every time I use Postman to send a PUT request and modify even the name, the password gets hashed again and both console logs fire.
The "save" middleware is calling next() but continuing on to complete the function. Use return or else to guard the rest of the code.
userSchema.pre("save", async function (next) {
// Check to see if password is modified. If it is, encrypt it. If not, execute next();
if (!this.isModified("password")) {
// Finish here
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
Sorry I'm new to javascript and nodejs, but basically I am trying to create a registration system. I've attached my form validator and my user schema, but I keep getting an isEmail of undefined error. I'm not sure if there's an easier way to go about checking these conditions. Earlier, I was using req.checkBody, but I wasn't sure how to use that to check if a certain user is above a given age.
router.post('/register', function(req, res, next) {
var name = req.body.name;
var email = req.body.email;
var password = req.body.password;
var password2 = req.body.password2;
var bday = req.body.bday;
var birthday = moment(bday);
let errors = [];
// Form Validator
if (!name || !email || !password || !password2 || !bday) {
errors.push({ msg: 'Please fill in all required fields' });
}
if (!req.body.email.isEmail()) {
errors.push({ msg: 'Please provide an appropriate email' });
}
if (req.body.password2 != req.body.password) {
errors.push({ msg: 'Please make sure your passwords match' });
}
if (!birthday.isValid()) {
errors.push({ msg: 'Date of Birth must be in appropriate format' });
}
if (moment().diff(birthday, 'years') < 13) {
errors.push({ msg: 'User must be at least 13 years of age' });
}
if(errors.length > 0){
res.render('register', {
errors,
name,
email,
password,
password2,
bday
});
} else{
var newUser = new User({
name: name,
email: email,
password: password,
bday: bday,
});
var UserSchema = mongoose.Schema({
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
bday: {
type: String
}
});
When you look at this here
if (!req.body.email.isEmail()) {
errors.push({ msg: 'Please provide an appropriate email' });
}
You are trying to access the isEmail properties from email which must've been non existent.
What the req might have looked like: { req: { body: {} } }, as you can see email must be undefined and undefined does not contain isEmail property.
What you can do is
if (req.body.email && !req.body.email.isEmail()) {
errors.push({ msg: 'Please provide an appropriate email' });
}
Check first if req.body.email exists before trying to access isEmail.
I am creating accounts for new users by this mutation createaccnt but i ended up getting the error above, have stared at it for days..could someone point where i have messed and correct.
here are the codes.
how i defined my schema
const userSchema = new Schema({
username:String,
email:String,
Password:String,
addedOn:String
});
type definitions
//user type definitio
type User{
id:ID!,
username:String!,
email:String!,
token:String!,
Password:String!,
addedOn:String!,
},
input UserInput{
email:String!,
username:String!,
Password:String!,
confirmPassword:String!
},
//mutation
type Mutation{
createAccnt ( userInput:UserInput!) : User!
}
this is how i implemented the mutatiion
//createAccnt
async createAccnt(parent,{userInput:{
email,
username,
Password,
confirmPassword
}},context,info){
try {
const {errors,valid}=validateUserInput(
email,
username,
Password,
confirmPassword
);
//allow for error detection
if(!valid){
throw new UserInputError('errors',{errors})
};
//check if user already exist
const user= await User.findOne({username});
if(user){
throw new UserInputError('username is taken',{
errors:{
username: 'username is taken'
}
});
};
//hash password
Password= await bcrypt.hash(Password,12);
//create a new user object
const newUsr= new User({
email,
username,
Password,
addedOn:new Date().toISOString()
});
//save to db
const res=await newUsr.save();
//web token
const token= jwt.sign({
id:res.id,
email:res.email,
usernaem:res.username
},SECRET_KEY,{expiresIn:'1h'});
return{
...res._doc,
id:res._id,
token
}
}
catch (error) {
throw new Error(error)
}
i have checked throughly through the code ,i can't identify the error.Help identify and correct where possible.
If you are unsure about the handling under the hood.
type User{
id:ID!,
username:String!,
email:String!,
token:String!,
Password:String!,
addedOn:String!,
}
"!" it's mean can not return "null" so make sure your code return a User correctly
Above it's mean you must have to return a User object like this.
return {
id: "some id here",
username: "some username here",
email: "some email here",
token:"token here",
Password:"pass here",
addedOn: "date here",
}
I'm working on an app being developed on Nodejs, Expressjs, Mongoose stack.
I'm building a form on submission of which if there are some validation errors[ want to use only mongoose validation, just to keep validation code at one place] then re-render these error messages back to form and if all fields are valid then send an email to end user if that email is not already available in mongoose.
So this form is very usual form except I'm doing rendering this form by expressjs and also want that enduser should get the info that there are field errors also send an email and notify the end user at the same time that email is in process of being sent, and if email already exists then notify the user instantly on form.
Some psuedocode which can help me to think in Nodejs way would be much appreciated. I'm coming from python/Django stack.
Below is email sender module which returns a promise object.
mailer.js
var path = require('path')
, templatesDir = path.resolve(__dirname, '..', 'email_templates')
, emailTemplates = require('email-templates')
, nodemailer = require('nodemailer')
, rsvp = require('rsvp')
;
var sendEmail = function (template_name, locals) {
return new rsvp.Promise(function(resolve,reject){
emailTemplates(templatesDir, function(err, template) {
if (err) {
return reject({error: err})
} else {
emailFrom = 'an_email_address',
mailer = {
host: 'some_amazon_instance',
port: 25,
service: 'SMTP', // Gmail, SMTP, Zoho
auth: {
user: 'credential',
pass: 'credential'
}
};
var transport = nodemailer.createTransport("SMTP", mailer);
var emailTo = emailFrom;
if (!locals.admin) {
emailTo = locals.email;
}
// Send a single email
template(template_name, locals, function(err, html, text) {
if (err) {
} else {
transport.sendMail({
from: emailFrom,
to: emailTo,
subject: locals.title,
html: html,
text: text
}, function(err, responseStatus) {
if (err) {
transport.close();
return reject({error: err});
} else {
resolve({result: responseStatus.message});
}
transport.close();
});
}
});
}
});
});
}
exports.sendEmail = sendEmail;
Below is the mongoose model to handle form data, validation and email sending.
invite.js
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, sendEmail = require('meanio').loadConfig().sendEmail
, rsvp = require('rsvp')
;
var StudioInviteSchema = new Schema({
name: {
type: String, required: [true, 'name is required']
},
email: {
type: String,
index: true,
unique: true,
required: [true, 'email is required']
},
phone: {
type: Number,
index: true,
unique: true,
required: [true, 'phone is required']
},
address: {
type: String, required: [true, 'address is required']
},
email_template: {type: String, required: true},
title: {type: String, require: true}
});
StudioInviteSchema.path('phone').validate(function (value) {
return parseInt(value)!=NaN && value.toString().length == 10;
}, 'Enter a valid phone number');
StudioInviteSchema.path('email').validate(function (value) {
var re = /^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)| (\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(value)
}, 'Enter a valid email');
StudioInviteSchema.methods = {
saveAndEmailToAdmin: function () {
var instance = this;
console.log(instance, this, 42);
return new rsvp.Promise(function (resolve, reject) {
instance.save(function (err) {
if (err) {
if (err.code == 11000) {
return reject({message: 'duplicate invite'})
}
return reject({message: err})
}
var admin_email_context = {
name: instance.name,
email: instance.email,
phone: instance.phone,
address: instance.address,
title: instance.title,
admin: true
}
var studio_email_context = {
name: instance.name,
message: "We'll contact you shortly",
email: instance.email,
title: 'invite received',
admin: false
}
var admin_email = sendEmail(instance.email_template, admin_email_context)
var studio_email = sendEmail('userinvite', studio_email_context)
rsvp.allSettled([admin_email, studio_email])
.then(function (array) {
console.log(array)
resolve({message: 'success'})
}, function (error) {
resolve({message: 'error'})
})
})
});
}
};
mongoose.model('StudioInvite', StudioInviteSchema);
And finally below is the controller of html view which invoke saveAndEmailToAdmin of invite.js model for form validation and email sending.
invite_controller.js
exports.studioInvite = function (req, res) {
var invite = new StudioInvite({
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
address: req.body.address,
email_template: 'studioinvite',
title: 'Invite request from studio'
});
/*
TODO:
have to use notifications to send messages to end users
like invite is sent successfully or there is some duplicacy
*/
invite.saveAndEmailToAdmin()
.then(function (result) {
console.log(result)
return [ send success messages ]
}, function (error) {
console.log(error);
return [ send error messages]
});
return [ i dont want to return at this line but if email sending takes time then have to return a response ]
}
I've found that i can use blocking code that means i've to put the user on hold untill everything is done on server, also I can use socket.io to send realtime notifications to end users about validation messages and email sent notifications.
But solution doesn't seem good to me.