How do I output validation errors to the view for the email unique: true option? In my handlebars view I am getting errors passed from var errors = req.validationErrors(); and displaying it in the view which is working. but the email validation for uniqueness is going to user.save((err) and is just being sent to the console. The validation is working correctly, no user is created if there is a duplicate email. I'm just trying to send the error message to the view. Can I do a req.check for unique: true ?
const UserSchema = new Schema({
email: { type: String, index: { unique: true } },
password: { type: String, required: true }
});
my createUser.handlebars view
{{#if errors}}
<section class="errors">
<ul>
{{#each errors}}
<li>{{this.msg}}</li>
{{/each}}
</ul>
</section>
{{/if}}
the createUser function
module.exports.createUser =
(req, res, next) => {
let user = new User({
email: req.body.email,
password: req.body.password
});
req.check('email', 'Invalid email').isEmail();
req.check('password', "Passwords must match").isLength({min: 6}).equals(req.body.passwordConfirmation);
var errors = req.validationErrors(); // save any error messages
// check if there are any errors
if (errors) {
req.session.errors = errors;
req.session.success = false;
return res.redirect('/users/new');
} else {
req.session.success = true;
}
// save the user
user.save((err) => {
if (err) {
console.log("Error : %s ", err);
}
// set the session messages back to null and redirect
req.session.success = null;
req.session.errors = null;
res.redirect('/');
});
};
If I'm not wrong, you're using express-validator for validation. The issue is you're mixing two things: form validation and database constraints.
Uniqueness is not an intrinsic property of a data item and it cannot be checked independently of the entire set.
You can only check for uniqueness using a call to the database. If your database schema defines this attribute/column as unique, then trying to store it will throw an error (at least in SQL-based databases and MongoDB).
You can create your custom validation check like this
check('email').custom(value => {
return userController.findByEmail(value).then( user => {
if(user){
return Promise.reject('this email already exists')
}
})
})
i write this in Router and define function in Controller
async findByEmail(email){
const candidate = await User.findOne({ where: {username}})
if(candidate){
return true
}else{
return false
}
}
Related
In my Node.js/MERN app, I get error 250 2.0.0 OK 1590267554 o18sm275551eje.40 - gsmtp & something went wrong when I register my user for authentication and receive an email with EMPTY body. I am using the code from https://blog.bitsrc.io/email-confirmation-with-react-257e5d9de725
I can see user is added to mongodb database with confirmed set to false. Why am I not getting the complete email with confirmation?
Please find my attached code for my MERN application. I would really appreciate a reply! Thank you!
Register
Register route in users which takes you to login and on React side OnSubmit starts chain of sending and confirming email.
router.post("/register", type, function (req, res, next) {
// var tmp_path = req.file.path;
if(!req.file){
console.log("File missing");
}
/** The original name of the uploaded file
stored in the variable "originalname". **/
// var target_path = 'uploads/' + req.file.originalname;
// /** A better way to copy the uploaded file. **/
// var src = fs.createReadStream(tmp_path);
// var dest = fs.createWriteStream(target_path);
// src.pipe(dest);
// fs.unlink(tmp_path);
// src.on('end', function() { res.render('complete'); });
// src.on('error', function(err) { res.render('error'); });
// Form validation
const { errors, isValid } = validateRegisterInput(req.body);
const url = req.protocol + '://' + req.get('host')
// Check validation
if (!isValid) {
return res.status(400).json(errors);
}
//Checks email against registered emails in database
registeredemails.findOne({ email: req.body.email}).select("email").lean().then(result => {
if (!result) {
return res.status(400).json({email: "Email not provided"});
}
});
User.findOne({ email: req.body.email }).then(user =>
{
if (user) {return res.status(400).json({ email: "Email already exists" })
}
else if(!user){
const newUser = new User({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
fileimg: url + '/public/' + req.file.filename
});
// // Hash password before saving in database
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(newUser =>
sendEmail(newUser.email),
templates.confirm(newUser._id)
)
.then(() => res.json({ msg: msgs.confirm }))
.catch(err => console.log(err))
}
)}
)}
else if (user && !user.confirmed) {
sendEmail(user.email, templates.confirm(user._id))
.then(() => res.json({ msg: msgs.resend })).catch(err => console.log(err))
}
// The user has already confirmed this email address
else {
res.json({ msg: msgs.alreadyConfirmed })
}
}).catch(err => console.log(err))
sendemail as used in the MEDIUM articles
const nodemailer = require('nodemailer');
const { CLIENT_ORIGIN } = require('../../config')
// const mg = require('nodemailer-mailgun-transport');
// The credentials for the email account you want to send mail from.
const credentials = {
secure: true,
service: 'Gmail',
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS
// These environment variables will be pulled from the .env file
// apiKey: 'b61286bf9e28b149fac32220f0c7349f-e5e67e3e-00a38515',
// domain: 'sandbox0b8a7f0ebcc74c0d8161304f24909bd2.mailgun.org'
}
}
// Getting Nodemailer all setup with the credentials for when the 'sendEmail()'
// function is called.
const transporter = nodemailer.createTransport(credentials)
// exporting an 'async' function here allows 'await' to be used
// as the return value of this function.
module.exports = async (to, content) => {
// The from and to addresses for the email that is about to be sent.
const contacts = {
from: process.env.MAIL_USER,
to // An array if you have multiple recipients.
// subject: 'React Confirm Email',
// html: `
// <a href='${CLIENT_ORIGIN}/confirm/${id}'>
// click to confirm email
// </a>
// `,
// text: `Copy and paste this link: ${CLIENT_ORIGIN}/confirm/${id}`
}
// Combining the content and contacts into a single object that can
// be passed to Nodemailer.
const email = Object.assign({}, content, contacts)
// This file is imported into the controller as 'sendEmail'. Because
// 'transporter.sendMail()' below returns a promise we can write code like this
// in the contoller when we are using the sendEmail() function.
//
// sendEmail()
// .then(() => doSomethingElse())
//
// If you are running into errors getting Nodemailer working, wrap the following
// line in a try/catch. Most likely is not loading the credentials properly in
// the .env file or failing to allow unsafe apps in your gmail settings.
await transporter.sendMail(email, function(error, info){
if(error)
{
return console.log(error);
}
else
{
return console.log(info.response);
}
})
}
templates.confirm as used in Medium article
onst { CLIENT_ORIGIN } = require('../../config')
// This file is exporting an Object with a single key/value pair.
// However, because this is not a part of the logic of the application
// it makes sense to abstract it to another file. Plus, it is now easily
// extensible if the application needs to send different email templates
// (eg. unsubscribe) in the future.
module.exports = {
confirm: id => ({
subject: 'React Confirm Email',
html: `
<a href='${CLIENT_ORIGIN}/confirm/${id}'>
click to confirm email
</a>
`,
text: `Copy and paste this link: ${CLIENT_ORIGIN}/confirm/${id}`
})
}
currently I'm trying to create a new user in my mongodb database. The user also has to submit his email.
My model looks like this:
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
email: {
type: String,
required: true,
unique: true,
match: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
}
This is just the important part of it because there I state, that mail has to match this specific charset.
Now my problem: If I update the email with the PATCH method
router.patch('/user', checkAuth, async (req, res, next) => {
try {
const id = req.body._id;
const update = req.body;
const options = { new: true };
const result = await User.findByIdAndUpdate(id, update, options);
if (!result) {
throw createError(404, 'User does not exist');
}
res.send(result);
} catch (error) {
console.log(error.message);
if (error instanceof mongoose.CastError) {
return next(createError(400, 'Invalid User Id'));
}
next(error);
}
})
The user can enter whatever he wants into the email field without it being checked for the charset. Do you have any ideas how to fix it?
Thanks in advance, Tom
If you are asking for pattern-matching in HTML, then you can validate your email address using this <input> tag:
<input pattern="/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/" required />
Check ValidatorJS to validate at an API level.
I'm trying to create a basic register/sign up web app with Express, Vue.js and MongoDB.
I have written some backend validation such as checking the all the fields are filled in and the passwords match etc... and then push a string of the error message into an array if the user fails the validation.
If a user tries to sign up on the front end and fails some part of the validation the user is not inserted into the database but to message is displayed as to why and that what I'm having trouble with.
router.post("/register", (req, res) => {
const name = req.body.name;
const email = req.body.email;
const password = req.body.password;
const password2 = req.body.password2;
let errors = [];
// check required fields
if (!name || !email || !password || !password2) {
errors.push({ msg: "please fill in all fields" });
}
// check for errors
if (password !== password2) {
errors.push({ msg: "Passwords do not match" });
}
// password validation
if (password.length < 6) {
errors.push({ msg: "Password to short" });
}
if (errors.length > 0) {
// if there are errors redirect
res.redirect("/");
console.log(errors);
} else {
// res.send("pass");
// check if the user already exists:
User.findOne({ email: email }).then(user => {
if (user) {
// User exists
// if you have found one user
// then user exists and send to home page
errors.push({ msg: "Email is already registered" });
res.redirect("/");
console.log(errors);
} else {
// create new user by using the keyword 'new'
// name is equal to const name = req.body.name; etc...
const newUser = new User({
name: name,
email: email,
password: password
});
console.log(newUser + " hi new user");
// Hash Password before insert into db
bcrypt.genSalt(10, (err, salt) =>
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
// set PS to hashed PS
newUser.password = hash;
// save user
// insert into db then redirect to login
newUser
.save()
.then(user => {
res.redirect("/login");
})
.catch(err => {
console.log(err);
});
})
);
}
});
}
});
Vue:
name: "RegisterForm",
// data is a function that
// returns an object
data: () => ({
errorMessage: "",
user: {
name: "",
email: "",
password: "",
password2: ""
}
}),
// watch allows you to run a function
// any time data changes
watch: {
user: {
handler() {
this.errorMessage;
},
// deep means anytime data changes this watch will run
deep: true
}
},
methods: {
register() {
// clear error message on register
this.errorMessage = "";
// use keyword 'this' to refer to the current object 'methods'
if (this.validUser()) {
// send data to server
axios({
method: "post",
url: "http://localhost:4000/register",
data: {
name: this.user.name,
email: this.user.email,
password: this.user.password,
password2: this.user.password2
}
})
.then(response => {
console.error(response)
})
.catch(error => {
console.error(error)
});
}
},
validUser() {
// if (this.user.password !== this.user.password2) {
// this.errorMessage = "Passwords must match";
// return false;
// } else {
return true;
// }
// }
}
}
};
There is some frontend validation that works (by doing this.errorMessage = "Passwords must match or some error";) and shows a message but I need to show the errors from "let errors = []"
At the moment I'm currently getting a 'ReferenceError: error is not defined' error in the console when typing in invalid data such as two none matching passwords.
Use express-flash-message for save those on redirect. This plugins also expose a local variable for render those message. See documentation.
I have a page index.js which has a form to add users, and beside it a list of users in the database.
/routes/index.js
var express = require('express');
var router = express.Router();
var User = require('../schemas/user');
router.post('/create', function(req, res, next) {
var user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password
});
user.save(function(err) {
if (err) {
console.log('user save error ' + err.errmsg);
return res.json(err.errmsg);
}
res.redirect('/');
});
});
/* GET home page. */
router.get('/', function(req, res, next) {
User.find(function (err, users) {
if (err) {
console.log('get error ' + err);
//return res.sendStatus(500);
}
res.render(
'index',
{
userList : users
}
);
});
});
module.exports = router;
/schemas/user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
group: String,
created_at: Date,
updated_at: Date
});
var User = mongoose.model(
'User',
userSchema
);
module.exports = User;
Here is my view:
/views/index.pug
extends layout
block content
h1= title
p Welcome to #{title}
.container
.row
.col-sm
h1 Create User
form(
method='POST'
action='/create'
)
.form-group
label(for='username') Username:
input#username.form-control(
type='text',
placeholder='Enter username...',
name='username'
)
if usernameError
p.error= usernameError
.form-group
label(for='password') Password:
input#password.form-control(
type='password',
placeholder='Enter password...',
name='password'
)
if passwordError
p.error= passwordError
.form-group
label(for='email') Email:
input#email.form-control(
type='email',
placeholder='Enter email...',
name='email'
)
if emailError
p.error= emailError
button.btn.btn-primary(
type='submit',
) Submit
.col-sm
h2 User List
ul
each user in userList
li= user.username
As you can see, I have some conditionals in my index.pug file. What I want to do is if an error occurs I want to assign a message to a variable based on the error type (i.e. username already taken, or password too short) and pass that variable over to my pug view. The view will then render the message if the proper variable is set. Can somebody help me out? I'm mostly struggling with the fact that I also have to render the list of users, if I try to find users within the error catching part of the post node complains about headers already being set, i.e. If I have my router.post function like so:
router.post('/create', function(req, res, next) {
var user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password
});
user.save(function(err) {
if (err) {
console.log('user save error ' + err.errmsg);
User.find(function (err2, users) {
if (err2) {
console.log('get error ' + err2);
//return res.sendStatus(500);
}
res.render(
'index',
{
userList : users,
usernameError: err.errmsg
}
);
});
}
res.redirect('/');
});
});
Then I expect to see the usernameError message filled in my view but instead I get an error from the node server:
user save error E11000 duplicate key error collection: test.users index: username_1 dup key: { : "John" }
POST /create 302 71.995 ms - 46
Error: Can't set headers after they are sent.
username: { type: String, required: true, unique: true },
it's because unique true.
Error: Can't set headers after they are sent.
Error because you haven't return error. Whenever error occure simply return like
if (err) return next(err)
provided that you are using express centralized error handler
check last lines of your app.js.All errors from next(err) goes here
app.use(function(err, req, res, next) {
console.error( err);
..............
});
Below I have a very basic post request to create a user via REST API. Currently if I have a place where an error state happens I am updating the response object and returning the function. My question is there a better way to do this? Or am I on the right track?
app.post('/api/user/create', function (request, response)
{
// Create variables foreach input.
var firstName, lastName, emailAddress, password, passwordConfirm, User;
firstName = validator.trim(request.body.firstName);
lastName = validator.trim(request.body.lastName);
emailAddress = validator.trim(request.body.emailAddress);
password = validator.trim(request.body.password);
passwordConfirm = validator.trim(request.body.passwordConfirm);
if(!validator.isEmail(emailAddress))
{
response.status(500).send({error: true, message: "ERROR_INVALID_EMAIL"});
return;
}
if(password != passwordConfirm)
{
response.status(500).send({error: true, message: "ERROR_INPUT_COMPARATOR_PASSWORD_INVALID"});
return;
}
User = db.User;
User.create({
firstName: firstName,
lastName: lastName,
emailAddress: emailAddress,
password: passwd.crypt(password)
}).then(function()
{
response.status(200).send({error: false, message: "SUCCESS_USER_CREATED"});
}).error(function(error)
{
console.log(error);
response.status(500).send({error: true, message: error.errors});
});
});
You can create an error middleware and just pass the Error instance to next callback:
if(!validator.isEmail(emailAddress))
{
var error = new Error();
error.message = "ERROR_INVALID_EMAIL";
return next(error);
}
Register the middleware after all the routes:
app.use(function (err, req, res, next) {
return res.status(500).send({error: true, message: err.message});
});