I am trying to have my server reject the signup request if the user tries to sign up with an existing account. However, I cant seem to reject it properly and pass the error message to my client side.
//server.js
app.post('/signup', (req, res) => {
const email = req.body.email
const plainTextPassword = req.body.password;
//check if user already exists
User.find({ email: email }, (err, existingUser) => {
//account doesnt exist
if (existingUser.length === 0) {
bcrypt.hash(plainTextPassword, saltRounds, async (err, hash) => {
try {
const user = new User({
email: email,
password: hash
});
let result = await user.save();
if (result) {
res.send(result)
}
} catch (e) {
res.send(e);
}
})
} else {
//notify user that account exists
return Promise.reject(new Error('Account already exists'))
}
})
})
//reduxSlice.js
export const signup = createAsyncThunk(
'userAuth/signup',
async (payload, thunkAPI) => {
const { email, password } = payload
try {
const result = await fetch(
signupPath, {
mode: 'cors',
credentials: 'include',
method: "post",
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json'
}
}
)
return result.json()
} catch (error) {
console.log(error) //this line executes
}
}
)
From my reduxdev tools, my signup is still fulfilled EVEN though I rejected it from my server. Also, my server crashes after one attempt, which leads me to suspect there is an uncaught error.
The client only receives what you do res.send() with or next(err) which will then call res.send(). Promises are local only to the server and are not something that gets sent back to the client.
In your original code, I'd suggest that you use ONLY promise-based asynchronous operations and then you can throw in your code, have one place to catch all the errors and then send an error back to the client from there.
class ServerError extends Error {
constructor(msg, status) {
super(msg)
this.status = status;
}
}
app.post('/signup', (req, res) => {
try {
const email = req.body.email
const plainTextPassword = req.body.password;
//check if user already exists
const existingUser = await User.find({ email: email });
//account doesnt exist
if (existingUser.length !== 0) {
throw new ServerError('Account already exist', 403);
}
const hash = await bcrypt.hash(plainTextPassword, saltRounds);
const user = new User({
email: email,
password: hash
});
const result = await user.save();
res.send(result);
} catch (e) {
if (!e.status) e.status = 500;
console.log(e);
res.status(e.status).send({err: e.message});
}
});
Then, in your client code that is using fetch(), you need to check result.ok to see if you got a 2xx status back or not. fetch() only rejects if the network connection to the target host failed. If the connection succeeded, even if it returns an error status, the fetch() promise will resolve. You have to examine result.ok to see if you got a 2xx status or not.
//reduxSlice.js
export const signup = createAsyncThunk(
'userAuth/signup',
async (payload, thunkAPI) => {
const { email, password } = payload
try {
const result = await fetch(
signupPath, {
mode: 'cors',
credentials: 'include',
method: "post",
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json'
}
});
// check to see if we got a 2xx status success
if (!result.ok) {
throw new Error(`signup failed: ${response.status}`);
}
return result.json()
} catch (error) {
console.log(error) //this line executes
}
}
)
Related
This is a todo list web app, I have used nodejs and reactjs in it
I am not able to use the login feature , It shows me the error : invalid token
I have tried hard coding the token (which generates on the sign up) and that way it worked. But with the below code it doesnt work.
Using JWT for Authentication token generation
Funtion that handles the Login Click (user puts email and password)
const handleSubmit = async (e) => {
e.preventDefault();
const response = await fetch('http://localhost:5000/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: credentials.email, password: credentials.password })
});
const json = await response.json();
if (json.success) {
localStorage.setItem('token', JSON.stringify(json.authToken));
showAlert('Successfully Logged in');
navigate("/");
} else {
alert("Invalid credentials");
}
}
Backend Api Call (Using Nodejs and Express)
router.post("/login", fetchUser,
[
body("email", "Enter a valid email").isEmail(),
body("password", "Password cannot be blank").exists(),
], async (req, res) => {
let success = false;
// if there are errors, handle them with bad requests
const errors = validationResult(req);
if (errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const { email, password } = req.body;
// Check if the user with requested email exists in the database
let user = await User.findOne({ email });
if (!user) {
success = false;
return res.status(400).json({ success, error: "Please enter the correct credentials" });
}
// Check if the user with requested passwork exists in the database
const comparePassword = await bcrypt.compare(password, user.password);
if (!comparePassword) {
success = false;
return res.status(400).json({ success, error: "Please enter the correct credentials" });
}
// Auth Token Generation using jwtToken
const data = {
user: {
id: user.id,
},
};
success = true;
let authToken = jwt.sign(data, JWT_Secret);
res.json({ success, authToken });
} catch (error) {
res.status(500).send("Internal error occured");
}
});
When I tried Hardcording the auth-token it worked
By clicking on login the new auth-token should be generated and set as 'token' in the local storage. Through which data will be accessed using different end points.
At this line json.authToken is a string already. You don't need to stringify it again.
localStorage.setItem('token', JSON.stringify(json.authToken))
Just remove the function and it'll be fine.
localStorage.setItem('token', json.authToken)
I am working on a React and Node app and I don't understand how to pass the error given from the back end to the catch block in the fetch in the front end.
The login function uses fetch that throws an error if the server returns a not-ok status. The server also returns an array of errors that I need to display on the front end.
My problem is that when forcing an error and throwing the error to be caught in the catch block of the fetch promise, I cannot feed the catch with the array of errors returned by the back end.
I feed the response to the catch and there when it is logged it says it is an object Response. And it does not have the errors property coming from the back end response.
This is the login function on the front end:
function handleLogin() {
fetch('http://localhost:5000/auth/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
})
.then((response) => {
if(!response.ok) {
throw Error(response)
}
return response.json()
})
.then((token) => {
localStorage.setItem('token', token);
history.push('/');
window.location.reload();
})
.catch((error) => {
console.log('error: ', error); // error: Error: [object Response]
console.log('error:', error.errors); // undefined
setErrors(error.errors)
})
}
This is the controller of the login in the back end:
exports.login = async (req, res) => {
const { password, username } = req.body;
const hasErrors = validationResult(req);
// VALIDATE INPUTS
if (!hasErrors.isEmpty()) {
console.log('there are errros')
return res.status(401).json({
erros: hasErrors.array(),
});
}
// VALIDATE USER
const user = await User.findOne({ username });
if (!user) {
return res.status(401).send({
erros: [
{
msg: 'Invalid Credentials 1',
},
],
});
}
const isValid = await bcrypt.compare(password, user.password);
if (isValid) {
// SIGN THE JWT
const token = await JWT.sign({ username }, 'mysecret', {
expiresIn: 864_000,
});
return res.json(token);
} else {
return res.status(401).send({
erros: [
{
msg: 'Could not save the user into the db',
},
],
});
}
}
Sorry for this probably very basic question but I dont fully understand error handling.
I have a login route. I want to send an error msg for example the case where a wrong email is provided
const user = await User.findOne({email})
if (!user) return res.status(404).send({error: "email not found"});
However that one is never received in my client. The only err I see when i log it is
[AxiosError: Request failed with status code 404]
How do I do it? Thanks !!
const authReducer = (state, action) => {
switch (action.type) {
case 'add_error':
return {...state, errorMessage: action.payload}
case 'signin_success':
return {errorMessage: '', token: action.payload}
default:
return state;
}
};
const login = dispatch => async ({email, password}) => {
try {
const response = await API.login(email, password); // I also tried to put in outside the try block to maybe then get the err msg in the response but didnt
await AsyncStorage.setItem('token', response.data.token);
dispatch({type: 'signin_success', payload: response.data.token});
Navigation.navigate('Tabs', { screen: 'Home' });
} catch (err) {
dispatch({type: 'add_error', payload: 'Something went wrong with signing up'})
}
};
let API = {
login: (email, password) => {
return BaseAPI.post('/login', {email, password})
}
}
route handler
app.use(router.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(422).send({error: "-Must provide email and password"})
}
const user = await User.findOne({email})
if (!user) return res.status(404).send({error: "email not found"});
try {
await user.comparePassword(password);
const token = jwt.sign({userId: user._id}, 'MY_SECRET_KEY')
res.send({token});
} catch (err) {
return res.status(422).send({error: 'Invalid password or me'});
}
}
I created a Backend server and posted it to Heroku and now I am using the server URL to post and get data from it. However when I display an error message it's getting me the status code instead of the actual error.
My Backend login code.
export const loginUser = asyncHandler(async(req, res) => {
const { email, password } = req.body;
const user = await userModel.findOne({ email });
const token = generateToken(user._id)
if(user && (await user.matchPassword(password))){
res.json({
_id:user._id,
username: user.username,
email: user.email,
profilePic:user.profilePic,
admin:user.admin,
token:token,
});
} else {
res.status(400)
throw new Error("invalid email or password please check and try again!");
}
});
My user Actions code since I am suing redux
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const url = `${process.env.REACT_APP_SERVER_URL}api/loginuser`;
const config = {
headers: {
"Content-type": "application/json",
},
};
const { data } = await axios.post(
url,
{
email,
password,
},
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Error in frontend display
First you need to send an error message from your Back End like so:
export const loginUser = asyncHandler(async(req, res) => {
const { email, password } = req.body;
const user = await userModel.findOne({ email });
const token = generateToken(user._id)
if(user && (await user.matchPassword(password))){
res.json({
_id:user._id,
username: user.username,
email: user.email,
profilePic:user.profilePic,
admin:user.admin,
token:token,
});
} else {
res.status(400).json({errorMessage :"invalid email or password please check and try again!" })
}
});
Then get it in the React part (make sure to read the comment I added after that console.log):
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const url = `${process.env.REACT_APP_SERVER_URL}api/loginuser`;
const config = {
headers: {
"Content-type": "application/json",
},
};
const { data } = await axios.post(
url,
{
email,
password,
},
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
console.log(error); // look at the console, as I may miss the correct retuned error object
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response.data.errorMessage
});
}
};
you need to return response instead of throwing the error:
res.status(400)
res.json({
message: "invalid email or password please check and try again!"
});
I would like to be able to redirect from registration-page to login-page on successfull registration and again from login-page to home-page afteer successfull login.
I dont know what methods to use or where to call them.
This is the register call.
app.post("/api/register", async (req, res) => {
const { username, password: plainTextPassword } = req.body;
const password = await bcrypt.hash(plainTextPassword, 10);
try {
const response = await User.create({
username,
password
})
console.log("User created", response)
} catch (error) {
if (error.code === 11000) {
return res.json({ status: "error", error: "Username already in use" })
}
throw error
}
res.json({ status: "ok" });
});
This is the script
<script>
const form = document.getElementById("reg-form");
form.addEventListener("submit", registerUser);
async function registerUser(event) {
event.preventDefault();
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const result = await fetch("/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username,
password
})
}).then((res) => res.json())
if (result.status === "ok") {
alert("Success");
} else {
alert(result.error)
}
}
</script>
</body>
</html>
You should return the line that redirects
return res.redirect('/UserHomePage');