Cannot read property 'error' of undefined. What's the problem? - node.js

My REST API reasons this: {"error":"Auth failed. User does not exist"}.
I try save this error to my state using setState in React, but I have a this error: Cannot read property 'error' of undefined. What's the problem?
export const login = user =>
axios
.post('http://localhost:3000/api/users/login', {
login: user.login,
password: user.password,
})
.then(res => {
localStorage.setItem('userToken', res.data.token);
return res.data.token;
})
.catch(err => {
console.log(err);
});
Function in React.js:
onSubmit(e) {
e.preventDefault();
const user = {
login: this.state.login,
password: this.state.password,
};
login(user).then(res => {
if (!res.error) { // <- this error: TypeError: Cannot read property 'error' of undefined
this.props.history.push(`/dashboard`);
} else {
this.setState({ error: res.error });
}
});
}
This is my backend code:
// Login Action
...
return res.status(200).json({
message: 'Auth successful',
token,
});
}
res
.status(400)
.json({ error: 'Auth failed. The password is incorrect.' });
} else {
res.status(400).json({ error: 'Auth failed. User does not exist' });
}
})
.catch(err => {
res.status(400).json({ error: err });
});
};

Try this:
login(user).then(() => this.props.history.push(`/dashboard`))
.catch(error=>this.setState({ error })
But maybe there is another problem, you cannot normally push to state the way you do for immutability concern. I guess you know, but I post in case:
this.setState({ history: [...this.state.history, `/dashboard`] })

since your backend is returning a response with 400 status code, you have to handle that in the catch block of your login function. right now you are writing the error into the console and not returning anything, that is why your login response is undefined in your React code and you are getting that error.
to fix this, change the catch block of your login function so it looks something like this
.catch(err => {
console.log(err);
return {error:err};
});

Try this;
export const login = user =>
axios
.post('http://localhost:3000/api/users/login', {
login: user.login,
password: user.password,
})
.then(res => {
localStorage.setItem('userToken', res.data.token);
})
.then(() => return localStorage.getItem('userToken');)
.catch(err => {
console.log(err);
});

Related

How to switch back to async/await?

I am new to async/await so I have an exercise like below code, I have converted this code to async/await many times and still no success. Please help me. Thanks very much!
My code is as follows:
exports.register = (req, res) => {
const user = req.body;
try {
// Validate the registration form
validateRegisterForm(user)
.then((response) => {
// If response is true, hash the password
if (response) {
Md5Password(user.password)
.then(async (hash) => {
const { name, email } = user;
const newUser = new User({
name,
password: hash,
});
// Save the user
const savedUser = await newUser.save();
res.status(200).json(savedUser);
})
.catch((error) => {
res.status(500).json({
message: error.message,
err: "500: Internal Server Error",
});
});
}
// But if response is false, show the error message
else {
res.status(401).json({
message: errorMessage(),
error: "401: Unauthorized",
});
}
})
.catch((error) => {
res.status(500).json({
message: error.message,
err: "500: Internal Server Error",
});
});
} catch (error) {
res.status(500).json({
error: error.message,
message: "registration failed",
e: "500: Internal Server Error",
});
}
};
Please help me, thanks a lot!
Not sure exactly what you're trying to achieve, but here's a version of your code with async/await:
exports.register = async (req, res) => {
const user = req.body;
try {
// Validate the registration form
const response = await validateRegisterForm(user);
// If response is true, hash the password
if (response) {
const hash = await Md5Password(user.password);
const { name, email } = user;
const newUser = new User({
name,
password: hash,
});
// Save the user
const savedUser = await newUser.save();
res.status(200).json(savedUser);
} else {
res.status(401).json({
message: errorMessage(),
error: "401: Unauthorized"
});
}
} catch (e) {
res.status(500).json({
message: e.message,
err: "500: Internal Server Error"
});
}
}

How to get error object on client side from node.js backend?

I'm having a little issue. I'm developing an app and I created API with node.js(using express). Right now I'm trying to send my error objects from node to react but for some reason I cannot get it. I can see the object in the network tab but I want to use it, like console it to the client.
back-end:
app.post('/api/users/login', async (req, res) => {
try {
const user = await User.findByCredentials({ ...req.body });
const token = await user.generateAuthToken();
res
.cookie('w_auth', token)
.status(200)
.send({ user, token });
} catch (error) {
res.status(400).send({ success: false, error: 'some error' });
}
});
client-side:
loginUser:
export const loginUser = dataToSubmit => {
return axios.post(`${USER_SERVER}/login`, dataToSubmit);
};
loginUser(dataToSubmit)
.then(res => {
console.log(res);
dispatch({ type: 'SET_USER', user: res.data.user });
})
.catch(error => {
console.log(error);
});
I tried also just send a respond without error from back-end which also didn't work.
picture of what I get:
network tab:
You can catch it the below way.
axios
.post(url, data)
.then(response => {
//You get success response here.
})
.catch(err => {
//Error response here
});

Getting 400 Bad Request despite catching the err in angular2+

I have designed a login page where login is successful when i put a correct login and password and Login doesn't happen when I put an incorrect username or password which is correct. However, I get this error:
POST http://localhost:3003/login/authenticate 400 (Bad Request)
ERROR HttpErrorResponse {headers: HttpHeaders, status: 400, statusText: "Bad Request", url: "http://localhost:3003/login/authenticate", ok: false, …}
Everything works properly however, I get the error in the console. Like this:
I want the 400 bad request error to not appear in the console. How to do that?
login.component.ts
login(data) {
console.log("Inside Login");
this.authenticateObj = {
username: data.username,
password: data.password
}
this.http.post("http://localhost:3003/login/authenticate",
this.authenticateObj)
.map(Response => Response)
.catch((err) => {
console.log("err =", err)
alert('Login Failed. Username or Password
is incorrect');
return Observable.throw(err);
})
.subscribe((res: Response) => {
console.log("Inside login authenticate subscribe");
this.info = res;
if (this.info.message == 'Login Successful.') {
console.log("test after login = ", this.info);
if (localStorage.getItem('username') &&
localStorage.getItem('token')) {
alert('Login Successful');
this.router.navigate(['/file-upload/wsdl']);
} else {
this.notification.Error('Unauthorized');
}
}
if (this.info.message == 'error') {
alert('Login Failed');
}
else if (this.info.status == 400) {
alert('Login Failed');
}
})
}
login.controller.js
function authenticateUser(req, res, next) {
console.log("Inside authenticateUser = ", req.body)
LoginService.authenticate(req,req.body)
.then(function (token) {
if (token) {
res.setHeader("authorization",token.token);
res.send({
message: 'Login Successful.',
response: token
});
} else if(res.message == 'Username or Password is
incorrect'){
res.status(401).send({
message: 'Unauthorized. '
});
}
else {
console.log("inside controller, else res.status-400");
res.status(400).send({
message: 'Username or password is incorrect'
});
}
})
.catch(function (err) {
console.log("inside controller, catch res.status 400")
// res.status(400).send(err);
res.status(400).send({
message: 'Username or password is incorrect'
});
});
}
In order to handle errors from server properly, you have to catch them in the subcribe() method of the Observable returned by http.post from Rxjs:
this.http.post("http://localhost:3003/login/authenticate", this.authenticateObj)
.subscribe(
(res: Response) => {
// code when no error...
},
err => {
// error handling...
},
() => {
// finally...
}
);
IMO Bad request is an incorrect response by your server for incorrect username/password combination. You can return a "401" or a "200" itself depending on your requirement.
Now if you want the error not to appear in the console then add an error callback in your subscribe().
this.http.post("http://localhost:3003/login/authenticate", this.authenticateObj)
...
// rest of the code
.subscribe((res: Response) => {
// your code
}, (error) => {
// handle the error here, show some alerts, warnings, etc
console.log(error)
})

Node.js UnhandledPromiseRejectionWarning: Error: Can't set headers after they are sent

I'm new to node.js and want to send out dataof User and Match in a single response by querying mongodb twice .
router.get('/preview/', checkAuth, (req, res)=> {
const errors = {};
const match = {}
User.findOne({_id: req.user.id})
.then(user => {
if (!user) {
return res.status(404).json({errors: 'Could not find a user' });
}
Match.findOne({ user: req.user.id }).then(m => {
console.log('match found!');
match = m;
}).catch(err=> res.status(404).json(err)); // <-error occures here
res.status(200).json({user, match});
})
.catch(err=> res.status(404).json(err));
});
But I get this error:
(node:8056) UnhandledPromiseRejectionWarning: Error: Can't set headers after they are sent.
How can I fix it?
Please have a look at the comments added in your code.
router.get('/preview/', checkAuth, (req, res)=> {
const errors = {};
const match = {}
User.findOne({_id: req.user.id})
.then(user => {
if (!user) {
return res.status(404).json({errors: 'Could not find a user' });
}
Match.findOne({ user: req.user.id }).then(m => {
console.log('match found!');
match = m;
}).catch(err=> res.status(404).json(err)); // <-error occures here because you sent the response if error occurs
res.status(200).json({user, match}); // this will be executed even if there is an error so it will again try to send the response
})
.catch(err=> res.status(404).json(err));
});
Improved code:
router.get('/preview/', checkAuth, (req, res) => {
const errors = {};
User.findOne({ _id: req.user.id })
.then((user) => {
if (!user) {
return res.status(404).json({ errors: 'Could not find a user' });
}
Match.findOne({ user: req.user.id })
.then((m) => {
console.log('match found!');
res.status(200).json({ user, m }); // send the success response when the match found
})
.catch((err) => res.status(404).json(err)); // send the error response when erro thrown
})
.catch((err) => res.status(404).json(err));
});

Keep getting "Can't set headers after they are sent" using Node/Express

I keep getting "Can't set headers after they are sent" building a Node/Express API.
The issue is I am not setting the headers after the response has been sent anywhere. I am always calling res.status(xxx).json({}) to close ever condition.
Route
const router = require('express').Router();
router.get('/password/validate/:hash', PasswordController.validate);
router.post('/password/update', PasswordController.update);
Controller
This is where the error is occurring. I am calling the validate request specifically.
// Import node packages
const mongoose = require('mongoose');
const Password = require('../models/password');
const User = require('../models/user');
const bcrypt = require('bcryptjs');
const moment = require('moment');
const string = require('../middleware/string_functions')
exports.update = (req, res, next) => {
User.findOne({ email: req.body.email })
.exec()
.then(user => {
if (!user) {
res.status(401).json({
message: 'Cannot retrieve account'
})
}
const expiry = moment().add(30, 'seconds');
const unique_string = string.generate_random(32);
const url_hash = string.base64_encode(unique_string +':'+ user._id);
bcrypt.hash(unique_string, 10, (err, hash) => {
if (err) {
res.status(500).json({
error: err.message
})
}
const query = { user_id: user._id }
const newData = {
hash,
expiry
}
Password.findOneAndUpdate(query, newData, { upsert: true, new: true })
.exec()
.then(request => {
res.status(201).json({
message: 'success',
url: 'localhost:8081/users/password/validate/' + url_hash,
data: request
})
})
.catch(err => {
res.status(500).json({
error: err.message
})
})
})
})
.catch(err => {
res.status(500).json({
error: err.message
})
})
}
exports.validate = (req, res, next) => {
if (!req.params.hash) {
res.status(500).json({
error: 'Missing hash'
})
}
const data = string.base64_decode(req.params.hash).split(':');
console.log(data)
Password.findOne({ user_id: data[1] })
.exec()
.then(request => {
if (!request) {
res.status(404).json({
message: 'Change request not found or expired'
})
}
bcrypt.compare( data[0], request.hash, (err, result) => {
if (err) {
res.status(500).json({
error: err.message
})
}
if (result) {
if (moment().isAfter(request.expiry)) {
res.status(401).json({
message: 'Time has expired'
})
}
res.status(200).json({
message: 'Hash validation successful'
})
}
res.status(500).json({
error: 'Something went wrong'
})
})
})
.catch(err => {
res.status(500).json({
error: err.message
})
})
}
Console Error
_http_outgoing.js:494
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
at validateHeader (_http_outgoing.js:494:11)
at ServerResponse.setHeader (_http_outgoing.js:501:3)
at ServerResponse.header (/Users/chrislloyd/Development/Projects/happy-hour-api/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/Users/chrislloyd/Development/Projects/happy-hour-api/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/Users/chrislloyd/Development/Projects/happy-hour-api/node_modules/express/lib/response.js:267:15)
at bcrypt.compare (/Users/chrislloyd/Development/Projects/happy-hour-api/api/controllers/passwords.js:83:22)
at /Users/chrislloyd/Development/Projects/happy-hour-api/node_modules/bcryptjs/dist/bcrypt.js:297:21
at /Users/chrislloyd/Development/Projects/happy-hour-api/node_modules/bcryptjs/dist/bcrypt.js:1353:21
at Immediate.next [as _onImmediate] (/Users/chrislloyd/Development/Projects/happy-hour-api/node_modules/bcryptjs/dist/bcrypt.js:1233:21)
at runCallback (timers.js:789:20)
at tryOnImmediate (timers.js:751:5)
at processImmediate [as _immediateCallback] (timers.js:722:5)
Updated Example
exports.update = (req, res, next) => {
// Check if hash value exists
if (!req.params.hash) {
res.status(500).json({
error: 'Missing hash value'
});
return;
}
// Check if password and confirmation are the same
if (req.body.password != req.body.passwordConfirmation) {
res.status(401).json({
message: 'Password confirmation does not match'
});
return;
}
// Decode and split hash and user id into array
const data = string.base64_decode(req.params.hash).split(':');
// Find record that contains user id
Password.findOne({ user_id: data[1] })
.exec()
.then(request => {
console.log(request)
// Throw 404 error if record is not found
if (!request) {
return res.status(404).json({
message: 'Password change request doest not exist or timed out'
});
}
// Check if change request has expired
if (moment().isAfter(request.expiry)) {
res.status(401).json({
message: 'Password change request expired',
request: {
request: 'http://localhost:3001/users/password/request'
}
});
// Delete expired record
Password.remove({ _id: request._id })
.exec()
.catch(err => {
res.status(500).json({
error: err.message
});
});
return;
}
// Compare hash value from encoded string to encrypted hash value in database
console.log(mongoose.Types.ObjectId(request.user_id))
bcrypt.compare( data[0], request.hash, (err, result) => {
// Bcrypt error performing comparison
if (err) {
res.status(500).json({
error: err.message
});
return;
}
// Check if result is true
if (result) {
// Find user record matching request.user_id and update password
User.findOneAndUpdate({ _id: mongoose.Types.ObjectId(request.user_id) }, {$set: { password: req.body.password }}, {new: true}, (err, user) => {
console.log(user)
// Error finding and updating user record
if (err) {
res.status(500).json({
error: err.message
});
return;
}
// If returned user account is not null
if (user) {
res.status(200).json({
message: 'Password updated',
user
});
return;
}
// Could not find user record
res.status(404).json({
message: 'Could not find user account to update'
});
return;
})
}
// Catch all error
res.status(500).json({
error: 'Something went wrong'
});
return;
})
})
.catch(err => {
res.status(500).json({
error: err.message
});
return;
});
}
That particular error is caused when you send multiple responses to the same request.
You see to be thinking that as soon as you do res.status(...).json(...) that your function returns and stops executing. It does not. res.json() is just a regular function call. It doesn't change the control flow in your function at all (unless it throws an exception). A successful call to res.json() executes and then your function just keeps right on executing the lines of code that follow.
What you need is a return statement after each time you send a response (if there is any other code in your function that could execute and send another response) so that your function doesn't continue to execute and send another response or you could bracket your responses in if/else statements so you don't execute the sending of more than one response.
Here's a fixed version with 5 added return statements to keep the rest of your code from executing after you've sent a response and to keep you from sending multiple responses to the same request. Each addition is commented with ==> added:
// Import node packages
const mongoose = require('mongoose');
const Password = require('../models/password');
const User = require('../models/user');
const bcrypt = require('bcryptjs');
const moment = require('moment');
const string = require('../middleware/string_functions')
exports.update = (req, res, next) => {
User.findOne({ email: req.body.email })
.exec()
.then(user => {
if (!user) {
res.status(401).json({
message: 'Cannot retrieve account'
})
return; // <== added
}
const expiry = moment().add(30, 'seconds');
const unique_string = string.generate_random(32);
const url_hash = string.base64_encode(unique_string +':'+ user._id);
bcrypt.hash(unique_string, 10, (err, hash) => {
if (err) {
res.status(500).json({
error: err.message
})
return; // <== added
}
const query = { user_id: user._id }
const newData = {
hash,
expiry
}
Password.findOneAndUpdate(query, newData, { upsert: true, new: true })
.exec()
.then(request => {
res.status(201).json({
message: 'success',
url: 'localhost:8081/users/password/validate/' + url_hash,
data: request
})
})
.catch(err => {
res.status(500).json({
error: err.message
})
})
})
})
.catch(err => {
res.status(500).json({
error: err.message
})
})
}
exports.validate = (req, res, next) => {
if (!req.params.hash) {
res.status(500).json({
error: 'Missing hash'
})
}
const data = string.base64_decode(req.params.hash).split(':');
console.log(data)
Password.findOne({ user_id: data[1] })
.exec()
.then(request => {
if (!request) {
res.status(404).json({
message: 'Change request not found or expired'
})
return; // <== added
}
bcrypt.compare( data[0], request.hash, (err, result) => {
if (err) {
res.status(500).json({
error: err.message
})
return; // <== added
}
if (result) {
if (moment().isAfter(request.expiry)) {
res.status(401).json({
message: 'Time has expired'
})
}
res.status(200).json({
message: 'Hash validation successful'
})
return; // <== added
}
res.status(500).json({
error: 'Something went wrong'
})
})
})
.catch(err => {
res.status(500).json({
error: err.message
})
})
}
The res object by itself does not stop the execution of your program. You must use return if you prefer to use Guard Clauses instead of Nested Conditions
Replace statements like this:
if (err) {
res.status(500).json({
error: err.message
})
}
With this:
if (err) {
res.status(500).json({
error: err.message
});
return; // return statement added
}

Resources