getting isEmail of undefined error when creating registration system in nodejs - javascript

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.

Related

How to perform user-input validation using if statements

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

Why find() using models won't work in signup route?

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." });
}

Avoid using nested promise for validation

I am using node and mongoDB to create a backend service.
I do have a schema as follows :
const UserSchema = new Schema({
name : {
type : String,
required : true
},
email : {
type : String,
required : true
},
phoneNo : {
type : Number,
required : true
},
password : {
type : String,
required : true
},
avatar : {
type : String
},
date : {
type : Date,
default : Date.now()
}
})
Now I want to validate whether the phone no and email exists or not. For this check I have the following code snippet :
User.findOne({ email : req.body.email })
.then(user => {
if (user){
errors.email = 'Email already exists';
return res.status(400).json(errors);
} else {
User.findOne( {phoneNo : req.body.phoneNo})
.then (user => {
if(user) {
errors.phoneNo = 'Phone no already exists';
return res.status(400).json(errors);
}
else {
.....
So I am using nested promise. I believe this is not a good practice to go on. But I need to show different validation messages for different points. Can this be achievable in any simpler way ?
You could use async/await that will make your code simpler and easier to reason about.
async function main() {
const existingByEmail = await User.findOne({ email: req.body.email });
if (existingByEmail) {
errors.email = "Email already exists";
return res.status(400).json(errors);
}
const existingByPhone = await User.findOne({ phoneNo: req.body.phoneNo });
if (existingByPhone) {
errors.phoneNo = "Phone no already exists";
return res.status(400).json(errors);
}
}
You can set email and phoneNo to be unique (recommended) and check for duplication error handled by mongoose.
email : {
type : String,
required : true,
unique : true // add unique option
},
phoneNo : {
type : Number,
required : true,
unique : true // add unique option
},
This package mongoose-unique-validator will return the field that trigger duplication error. But you can also handle the duplication error by yourself, as suggested here, but you will have to check the index by yourself to see which field caused duplication
Above approaches will handle duplication errors when you insert/update a document, so you won't need your current validation code.
For the purpose of improving your validation flow, if you want to avoid nested Promises, you can call them at the same time using Promise.all()
/* without async/await */
Promise.all([
User.findOne({ email: req.body.email }).exec(),
User.findOne({ phoneNo: req.body.phoneNo }).exec()
]).then(([emailUser, phoneUser]) => {
if (emailUser) {
errors.email = "Email already exists";
return res.status(400).json(errors);
}
if (phoneUser) {
errors.phoneNo = "Phone no already exists";
return res.status(400).json(errors);
}
})
/* with async/await */
const [emailUser, phoneUser] await Promise.all([
User.findOne({ email: req.body.email }).exec(),
User.findOne({ phoneNo: req.body.phoneNo }).exec()
])
if (emailUser) {
errors.email = "Email already exists";
return res.status(400).json(errors);
}
if (phoneUser) {
errors.phoneNo = "Phone no already exists";
return res.status(400).json(errors);
}

why is graphql returning "cannot return null of non-nullable field mutation.createAccnt"

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",
}

Show inline field validation errors along with email sent message to an end user filling up a form

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.

Categories