I actually work on a school project, I have to do a dating website with a micro-framework.
I choose to use ExpressJS to learn NodeJS, but I have some struggles with the asynchronous...
I try to make a profile update form where you have to put you're old password to update your e-mail address or your password or both on one form. But with the asynchronous i don't know how to validate the form.
We can't use ORM, validator or user manager (like passport).
Here is what i tried
First route to the form
router.get('/profile', isConnected, (request, response) => {
response.render('profile.ejs');
});
Second route to validate the form
router.post('/changeCredentials', isConnected, (request, response) => {
var user = User.formJSON(request.session.user);
if (User.hashPassword(request.body.old_password, user.salt) === user.password)
{
if (request.body.email !== undefined && request.body.email !== user.email)
{
request.formValidate('email', 'Email not valid').isNotEmpty().isEmail();
request.formValidate('email', 'Email already used').isUnique(User, () => {
if (request.formIsValid)
{
user.email = request.body.email;
request.user.connect(user);
console.log(request.session);
User.update(user, () => {
request.flash('success', 'Email updated');
});
}
});
}
if (request.body.new_password !== undefined && request.body.new_password !== '')
{
request.formValidate('new_password', 'Password is not valid.').isNotEmpty().isLength(6, 255).isGoodPassword();
request.formValidate('new_password', 'Password does not match').isEqualTo(request.body.new_password_conf);
if (request.formIsValid)
{
user.password = request.body.new_password;
User.update(user, () => {
request.flash('success', 'Password changed!');
}, true);
}
}
}
else
{
request.flash('error', 'Bad Old password');
}
response.redirect('/u/profile');
});
Full code here (you can find this part in 'routes/user.js')
Thanks for your help.
This is a common problem with asynchronous callbacks. You will need to have your response in every possible path because your catch-all redirect ignores asynchronous calls.
router.post('/changeCredentials', isConnected, (request, response) => {
var user = User.formJSON(request.session.user);
if (User.hashPassword(request.body.old_password, user.salt) === user.password)
{
if (request.body.email !== undefined && request.body.email !== user.email)
{
request.formValidate('email', 'Email not valid').isNotEmpty().isEmail();
request.formValidate('email', 'Email already used').isUnique(User, () => {
if (request.formIsValid)
{
user.email = request.body.email;
request.user.connect(user);
console.log(request.session);
User.update(user, () => {
request.flash('success', 'Email updated');
response.redirect('/u/profile');
});
return;
}
response.redirect('/u/profile');
});
return;
}
if (request.body.new_password !== undefined && request.body.new_password !== '')
{
request.formValidate('new_password', 'Password is not valid.').isNotEmpty().isLength(6, 255).isGoodPassword();
request.formValidate('new_password', 'Password does not match').isEqualTo(request.body.new_password_conf);
if (request.formIsValid)
{
user.password = request.body.new_password;
User.update(user, () => {
request.flash('success', 'Password changed!');
response.redirect('/u/profile');
}, true);
return;
}
}
} else {
request.flash('error', 'Bad Old password');
}
response.redirect('/u/profile');
});
The above code show a callback-style approach. A more elegant solution can be created with Promises and generators.
Related
So I am trying to create a token for authentication.
Scenario 1:
When I am accessing my local server directly localhost:3001/login I was able to update/set cookies. Here's a snippet of my code:
app.get('/login', async (req, res) => {
const run_query = (username, password) => {
UserModel.findOne({ username:username }, (e, obj) => {
if(obj === null)
res.send('no user')
else if(e)
res.send(obj)
else {
bcrypt.compare(password, obj.password, function(e, resp) {
if(resp) {
const accessToken = createTokens(obj)
res.cookie(`access-token`, accessToken);
res.send(obj)
}
else
res.send('wrong password')
})
}
})
}
run_query(req.body.username, req.body.password)
})
Scenario 2:
When I am trying to access localhost:3001/login via axios.get on button click (React) I am able to run my query successfully and send the obj data of the user but it is not setting the cookie.
Here's how I access /login via React:
const login = async () => {
try {
const res = await axios.get('http://localhost:3001/login', {
username: username,
password: password
})
const loginTrue = (res) => {
setLogged(true)
console.log(res)
}
res.data === 'wrong password' ? alert('Wrong Password')
: res.data === 'no user' ? alert('Username not Found')
: res.data === 'empty' ? alert('Please Fill up all fields')
: res.data._id !== undefined && res.data._id !== '' ? loginTrue(res.data)
: alert('Error: ' + res.data.Error)
} catch (e) {
console.log(e)
}
}
Question:
Why am I only able to update my cookie when accessing localhost:3001/login directly?
I must've missed something or did something wrong. Thanks!
There has been this thread regarding how cookies themselves work and how Axios is handling them. There are a couple of things that might work depending on your environment so I won't make it a duplicate.
I'm trying to make email verification in my vue.js/express app.
I can create the user and send emails. But showing a message like "verification mail sent" won't work.
The error occurs when executing the code in the then() callback after the execution in DataService.
When registering the following functions are executed:
vuex
const actions = {
registerUser({
commit
}, user) {
commit('registerRequest', user)
return DataService.registerUser(JSON.stringify(user))
// HERE'S THE ERROR
.then(response => {
commit('confirmation', response.message)
setTimeout(() => {
state.status = {
confirmHere: ''
}
}, 4000);
})
.catch(...)
confirmation:
confirmation: (state, msg) => {
state.status = {
confirmHere: msg
}
},
DataService
registerUser(user) {
// Send email for registration
apiClient.post('/user/register/sendMail', user)
.then(res => {
return apiClient.post(`/user/register`, user)
})
.catch(err => {
throw err;
})
},
The sendmail function is using nodemailer to send an email and returns
res.status(200).json({
message: "success"
});
The register function in express is:
router.post('/register', async (req, res) => {
try {
if (req.body.username !== undefined && req.body.password !== undefined) {
let password = await bcrypt.hashSync(req.body.password, saltRounds);
let compareUser = await db.getObject({}, User, 'SELECT * FROM app_users WHERE username=? LIMIT 1', [req.body.username]);
if (compareUser !== undefined) {
res.status(409).json('User already exists');
return;
}
const tmp = {
username: req.body.username,
password: password
};
await db.query('INSERT INTO app_users SET ?', [tmp]);
let user = await db.getObject({}, User, 'SELECT * FROM app_users WHERE username=? LIMIT 1', [req.body.username]);
if (user === undefined)
res.status(500).json('Internal server error');
res.status(201).json({
"message": "Bestätigungs-Email gesendet."
});
} else {
res.sendStatus(400);
}
} catch (error) {
res.sendStatus(500);
}
});
You forgot to return the response from DataService.registerUser
// DataService.js
registerUser(user) {
// Send email for registration
return apiClient.post('/user/register/sendMail', user)
.then(res => {
return apiClient.post(`/user/register`, user)
})
.catch(err => {
throw err;
})
The issue is that your registerUser function doesn't return anything whereas you're expecting it to return a promise.
Change your registerUser to:
registerUser(user) {
// Send email for registration
return apiClient.post('/user/register/sendMail', user)
.then(res => {
return apiClient.post(`/user/register`, user)
})
}
(FYI in the example, I left the .throw out because it already gets handled by the Promise you return ;)
I faced to a little problem which blocks me. I'm working on authentication user service for my app used Node.js. I'm working on a PUT user route and need to compare the old and new password used bcrypt.
Sense adding a comparative try/catch I'm getting the following error:
Error: Can't set headers after they are sent.
app.put(`/users/:email`, checkAuthenticated, envisionDuplicateEmails, async (req, res) => {
const accountEmail = req.params.email
body = req.body
const user = users.find((user) => user.email === accountEmail)
const index = users.indexOf(user)
if (!user) {
res.status(500).send('Account not found.');
} else {
try {
if (await bcrypt.compare(body.password, user.password)) {
body.password = user.password
} else {
const hashedPassword = await bcrypt.hash(body.password, 10)
body.password = hashedPassword
}
} catch (e) {
return res.status(500).send('Internal server error');
}
const updatedAccount = { ...user, ...body }
users[index] = updatedAccount
res.redirect('/')
}
})
utility functions:
function checkAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.redirect('/login')
}
function envisionDuplicateEmails(req, res, next) {
accountEmail = req.params.email
bodyEmail = req.body.email
if (bodyEmail) {
if (bodyEmail != accountEmail) {
checkEmailExist(req, res, next)
}
}
return next()
}
function checkEmailExist(req, res, next) {
const accountEmail = req.body.email
const getAccount = users.find((user) => user.email === accountEmail)
if (getAccount === undefined) {
} else {
return res.status(500).send({ 'message': 'Account email already exist' })
}
return next()
}
Thanks for help :P
You are trying to re-execute the res.status(500) twice.
In your try/catch clause, just add the return keyword like that:
try {
if (await bcrypt.compare(body.password, user.password)) {
body.password = user.password
} else {
const hashedPassword = await bcrypt.hash(body.password, 10)
body.password = hashedPassword
}
} catch (e) {
// I've added the return keyword here
return res.status(500).send('Internal server error');
}
Now, when your try/catch catch an error, the code not continue and stop here.
So, this worked before, and all the sudden decided to stop working, and I have no idea why.
EDIT: Updated the code to show what I currently got now
router.post('/register', async (req, res) => {
// let query
let query;
// Start email checks
req.check('email', 'Email is not valid.')
.isEmail()
.custom(async value => {
query = {email: value};
User.findOne(query).then(user => {
if (user) return false;
});
}).withMessage('Email is in use.');
// Start username checks
req.check('username', 'Username is required.')
.notEmpty()
.isLength({ min: 5, max: 15}).withMessage('Username requires 5-15 alphanumberic characters.')
.isAlphanumeric().withMessage('Username must be alphanumeric only.')
.custom(async value => {
query = {username: value}
User.findOne(query).then(user => {
if (user) return false;
});
}).withMessage('Username is in use.');
// Start password checks
req.check('password', 'Password is required.')
.notEmpty()
.isLength({min: 5}).withMessage('Password must be atleast 5 characters long.');
req.check('confirmPassword', 'Confirm Password is required.')
.notEmpty()
.custom(value => value === req.body.password).withMessage('Password must match');
const errors = await req.getValidationResult();
//console.log(errors);
if (!errors.isEmpty()) {
res.render('index', {
errors: errors.mapped()
});
} else {
let newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password,
});
let hash = bcrypt.hashSync(req.body.password, 10);
newUser.password = hash;
newUser.save(err => {
if (err) {
console.log(err);
} else {
res.render('index', {
success: 'Registration Successful'
});
}
});
}
});
So its pretty clear its something with my custom checks, and I do not know why.
EDIT:
It seems there is confusion. The checks are working correctly, what I'm having issues with is it populating the errors when I want it to. If i try to register with the same email, it will pull up the user and will go through my if statements. If I use Promise.reject() it doesn't work. If I use false, it doesn't work. Again, the checks itself work, the error handling seems like it isn't.
EDIT TWO:
So I have tried this method (all the other code is still the same)
// Start email checks
req.checkBody('email', 'Email is not valid.')
.isEmail()
.custom(value => {
query = {email: value}
User.findOne(query).then(user => {
if (user) console.log('Email Exists'); return false;
});
}).withMessage('Email in use.');
// Start username checks
req.check('username', 'Username is required.')
.notEmpty()
.isLength({ min: 5, max: 15}).withMessage('Username requires 5-15 alphanumberic characters.')
.isAlphanumeric().withMessage('Username must be alphanumeric only.')
.custom(value => {
query = {username: value}
User.findOne(query).then(user => {
if (user) console.log('Username Exists'); return false;
});
}).withMessage('Username in use.');
This should work. Since node.js is non render blocking,the db query may not complete before it proceeds to next step. You could use the format I have posted below or try the async library in which case await keyword should be placed before User.findOne
User.findOne(query).then(user=>{
if(user) return false
}).catch(err => console.log(err))
Finally found an answer, which incidentally makes it a little easier to read as well. I ended up making my own custom validators:
customValidators: {
emailExists: (email) => {
let query = {email: email};
return new Promise((resolve, reject) => {
User.findOne(query, (err, results) => {
if (results === null) {
resolve(err);
}
reject(results);
});
});
},
userNameExists: (username) => {
let query = {username: username};
return new Promise((resolve, reject) => {
User.findOne(query, (err, results) => {
if (results === null) {
resolve(err);
}
reject(results);
});
});
}
},
Then:
req.check('email', 'This email is in use.').emailExists();
req.check('username', 'Username is in use.').userNameExists();
req.asyncValidationErrors().then(() => {
console.log('No errors');
let newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password,
});
let hash = bcrypt.hashSync(req.body.password, 10);
newUser.password = hash;
newUser.save(err => {
if (err) {
console.log(err);
} else {
res.render('index', {
success: 'Registration Successful'
});
}
});
}).catch(errors => {
res.render('index', {
errors: errors
});
});
Error: data and hash arguments required
i am doing simple, login signup and forgot password in node js using
bcrypt hash
code : for login
app.post('/login', (req, res) => {
console.log('login');
let {email, password} = req.body;
User.updateOne({email: email}, ' email password', (err, userData) => {
if (!err) {
let passwordCheck = bcrypt.compareSync(password, userData.password);
if (passwordCheck) {
console.log('login2');
req.session.user = {
email: userData.email,
id: userData._id
};
req.session.user.expires = new Date(Date.now() + 3 * 24 * 3600 * 1000);
res.status(200).send('You are logged in, Welcome!');
} else {
res.status(401).send('incorrect password');
console.log('login3');
}
} else {
res.status(401).send('invalid login credentials');
console.log('login4');
}
});
});
code for signUp :
app.post('/signup', (req, res) => {
let {email, password} = req.body;
let userData = {password: bcrypt.hashSync(password, 5, null), email };
console.log('out save');
let newUser = new User(userData);
newUser.save().then(error => {
if (!error) {
console.log('in save');
return res.status(200).json('signup successful');
} else {
if (error.code === 11000) {
return res.status(409).send('user already exist!');
} else {
console.log(JSON.stringigy(error, null, 2));
return res.status(500).send('error signing up user');
}
}
});
});
i have tried console logging few lines and turned out that the code doesn't go into signup
newUser.save();
tell me where i'm going wrong
The issue is with this line newUser.save().then(error => {. Do you notice the .then(). That is a resolved promise so it wouldn't be returning an error. Typically you would see something like this.
Promise()
.then((result) => {
// result is a resolved promise
})
.catch((error) => {
// error is a rejected promise
})
So you should try changing your code to this:
newUser.save()
.then(result => {
console.log('in save')
return res.status(200).json('signup successful')
})
.catch(error => {
if (error.code === 11000) {
return res.status(409).send('user already exist!')
} else {
console.log(JSON.stringigy(error, null, 2))
return res.status(500).send('error signing up user')
}
})
It looks like you're using mongoose, here is the API docs for Document.prototype.save() https://mongoosejs.com/docs/api.html#document_Document-save
Their documentation uses callback functions for the most part but if you scroll to the end of the .save() documentation you will see they show one example with a promise.
bcrypt.compareSync takes 2 parameters; passwordToCheck, passwordHash
You are getting error "bcrypt Error: data and hash arguments required"
This error means one or both parameters are either null or undefined,
In your case you need to make sure that password, userData.password are correctly going in function bcrypt.compareSync