In my signup.ejs file, I have setup a form with post request and i have written /welcome route in signup.js file, I am trying to figure out how i can stop the post request to the given route if validation fails or how i can dynamically assign a different route like an error route (localhost:3000/signup/error instead of localhost:3000/signup/welcome if validation fails) as everytime signup fails, it is routing to localhost:3000/signup/welcome which is not right. I tried setting up variables in signup.ejs file to pass it in action, however, it only routes to /welcome and it doesn't route to /error. Basically, I just want to see error endpoint or just stop the POST request if validation fails. Please suggest.
signup.ejs
<h2>Sign Up</h2>
<% var route = ''; %>
<% if (!messages.error) { %>
<% route = '/welcome' %>
<% }else{ %>
<% route = '/error' %>
<% } %>
<form action="signup<%- route %>" method="post" name="form1">
signup.js
router.post('/welcome', function(req, res, next) {
req.checkBody('name', 'Name cannot be empty').notEmpty();
req.checkBody('email', 'Please enter valid email').isEmail();
req.checkBody('password', 'Password should be more than 5').isLength({ min: 5 });
req.checkBody('password1', 'Password do not match').equals(req.body.password);
var errors = req.validationErrors();
if(!errors) {
var user = {
name: req.sanitize('name').escape().trim(),
email: req.sanitize('email').escape().trim(),
password: req.sanitize('password').escape().trim()
}
connection.query('Insert into customers set ?', user, function(err, result) {
if(err) {
req.flash('error', err);
//render to signup
res.redirect('/signup', {
title: 'Add New User',
name: user.name,
email: user.email,
password: user.password
})
}
req.flash('success', 'Successfully signed up');
res.render('welcome', {
title: 'Signed In'
});
})
}else {
var error_msg = ''
errors.forEach(function(error) {
error_msg += error.msg + '<br>'
})
req.flash('error', error_msg);
res.render('signup', {
name: req.body.name,
email: req.body.email,
password: req.body.password
})
}
});
Related
So I converted the .hbs template to .ejs template and try to see the difference. I thought converting would be same, but it turns out it doesn't.
My code is working. I can register a user, but no error shows up. The error display is the main problem in my code.
Error-looping of .ejs
This is the .HBS version of Error
{{#if message}}
<h4 class="alert alert-danger mt-4">{{message}}</h4>
{{/if}}
This is the .EJS version of Error
//SignUp.js / SignIn.js
<% if (error) { %>
<h4 class="alert alert-danger mt-4">{{message}}</h4>
<% } %>
Another version of Error in .EJS
<% if (hasErrors) {%>
<div class="alert alert-danger">
<% messages.forEach(function(message){ %>
<p><%= message %></p>
<% });%>
</div>
<% }%>
This is the Controllers folder - auth.js
exports.signin = async (req, res) => {
try {
const {email, password} = req.body;
if(!email || !password) {
return res.status(400).render('shop/signin', {
message: 'Please provide an email and/or password'
});
}
con.query('SELECT * FROM users WHERE email = ?', [email], async (error, results) => {
console.log(results);
if(!results || !(await bcrypt.compare(password, results[0].password))) {
res.status(401).render('shop/signin', {
message: 'Email or Password is incorrect'
});
}
else {
const id = results[0].id;
const token = jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN
});
console.log("The token is: " + token);
const cookieOptions = {
expires: new Date(
Date.now() = process.env.JWT_COOKIE_EXPIRES * 24 * 60 * 60 * 1000
),
httpOnly: true
}
res.cookie('jwt', token, cookieOptions);
res.status(200).redirect("shop/profile");
}
});
}
catch(error) {
console.log(error);
}
}
exports.signup = (req, res) => {
console.log(req.body);
const {name, email, password, passwordConfirm} = req.body;
con.query('SELECT email FROM users WHERE email = ?', [email], async (error, results) => {
if(error) {
console.log(error);
}
if(results.length > 0) {
return res.render('shop/signup', {
message: 'That email is already in use!'
});
}
else if(password !== passwordConfirm) {
return res.render('shop/signup', {
message: 'Passwords do not match!'
});
}
let hashedPassword = await bcrypt.hash(password, 8);
console.log(hashedPassword);
con.query('INSERT INTO users SET ?', {name: name, email: email, password: hashedPassword}, (error, results) => {
if(error) {
console.log(error);
}
else {
console.log(results);
return res.render('shop/signup', {
message: 'User Registered!'
});
}
});
});
}
This is the Routes folder - user.js
router.post('/signup', authController.signup);
router.post('/signin', authController.signin);
module.exports = router;
Lord
You can solve this job in two ways.
1-Local Message
return res.send("<script> alert('Error Message'); window.location = 'shop/signin'; </script>")
2- If you don't want to use local messages Use 'flash' and 'session' packages
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
}
}
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);
..............
});
I started to develop a little web site in NodeJS, with admin authentication based on https://github.com/DanialK/Simple-Authentication, it work very well.
I can create a user, login with it and see the private page (dashboard).
But I have a problem with my template (I'm using Nunjucks), when the admin come to his dashboard I just wan't to show :
logged as : 'admin_username'.
This is my code :
Model :
const UserSchema = new mongoose.Schema({
username: String,
email: String,
password: String,
salt: String,
hash: String
})
Model definition :
const User = mongoose.model('user', UserSchema)
My route :
router.route('/admin/dashboard', requiredAuthentication)
.get((req, res) => {
console.log("################ GET DASHBOARD ##################")
requiredAuthentication(req, res, function() {
User.find().then(user => {
res.render('admin/dashboard.njk', {user: user})
console.log(user)
}).catch(err => {
console.error(err)
})
})
})
So I send "user", should be used in my template :
{% extends "base/layout.njk" %}
{% block content %}
admin:dashboard page <br /><br /><br />
{% if sessionFlash.message %}
<div class="{{ sessionFlash.type }}">
{{ sessionFlash.message }}
</div>
{% endif %}
You are logged as : {{ user.username }}
{% endblock %}
With user.username I can't get the username.
With user only I get the entire document from the DB, username, email, salt, hash.
This is the result of the console.log(user) from the route :
[ { _id: 58c58ad8a5e54c00117ce85b,
username: 'test',
email: 'test',
salt: '/71BBVmr8E3b/HUz8L89IWLV7xM/vG9nvJRGzQYPw4dwR8GICr0kJtGs8dqwNzsMU7yki9aa2WM7C2NRxf/ausw+4kyiLojfugYzdrh+6obBq5HcZPZfQq+djwsTyyd+CDPJ/EmbUQyIL1yM7lRLfkhfrCIZ9P1mJZZM9fv4thw=',
hash: '��F\u0000\u000b ��a�\u001c|A˓P�N&��\u0010�5ajd�7{c �#�mQ����&��W�rW\'�+������\u0013����������N�4�y>/1��R\u001ca>���=U�u<9�T�o" \u000b�����Ʌ^�\u0004\u001f��\u0007�B`A�d���N#M$���',
__v: 0 } ]
Don't know if this is important, there is the two function used for authentication : requiredAuthentication and authenticate :
function authenticate(name, pass, fn) {
if (!module.parent) console.log('authenticating %s:%s', name, pass);
User.findOne({
username: name
},
function (err, user) {
if (user) {
if (err) return fn(new Error('cannot find user'));
hash(pass, user.salt, function (err, hash) {
if (err) return fn(err);
if (hash == user.hash) return fn(null, user);
fn(new Error('invalid password'));
});
} else {
return fn(new Error('cannot find user'));
}
});
}
function requiredAuthentication(req, res, next) {
console.log("#### ->REQUIREDAUTHENTICATION() ####")
if (req.session.user) {
console.log("#### AUTH NEXT() ####")
next();
} else {
console.log("#### AUTH DIE() ####")
req.session.sessionFlash = {
type: 'alert alert-success',
message: 'You can't access the dashboard'
}
res.redirect('/admin/account/login');
}
}
Thanks for helping me, if you wan't additional informations ask me.
Looks like that user object is actually an array with 1 item. You can tell by that leading [ in the console.log output. To fix the issue, you can either pass in user[0] to your render function, or add a for loop to your template in case you'll be grabbing multiple users later on.
I have two schemas, one for user and one for friendship system (follow system)
module.exports = mongoose.model('User', {
email: {
type: String,
unique: true,
lowercase: true
},
password:String,
profile: {
fullname: String,
gender: String,
role: {type: String, default: 'Autorizado'},
country: String },
});
and this for friend system
module.exports = mongoose.model('Friendship', {
follower: String,
followed: String
});
I'm using passport to auth my users... the problem is that I want lo load the user information and the friend information related to the user globally using res.locals I make this happen using 2 functions in login post route....
router.post('/login', userController.postLogin,friendController.getFollow);
this is my postlogin function
exports.postLogin = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) return next(err);
if (!user) {
req.flash('errors', { msg: info.message });
return res.redirect('/login');
}
req.logIn(user, function(err) {
if (err) return next(err);
req.flash('success', { msg: 'Success! You are logged in.' });
res.redirect('/');
});
})
(req, res, next);
};
When I try to execute this code everything goes fine but when I log in I get the header error please how can I solved this or other way to get the user and follow information and use it in every view?.
EDIT 3: only works in the next two views
Single User page
<%= info.profile.fullname %>
<br>
<%= info.email %>
<br>
<%= info.profile.country %>
<br>
<%= user.follow %>
<br>
<% if(user && (user._id != info.id)){ %>
<br>
<form action="/follow/<%= info._id %>" method="post">
<button type="submit">Follow</button></form>
<%}%>
<br>
atras
List of Registered User
<%if (user) {%>
<h1>Hola <%= user.profile.fullname %> </h1>
<%}%>
<br>
<% users.forEach(function (user) {%>
<p><%= user.profile.fullname %></p>
<%});%>
Home
This is my index and in this view the user.follow doesn't work
<%if (!user) {%>
sign up
<br>
login
<%} else { %>
<h1>Hola <%= user.profile.fullname %> Eres lo Maximo</h1>
perfil
<br>
Logout
<br>
<%}%>
<br>
Lista de Usuarios
EDIT 4: new deserializeUser config.
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
FF.find({ follower:id }, function(err, follow) {
user.follow = follow;
});
done(err, user);
});
});
EDIT 5: This is my route code
router.get('/', function(req, res) {
res.render('index');
});
router.get('/login', userController.getLogin);
router.post('/login', userController.postLogin);
router.get('/logout', userController.logout);
router.get('/signup', userController.getSignup);
router.post('/signup', userController.postSignup);
router.get('/profile', passportConf.isAuthenticated, userController.getAccount);
router.post('/profile', passportConf.isAuthenticated, userController.postUpdateProfile);
You had friendController.getFollow which would've worked only for the request of '/login' page, so the follow object wouldn't have been persistently available on other routes. To fix that you had to put all that code inside the passport.deserializeUser function.
Inside the passport.deserializeUser function you could only call done once so you had to put the logic to find follow inside the callback of User.find so that you could attach follow to user.
This allowed you to remove the friendController.getFollow from your '/login' route which I think was the cause of your error.