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

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.

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');
}
});

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...'))

req.body.something returns undefined

I have been trying to post data to my express server using axios, and when I console.log(req.body.something) it returns undefined, and when I console.log(req.body) only it logs this message to the console:
[Object: null prototype] { '{"nameVal":"Usef","nickNameVal":"US"}': '' }
Any Help Will Be Appreciated.
// This My Server.js Code
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
app.use(bodyParser.json());
// create application/x-www-form-urlencoded parser
const urlencodedparser = bodyParser.urlencoded({ extended: false });
// Use Cors As MiddleWhere
app.use(cors());
// Get The Post Request
app.post("/user", urlencodedparser, (req, res) => {
console.log(req.body.name); // returns undefined
});
app.listen(5000);
// and this the react component state along with the axios post request
state = {
nameVal: "Usef",
nickNameVal: "US"
};
handleSubmit = event => {
event.preventDefault();
const { nameVal, nickNameVal } = this.state;
axios.post("http://localhost:5000/user", { nameVal, nickNameVal },
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
).then(res => {console.log(res)});
};
If you remove your custom Content-Type header from the axios request, axios will send your data as JSON by default, and it will be parsed by your express JSON parser middleware.
axios.post("http://localhost:5000/user", { nameVal, nickNameVal })
.then(res => console.log(res));
The data you send to the server is nameVal and nickNameVal, so trying to access req.body.name will still give undefined. Try logging nameVal and nickNameVal instead.
app.post("/user", (req, res) => {
console.log(req.body.nameVal, req.body.nickNameVal);
});
According to axios documentation you need to pass an instance of URLSearchParams (or a query string of the parameters) as the second argument.
const params = new URLSearchParams();
params.append('nameVal', state.nameVal);
params.append('nickNameVal', state.nickNameVal);
axios.post('/user', params);

Async Post with "await axios"

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.

Resources