Async Post with "await axios" - reactjs

in my React-App, I want to send an email with axios and nodemailer to the user after registration. So, when the button "Register" is pushed, it will use the api "/api/form" for sending the data to the server.
My problem is that the data will not be sent to the server in normal using. If I use the developertools with breackpoints for debugging this function, it works! So it can not be a problem with the server side, I think the problem is at the front end, but I don´t see why.
Is it the way I call axios.post?
Here is the code for the submit function (front end):
async onSubmit(e) {
e.preventDefault
var token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
const { firstName, email, authToken } = this.state;
const form = await axios.post('/api/form', {
firstName,
email,
authToken: token
}, console.log("form"),this.props.history.push(`/login`));
return form;
}
This is the code in index.js (server):
/*Double-Opt-In*/
app.use(express.static('public'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
/*Double-Opt-In*/
app.post('/api/form', (req, res) => {
console.log(req.body);
nodemailer.createTestAccount((err, account) => {
token= req.body.authToken;
link="http://localhost:3000/verify"+"?token="+token;
console.log("createTestAccount");
const htmlEmail = `
<h3>WebFit Registrierung</h3>
`
let transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: 'email',
pass: 'password'
}
})
let mailOptions = {
from: 'WebFit', // sender address
to: req.body.email, // list of receivers
replyTo: 'app#webfit.app',
subject: 'Welcome to Webfit <3', // Subject line
text: req.body.message, // plain text body
html: htmlEmail // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: %s', info.messageId);
// Preview only available when sending through an Ethereal account
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
});
});
});
const PORT = process.env.PORT || 3001
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
})
/*End Double-Opt-In*/

axios runs fine with async/await, so catch the throw/reject and check if axios or other. Depending on your api you can extract further info like shown:
async function login(token) {
/* post token and return jwt */
try {
const d = await api.post("/login", token);
return res.data["jwt"];
} catch (e) {
if(e.name == 'AxiosError'){
// map axios error to an app-friendly display message
// your api error handling dictates what you pull out,
// expressjs: return res.status(401).send("expired.");
// results in: '401 expired.'
return new Error(`${e.response.status} ${e.response.data}`);
} else {
return new Error(`${e.name} ${e.message}`);
}
}
}
If your tired of handling on every api call, use interceptors which can do the api error 2 local app error mapping service.js wide.

Related

Getting cors error in express and react app while redirecting when I use passport-local strategy and not when I use passport-google-oauth20

This is my back-end code.
import { config } from "dotenv"
import express,{Request,Response} from "express"
import mongoose, {ConnectOptions, Error} from "mongoose";
import DeckModal from './models/deck';
import cors from "cors"
import session from "express-session";
import passport from "passport";
import User from "./models/user";
import bcrypt from "bcryptjs"
import { UserDetails } from "./types/UserDetails";
import passportLocal from "passport-local"
const LocalStrategy=passportLocal.Strategy
const MongoDBStore = require('connect-mongodb-session')(session);
const GoogleStrategy =require("passport-google-oauth20")
config()
let PORT:number=5000
const app = express()
app.use(express.json())
const store = new MongoDBStore({
uri: process.env.MONGO_URI!,
collection: 'mysessions'
});
store.on('error', function(error:Error) {
console.log(error);
});
app.use(cors({ origin:"http://localhost:5173" ,credentials:true}))
app.set('trust proxy', 1)
// create session
app.use(session({
secret:process.env.SESSION_SECRET!,
resave:true,
saveUninitialized:true,
store:store,
// cookie:{
// httpOnly:true, //An HttpOnly Cookie is a tag added to a browser cookie that prevents client-side scripts from accessing data
// sameSite:"none", // If your frontend and backend is hosted on different origin then use sameSite:none in order to share cookie.
// secure:true, // it allows only https requests
// maxAge: 1000 * 60 * 60 * 24 * 7
// }
}))
app.use(passport.initialize())
app.use(passport.session())
// local strategy
passport.use(new LocalStrategy(
{usernameField:"email"},
function(username, password, done) {
User.findOne({ email: username }, function (err:Error, user:UserDetails) {
if (err) { return done(err); } // If there is error we will return an error to done function.
if (!user) { return done(null, false); } // If we don't find any user then we will return null and as there is no error we will send false
bcrypt.compare(password,user.password!,(err,result)=>{
if(err) throw err
if(result===true){
return done(null,user)
}
else{
return done(null,false)
}
})
});
}
));
//gooogle strategy
passport.use(
new GoogleStrategy(
{
callbackURL: "/auth/google/callback",
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
async (accessToken:any, refreshToken:any, profile:any, done:any) => {
const userInfo:UserDetails={
email:profile.emails[0].value,
profile_picture:profile.photos[0].value,
username:profile.displayName
}
try{
const user= await User.findOne({ email: profile.emails[0].value })
if(!user){
const newUser= new User(userInfo)
await newUser.save()
return done(null,newUser)
}
return done(null,user)
}
catch(err){
return done(null,false,err)
}
}
)
);
passport.serializeUser((user:any,done)=>{
return done(null,user._id)
})
passport.deserializeUser((id:string,done)=>{
User.findById(id,(err:Error,doc:any)=>{
const userInfo={
username:doc.username,
email:doc.email,
profile_picture:doc.profile_picture,
}
return done(null,userInfo)
})
})
// This is the recomended way to connect to the mongodb database using mongoose. source: https://docs.cyclic.sh/how-to/using-mongo-db#connection-example-mongoclient
mongoose.connect(process.env.MONGO_URI!,{
useUnifiedTopology: true,
useNewUrlParser: true
} as ConnectOptions )
.then(()=>{console.log("Connected to mongodb successfully");
// We will start to listen for request once the DB is connected
app.listen(process.env.PORT || PORT,()=>{
console.log("server is running on port "+PORT)
})
})
.catch((err:Error)=>{console.log(err)});
//Google auth
app.get('/auth/google', passport.authenticate('google', {
scope: ['profile','email']
// scope:['openid','profile', 'email']
}));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: "http://localhost:5173/login", failureMessage: true,successRedirect:"http://localhost:5173/auth/success"}));
app.get("/getuser",(req,res)=>{
if(req.user){
res.json(req.user) // here we get user in req.user because passports deserialize function attaches it automatically
}
else{
res.json(null)
}
})
app.post('/auth/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.send('done');
});
});
// Local auth
app.post("/signup",async(req:Request,res:Response)=>{
const {username,email,password}=req?.body
if(!username && !password && !email){
res.send("Please provide name, email and password")
return
}
try{
const user:UserDetails | null=await User.findOne({username})
if(!user){
const hashedPassword=await bcrypt.hash(password,10)
const newUser= new User({
username,
email,
password:hashedPassword
})
await newUser.save()
res.send("success")
}
else{
res.send("User already exist")
}
}
catch(error){
console.log(error)
}
})
app.post('/login',
passport.authenticate('local'),
(req, res) => {
if(req.user){
res.redirect("http://localhost:5173/auth/success")
}
else{
res.status(200).json({success:false});
}
}
);
I tried redirecting using successRedirect but that is also giving me same error
app.post('/login',
passport.authenticate('local',{successRedirect:"http://localhost:5173/auth/success"}));
**
When I authenticate using google auth it successfully redirects me but when I use local startegy for authentication it gives me cors error.**
Error message:
Access to XMLHttpRequest at 'http://localhost:5173/' (redirected from 'http://localhost:5000/login') from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
The front-tend request looks like this-
for local strategy:
const handleLogin = async (e:FormEvent<HTMLFormElement>) => {
e.preventDefault()
const data = { email, password };
axios.post("http://localhost:5000/login",data,{withCredentials:true})
}
for google strategy:
const handleGoogleLogin = async () => {
window.open("http://localhost:5000/auth/google","_self")
}

React profile page, how to avoid 'GET http://localhost:3001/users/profile 401 (Unauthorized)' when trying to get JSON data from back end

For this application, I am using React & Express. I have React running on PORT 3000, and Express running on PORT 3001. On the Express side, I have authentication working that uses JWT.
First, here is my auth.js service file:
const jwt = require('jsonwebtoken');
const models = require('../models');
const bcrypt = require('bcryptjs');
var authService = {
signUser: function (user) {
const token = jwt.sign({
Username: user.Username,
UserId: user.UserId
},
'secretkey',
{
expiresIn: '1h'
}
);
return token;
},
verifyUser: function (token) {
try {
let decoded = jwt.verify(token, 'secretkey');
return models.users.findByPk(decoded.UserId);
} catch (err) {
console.log(err);
return null;
}
},
hashPassword: function (plainTextPassword) {
let salt = bcrypt.genSaltSync(10);
let hash = bcrypt.hashSync(plainTextPassword, salt);
return hash;
},
comparePasswords: function (plainTextPassword, hashedPassword) {
return bcrypt.compareSync(plainTextPassword, hashedPassword);
}
}
module.exports = authService;
When a user makes a POST request to the signup route, it works:
router.post('/signup', function (req, res, next) {
models.users.findOrCreate({
where: {
Username: req.body.username
},
defaults: {
FirstName: req.body.firstName,
LastName: req.body.lastName,
Email: req.body.email,
Password: authService.hashPassword(req.body.password)
}
})
.spread(function (result, created) {
if (created) {
res.redirect("http://localhost:3000/login");
} else {
res.send('This user already exist')
}
});
});
Signup works in both Postman and React.
When a user makes a POST request to the login route, it works:
router.post('/login', function (req, res, next) {
models.users.findOne({
where: {
Username: req.body.username
}
}).then(user => {
if (!user) {
console.log('User not found')
return res.status(401).json({
message: "Login Failed"
});
} else {
let passwordMatch = authService.comparePasswords(req.body.password, user.Password);
if (passwordMatch) {
let token = authService.signUser(user);
res.cookie('jwt', token);
res.redirect('http://localhost:3001/users/profile');
} else {
console.log('Wrong Password');
}
}
});
});
Login works in both Postman and React.
When a user makes a GET request to the profile route, it semi-works:
router.get('/profile', function (req, res, next) {
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
if (user) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(user));
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
This works only in Postman, I can see the data that I want using Postman. In React, it will not get the profile route that I request. This is where the error comes in: Console Error
On the React side, this is profile GET component:
import React from 'react';
import axios from 'axios';
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
profileData: []
}
};
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
componentDidMount() {
this.fetchProfileData();
}
render() {
console.log(this.state.profileData);
if (this.state.profileData.length === 0) {
return <div>Failed to fetch data from server</div>
}
const profile = this.state.profileData.map(user => (
<div key={user.UserId}>Hello world</div>
));
return <div>{profile}</div>
}
}
export default UserProfile;
Then when I go to render this component, I just:
<UserProfile uri="http://localhost:3001/users/profile" />
Which then will render 'Failed to fetch data from server', then the console will log the '401 (Unauthorized)' error. I just can't get it to render in React.
And if anyone wants my Express app.js file for some extra information:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var models = require('./models');
var cors = require('cors');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
models.sequelize.sync().then(function () {
console.log("DB Synced Up");
});
module.exports = app;
Thank you in advanced. I have been struggling to figure this out.
I have tried toying with my UserProfile component. And I've tried toying with my /profile route in Express. The only 2 errors I've gotten is the 401 (Unauthorized) and something about the Headers. I know that my JWT key gets passed onto reacts side, because when I do 'localhost:3000/profile' (react side), I can see that I have the cookie stored. I'm not sure on how to approach authorization on React side. At this point, I am very clueless on what to do. This is the first time I've tried setting up authentication with React. I have always used Express and the .hbs files to render my profile pages. But I've been told that you shouldn't render a profile page in the back-end. So, here I am trying to do it with React.
I have rendered things from the back-end to the front-end, but that's without the use of JWT. I strongly believe that it has something to do with the JWT cookie. I just don't know how to authenticate it in React. Thanks again in advanced.
I fixed it by adding this into my React project:
I added this into my fetchProfileData()
{ withCredentials: true }
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI, { withCredentials: true }).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
Then in Express, I toyed with my Profile route. Put the data into an array, and sent it on its way:
router.get('/profile', function (req, res, next) {
var userData = [];
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
userData.push(user);
res.send(userData);
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});

How to Check if the user is logged in on reactjs using JWT

Im trying to make a system that it can check it the user is logged of not im using reactjs and JWT tokens that can stored to the cookies in browser.
This is my reactjs file code
const ApproveRequest = (approveOption) => {
if (approveOption === "approve"){
let request = 1;
axios.put("http://localhost:3001/cash/approverequest",{
approved: request,
id: id,
header: { accessToken: cookies.getItem("accessToken") },
withCredentials: true,
}).then((response) => {
if(response.data.error) {
console.log(response.data.error);
}else{
setCashObject({ ...cashObject, request: request });
alert("Request Approve");
}
});
} else {
alert("Field to update the request please contact the dev");
}
}
from my server JWT.js file
const validateToken = (req, res, next) => {
const accessToken = req.header("accessToken");
if(!accessToken) {
return res.json({error: "User not authenticated"});
}
try{
const validToken = verify(accessToken, "bluedragon14S");
if(validToken){
req.authenticated = true;
return next;
}
}catch (err) {
return res.json({error: err});
}
}
from server cash.js route
router.put("/approverequest", validateToken,async (req, res) => {
const { request = 1, id } = req.body;
await Cash.update({request: request}, {where: {id: id} });
res.json(request);
});
I wanted is i want to check if the user is logged in so that he/she can update the request
thank you in advance for your help
Addition in that code i can store the cookies into the browser i just don't know how to check if the user is logged in or not
I think you can access user cookies in this way :
req.cookies.accessToken
so change this :
const accessToken = req.header("accessToken");
to this :
const accessToken = req.cookies?.accessToken
if(accessToken )
...

Firebase not responding to cloud function

I am adding data to the realtime database with React JS, a contact form and the firebase initialized. That all works.
However, I'm trying to implement an email to be sent to me when a new contact form has been submitted. CURRENT PROBLEM: The cloud function is deployed yet when I submit the form (and realtime db is added to), nothing happens. Not even an error message in the firebase console.
Please can you take a look at my code and offer some advice as to how I can get the automatic emails sent.
const functions = require('firebase-functions')
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
admin.initializeApp()
require('dotenv').config()
const email = process.env.REACT_APP_SENDER_EMAIL;
const pass = process.env.REACT_APP_SENDER_PASS;
exports.sendEmailNotification = functions.firestore.document('messages/{id}')
.onCreate((snap, ctx) => {
const data = snap.data();
let authData = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: email,
pass: pass
}
});
authData.sendMail({
from: data.email,
to: data.to,
subject: data.name + ' sent a message',
text: data.text,
}).then(res => console.log('email sent')).catch(err => console.log(err));
});
Your function needs to return a promise that resolves when all the asynchronous work is complete.
return authData.sendMail({
from: data.email,
to: data.to,
subject: data.name + ' sent a message',
text: data.text,
})
Returning this promise lets Cloud Functions know when it's safe to clean up and move on.
I first attempted creating this as a firebase cloud function as well, but I shifted towards building nodemailer on the server. Working in firebase cloud functions I was using the loophole of downgrading to node: 8 in package.json (which is deprecated) and I was being forced into making a Google firebase paid plan. Both items were driving me into a corner that I didn't want to be in.
This is the result of nodemailer in node.js thanks to https://www.youtube.com/watch?v=nF9g1825mwk
const express = require('express')
require('dotenv').config()
const bodyParser = require('body-parser')
const exphbs = require('express-handlebars')
const path = require('path')
const nodemailer = require('nodemailer')
const app = express()
const email_from = process.env.EMAIL_FROM;
const sender_pass = process.env.SENDER_PASS;
const email_to = process.env.EMAIL_TO;
// View engine setup
app.engine('handlebars', exphbs())
app.set('view engine', 'handlebars')
//body parser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//static folder
app.use('/public', express.static(path.join(__dirname, 'public')))
app.get('/', (req, res) => {
res.render('contact', { layout: false })
})
app.post('/send', (req, res) => {
const output = `
<p>You have a new submission</p>
<h3>Contact Details</h3>
<ul>
<li>Name: ${req.body.name}</li>
<li>Company: ${req.body.company}</li>
<li>Email: ${req.body.email}</li>
<li>Phone: ${req.body.phone}</li>
</ul>
<h3>Message</h3>
<p> ${req.body.message} </p> `;
async function main() {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
// let testAccount = await nodemailer.createTestAccount();
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: email_from,
pass: sender_pass
},
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: email_from,
to: email_to,
subject: 'New Submission from Dean Productions!',
text: 'new submission',
html: output,
});
console.log("Message sent: %s", info.messageId);
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview only available when sending through an Ethereal account
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
res.render('contact', { layout: false, msg: 'Message has been sent!' })
}
main().catch(console.error);
})
app.listen(3000, () => console.log('Server started...'))

React onSubmit function doesn't hit Express endpoint (Nodemailer)

I have a form, which submits name, email and text.
onSubmit function of the React component:
onSubmit = e => {
const { name, email, text } = this.state;
axios.post('/feedback', { name, email, text })
.then((result) => {
console.log(result)
}).catch(err => console.log(err))
}
feedback.js file (api/feedback.js - this works fine and sends email if requested via Postman):
const express = require("express");
const router = express.Router();
const nodemailer = require("nodemailer");
// #route POST api/feedback
// #desc Tests resource route
// #access Public
router.post("/", function(req, res, next) {
let output = `<p>New feedback</p>
<h3>Feedback details</h3>
<ul>
<li>Name: ${req.body.name}</li>
<li>Email: ${req.body.email}</li>
</ul>
<h3>Feedback message</h3>
<p>${req.body.text}</p>
`;
const transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
auth: {
user: "t4qj6mgea2kpyep7#ethereal.email",
pass: "PASSWORD"
},
tls: {
rejectUnathorized: false
}
});
let mailOptions = {
from: 'Webtool feedback: <t4qj6mgea2kpyep7#ethereal.email>', // sender address
to: "TO#EMAIL.COM", // list of receivers
subject: 'Feedback from Webtool', // Subject line
text: 'Hello world', // plain text body
html: output // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log("Message sent: %s", info.messageId);
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
});
});
module.exports = router;
I have imported this into App.js, and set up the route for the feedback:
const feedback = require('./routes/api/feedback');
The issue is that the endpoint itself works, if I use Postman, I successfully receive an e-mail, so I don't suspect the feedback.js file. However, onSubmit() doesn't work.
​Did you make sure to include the middleware to protect web servers?
--Try this--
//middleware meant to protect web servers [CORS requests]
//can change '*' to 'http://localhost:3000' for local host testing
app.use((request, response, next) => {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Content-Type");
next();
app.use(bodyParser.json())
app.use(bodyParser.urlencoded){
extended: false
}));
You are sending the form to /feedback as a post request but in the server side you you are having a post method with route /.So,The route /feedback is not in the express and so it throwed you 404 not found.Try changing the server route to /feedback.

Resources