Handling Server-Side Errors on React: Formik & Yup - node.js

I've been trying to handle error from serverside (i.e. email already exists)
Summary of what I have tried so far:
Asynchronously create User Model with email being unique field upon POST /register request.
If there's an Error, I will send client side an error using "res.send(e)".
await Axios request saving it to variable response under try.
On the catch block, set error to field with name 'email'
Finally set submitting to false so user can fix the error.
Result: No console.log or error message from the server.
My questions are as follows:
How do I specify which error I am getting from server-side? (I've tried console logging error blocks by trying to access "e.detail" but I will get undefined.
How to handle this on client side using Formik and Yup? (What's the best practice?)
Below are my code set up.
My server router setup.
const handleErrors = (e) => {
console.log(Object.values(e));
res.send(e)
};
module.exports.register_post = async (req, res) => {
const { email, password, name, gender, social, about, interests } = req.body;
try {
const user = await User.create({
email,
password,
name,
gender,
twitter: social.twitter,
instagram: social.instagram,
about,
interests,
});
const token = createToken(user.id);
console.log(user);
res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 });
res.send(user);
} catch (e) {
handleErrors(e);
}
};
My Front-End React(with Formik) side set up
const onSubmit = async (values, { setSubmitting, setFieldError }) => {
const filteredValues = _pickBy(values, (_, key) => key !== 'password2');
try {
const response = await axios.post('http://localhost:8080/register', {
...filteredValues,
});
console.log(response);
history.push('/register/verification');
} catch (e) {
setFieldError('email', e);
} finally {
setSubmitting(false);
}
};

Related

How to render errors from node.js in react using fetch

My application has a login page that works fine when the user enters the correct login credentials but while test casing for eventual wrong entries of either usernames or passwords I've realized that my catch block isn't able to correctly format the error object from the backend thus nothing is being rendered to the frontend.
I've tried using res.send(401).json({"message":"Unauthorized"}); in the backend instead of res.sendStatus(401); but the former method doesn't trigger an error response and rather returns as a response in the fetch.
While using res.sendStatus(401);, although the error is triggered my catch block isn't able to render it's response.
The backend:
const User = require('../model/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const handleLogin = async (req,res) => {
const user = req.body.user.toLowerCase();
const pwd = req.body.pwd;
if(!user || !pwd) return res.sendStatus(400);
const foundUser = await User.findOne({username: user}).exec();
if(!foundUser) return res.sendStatus(401);
const match = await bcrypt.compare(pwd, foundUser.password);
console.log(match);
if(match){
const roles = Object.values(foundUser.roles);
const accessToken = jwt.sign(
{"userInfo": {
"username": foundUser.username,
"roles": roles
}},
process.env.ACCESS_TOKEN,
{expiresIn: "300s"}
);
const refreshToken = jwt.sign(
{"username": foundUser.username},
process.env.REFRESH_TOKEN,
{expiresIn: "1d"}
);
foundUser.refreshToken = refreshToken;
const result = await foundUser.save();
if(!result) return res.status(500);
res.cookie("jwt",refreshToken,{httpOnly: true, sameSite: "None", maxAge: 24*60*60*1000});
res.json({user, roles, accessToken});
}
else{
res.sendStatus(401);
}
}
module.exports = {handleLogin};
The fetch:
fetch(BASE_URL + "/login", {
method: "POST",
headers: {
"Content-Type":"application/json"
},
body: JSON.stringify({user: username,pwd})
})
.then(res => res.json())
.then(data => {
setUser(data);
console.log(data);
})
.then(() => {
setSuccess(true);
setTimeout(() => {
navigate("/");
}, 1000);
})
.catch((err)=>{
console.log(err);
if(err.status == "401"){
setErrMsg("Wrong username or password.")
}
else{
setErrMsg("Login failed, try again.")
}
errRef.current.focus();
})
Once the error is triggered the console displays the following error SyntaxError: Unexpected token 'U', "Unauthorized" is not valid JSON and in addition to that the error is not rendered to the frontend.
How can I correctly format the response from the backend or handle the error response from the front end to be able to correctly render it to the view?
Your first then assumes that the response has valid json. Instead it should check if the response status is ok and if not, throw an error that will be caught by the catch.
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.status);
})

Node Express server not send messages with whatsapp-web-js

i have a problem with my custom server, i'm trying to setup an api that send whatsapp messages in react as a frontend.
So, i have actually a route that send the QR to the frontend (working fine), a route that handle the authentication event (working fine) and
PROBLEM HERE:
...a route that send a message to a specific number (NOT WORK)
here my server code...what i'm doing wrong? if i launch a POST request to the endpoint on postman, i get an infinite loading (no errors).
const express = require('express')
const router = express.Router()
const fs = require('fs')
const { Client, LocalAuth } = require('whatsapp-web.js')
const authStrategy = new LocalAuth({
clientId: 'adminSession',
})
const worker = `${authStrategy.dataPath}/session-admin/Default/Service Worker`
if (fs.existsSync(worker)) {
fs.rmdirSync(worker, { recursive: true })
}
const client = new Client({
takeoverOnConflict: true,
authStrategy,
})
const sessionData = {
client: 'admin',
session: true,
qrCodeScanned: true,
}
client.on('authenticated', (session) => {
fs.writeFile(
'waSession.json',
JSON.stringify(sessionData),
'utf-8',
(err) => {
if (!err) {
console.log('Session saved on disk...')
}
}
)
})
router.get('/whatsapp/auth', async (req, res) => {
const dir = './waSession.json'
fs.readFile(dir, (err, data) => {
if (data.length === 0) {
return res.status(200).json({
message: 'You need to login first',
})
} else {
return res.status(200).json({
message: 'You are logged in.',
})
}
})
})
router.get('/whatsapp', async (req, res) => {
try {
client.on('qr', (qr) => {
res.status(200).send({
message: 'Connect whatsapp with this qr-code',
qrCode: qr,
})
})
await client.initialize()
res.status(404)
} catch (err) {
res.send(err)
}
})
router.post('/whatsapp/send', async (req, res) => {
const { phoneNumber, message } = req.body
try {
client.on('ready', async () => {
const number = phoneNumber
const text = message
const chatId = number.substring(1) + '#c.us'
await client.sendMessage(chatId, text)
})
await client.initialize()
res.json('Messaggio inviato')
} catch (err) {
res.status(404).send(err)
await client.destroy()
}
})
module.exports = router
A Client represents one authenticated WhatsApp user, and you have only one global variable client. This implies that all incoming requests will represent the same WhatsApp user, even if several different real users send requests to your server in parallel. This is probably not what you intend.
I suggest that you use express-session to associate every client with a session. Then a user needs to create a client and authenticate it only once during a session. All subsequent requests in the same session will re-use that client, and the client.on(...) and client.initialize() commands will not be repeated.

Cookies save and work fine in Insomnia app, but they do not work in my react application

I have a login page and a register page. When I register a user from the frontend form, it creates a new user. But when I try to log in to the user, I get the 200 status and my data back, but I guess it doesn't set the cookie. When I try to go to a protected route that only a logged-in user can access, I get the error I made from the backend which is "You are unauthenticated". How do I send or set the cookie in the front end too? Everything really works fine in the Insomnia app, the cookies get set.
this is how I'm making the post-login request
const submit = async (e) => {
e.preventDefault();
const data = { username, password };
try {
await axios.post(path, data).then((res) => {
console.log(res);
});
} catch (err) {
setLoading(false);
setError(err.message);
}
this is the login controller in the server side.
const login = async (req, res) => {
try {
const oneUser = await Users.findOne({ username: req.body.username });
if (!oneUser) {
return res.status(403).json("No such user in the database");
}
const isPassword = bcryptjs.compare(req.body.password, oneUser.password);
if (!isPassword) {
return res.status(500).json(`${req.body.username} Password is incorrect`);
}
const token = jwt.sign(
{
id: oneUser._id,
isAdmin: oneUser.isAdmin,
},
process.env.jwt
);
const { password, ...others } = oneUser._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json({ ...others });
} catch (err) {
res.status(500).json(err);
}
};

How to show validation errors sent from Node.js API to Next.js

I have created a Node.js API and am making requests to it using Next.js
Here is my Node.js controller. I am using express validator for validation.
If I fill in the form correctly, it works and the data is saved in mongo as expected. However, I want to send the validation errors back to the client when the form isn't filled in correctly. If I look in console, I can see the errors in the network tab.
exports.register = async (req, res) => {
// check if user exists in the database already
const emailExists = await User.findOne({ email: req.body.email });
if (emailExists) return res.status(400).send("Email already exists");
// hash password
const salt = await bcrypt.genSalt(10);
// hash the password with a salt
const passwordhash = await bcrypt.hash(req.body.password, salt);
// create new user
var user = new User({
name: req.body.name,
email: req.body.email,
password: passwordhash
});
try {
user = await user.save();
res.send({ user: user._id });
} catch {
res.status(400).send(err);
}
};
In Next.js, here is the code for making the http request
handleSubmit = event => {
const { name, email, password } = this.state;
event.preventDefault();
const user = {
name,
email,
password
};
try {
register(user);
} catch (ex) {
console.log(ex);
}
};
export const register = async user => {
const data = await http.post("http://localhost:8000/api/user/register", user);
console.log(data);
return data;
};
In console all I see is the below. So the console.log I am doing in the catch isn't working.
POST http://localhost:8000/api/user/register 422 (Unprocessable Entity)
Uncaught (in promise) Error: Request failed with status code 422
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:59)
That's because the catch statement isn't being run because the function isn't throwing an exception by itself. You should add the error handling inside the function like this:
try {
register(user);
} catch (ex) {
console.log(ex);
}
};
export const register = async user => {
const data = await http.post("http://localhost:8000/api/user/register", user).catch((e) {
throw new Error(e);
});
console.log(data);
return data;
};
I managed to get it working like this:
try {
const response = await register(user);
console.log(response);
} catch (ex) {
if (ex.response && ex.response.status === 422) {
const errors = ex.response.data.errors;
this.setState({ errors });
}
}

Adonis.js api Sign Up route is not working

I have an Adonis.js api-only app and my auth routes are not working.
Here is my signup route:
const Route = use('Route')
...
Route.post('/signup', 'UserController.signup')
Here is the action in the UserController:
'use strict'
const User = use('App/Models/User')
const Hash = use('Hash')
const Writ = use('App/Models/Writ')
class UserController {
async signup ({ request, auth, response }) {
// get user data from signup form
const userData = request.only(['name', 'username', 'email', 'password'])
console.log(userData);
try {
// save user to database
const user = await User.create(userData)
console.log(user);
// generate JWT token for user
const token = await auth.generate(user)
return response.json({
status: 'success',
data: token
})
} catch (error) {
return response.status(400).json({
status: 'error',
message: 'There was a problem creating the user, please try again later.'
})
}
}
...
module.exports = UserController
Using Postman, the console prints the request but returns:
{
"status": "error",
"message": "There was a problem creating the user, please try again later."
}
I hope you put all configuration right as mention in this document.
if your config right then this issue is user migration issue.
because user migration don't content name field so first check without send name into postman and not get name in controller like this
'use strict'
const User = use('App/Models/User')
const Hash = use('Hash')
const Writ = use('App/Models/Writ')
class UserController {
async signup ({ request, auth, response }) {
const userData =request.only(['username','email','password'])
console.log(userData);
try {
const user = await User.create(userData)
console.log(user);
// generate JWT token for user
const token = await auth.generate(user)
return response.json({
status: 'success',
data: token
})
} catch (error) {
return response.status(400).json({
status: 'error',
message: error
})
}
}
...
module.exports = UserController
and then try to generate token it's work
if you get success in response then change migration of user
try it:
'use strict'
const User = use('App/Models/User')
const Hash = use('Hash')
class UserController {
async signup ({ request, auth, response }) {
// get user data from signup form
const userData = request.only(['username', 'email', 'password'])
// ! Only existing fields in the database
console.log(userData);
try {
// save user to databas
const user = new User()
user.fill(userData)
await user.save()
// generate JWT token for user
const token = await auth.generate(user)
return response.json({
status: 'success',
data: token
})
} catch (error) {
return response.status(400).json({
status: 'error',
message: error
})
}
}
}
module.exports = UserController
It would be interesting to add a try/catch when creating the user to better target errors.
If it doesn't work, check the configuration files.
Have a nice day!

Resources