I trying to implement a contact form on Nodejs and express with SendGrid but it's giving me 403 Forbidden error, but the post request i sent returns 200. i don't know what i'm doing wrong, please i need a help to fix this.
Here is my whole route
const express = require('express')
const router = express.Router()
const ContacForm = require('../models/contact_form')
const fs = require('fs')
const path = require('path')
const sgMail = require('#sendgrid/mail');
const nodemailer = require("nodemailer");
router.get('/new', (req, res) => {
res.render("contact_form/new")
})
router.post('/', (req, res) => {
const output = `
<p>You have a new Request</p>
<h3>Contact Details </h3>
<ul>
<li>Name: ${req.body.name}</li>
<li>Email: ${req.body.email}</li>
/ul>
<h3>Message</h3>
<li>Request: ${req.body.request}</li>
`;
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'chukwumakingley1#gmail.com',
from: 'chukwumakingley1#gmail.com',
subject: 'Sending with Twilio SendGrid is Fun',
text: 'and easy to do anywhere, even with Node.js',
html: output,
};
sgMail.send(msg, (error, contact)=> {
if(error) {
console.log(error)
res.render("contact_form/new")
}
});
});
and these is the error message response i get on the terminal
ResponseError: Forbidden
at node_modules/#sendgrid/client/src/classes/client.js:105:29
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
code: 403,
message: 'Forbidden',
response: {
headers: {
server: 'nginx',
date: 'Sat, 11 Apr 2020 13:15:25 GMT',
'content-type': 'application/json',
'content-length': '281',
connection: 'close',
'access-control-allow-origin': 'https://sendgrid.api-docs.io',
'access-control-allow-methods': 'POST',
'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-
acl',
'access-control-max-age': '600',
'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html'
},
body: { errors: [Array] }
}
}
and here is my form
<form method="POST" action="/contact_form">
<label>Name</label>
<input type="text" name="name" id="name" placeholder="Enter Your Name">
<label>Email</label>
<input type="text" name="email" id="email" placeholder="Enter Your Email">
<label>Request</label>
<textarea name="request" id="request" placeholder="Enter Your Prayer Request" cols="30"
rows="10"></textarea>
<button type="submit"> Submit </button>
</form>
NOTE: I'm sending my SENDGRID_API_KEY variable and it's coming through
This is the kind of Sendgrid API that i am using
Integrate using our Web API or SMTP Relay
I just figured it out after taking time to research and read through the documentation.
It happened that I needed to do additional authentication called Single Sender Verification etc.
and I changed my code to give me a better understanding of the error
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'chukwumakingsley1#gmail.com',
from: 'chukwumakingsley1#gmail.com',
subject: 'Hello world',
text: output
};
sgMail
.send(msg)
.then(() => {
//Celebrate
console.log('Email Sent!');
})
.catch(error => {
//Log friendly error
console.error(error.toString());
console.log(output)
//Extract error msg
const {message, code, response} = error;
//Extract response msg
const {headers, body} = response;
});
});
after changing the code, the error message changed to
Forbidden (403)
The from address does not match a verified Sender Identity. Mail cannot be sent until this error is resolved. Visit https://sendgrid.com/docs/for-
developers/sending-email/sender-identity/ to see the Sender Identity
requirements
from this error, I read through the error docs on send-grids.
I think the additional authentication was added last month.
Your post request is returning 200 because you call res.render("contact_form/new") when there IS an error:
if(error) {
console.log(error)
res.render("contact_form/new")
}
Take a look at the body of the response you get from the SendGrid response. It contains an array of errors that may provide more information on why you're receiving a 403.
I had the same problem, the sendgrid from April 6, 2020 changed some criteria for authentication of free tests :(. Now it is necessary to make some configurations.
What solved for me was to follow this tutorial here from them. https://sendgrid.com/docs/ui/sending-email/sender-verification/. Only authorizing a few emails to send requests
Related
I'm trying to set up a form that sends info to my email using sendgrid and Gatsby Functions.
I've tried following the gatsby sendgrid example, and the youtube video "four real world solutions using gatsby functions..." but with both I end up getting a authorization/CORS error:
*console error
*response.body.errors
*verified single sender
I've tried a few things like:
adding cors middleware around my function (although I wasn't 100% I did this right)
setting res.set({"Content-Type": "application/json" Authorization: `Bearer ${process.env.SENDGRID_API_KEY}` });
setting res.setHeader('Access-Control-Allow-Origin', '*')
creating new api keys
testing gatsby develop & build, clearing cache etc
I'm running out of ideas so any advice would be appreciated :)
code for current form and api is below:
src/api/sendgrid.js
const sgMail = require('#sendgrid/mail')
console.log("api key:" + process.env.SENDGRID_API_KEY, process.env.SENDGRID_AUTHORIZED_EMAIL)
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
export default async (req, res) => {
res.set({"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`
});
const msg = {
to: 'myemail#gmail.com', // Change to your recipient
from: process.env.SENDGRID_AUTHORIZED_EMAIL, // Change to your verified sender
subject: 'Sending with SendGrid is Fun',
text: 'and easy to do anywhere, even with Node.js',
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
}
sgMail
.send(msg)
.then(() => {
console.log('Email sent'); console.log(msg);
})
.catch((error) => {
console.error(error);console.log('there was an error');
return res.status(500).json({
error: error.response,
})
})
return res.status(200)
}
src/pages/components/contact.js
const Contact = () => {
const [serverState, setServerState] = useState({
formSent: false,
});
const {
register,
handleSubmit,
formState: { errors },
} = useForm()
const onSubmit = data => {
fetch(`/api/sendgrid`, {
method: `POST`,
body: JSON.stringify(data),
headers: {
"content-type": `application/json`,
},
})
.then(res => res.json())
.then(body => {
console.log(`response from API:`, body);
})
}
console.log({ errors })
return(
<div style={{ display: "grid", width: "100%", }} id="contactSection" >
<div
style={{
// By using the same grid area for both, they are stacked on top of each other
gridArea: "1/1",
position: "relative",
// This centers the other elements inside the hero component
placeItems: "center",
display: "grid",
width: "100%",
}}
>
<ContactDiv>
{/* class={serverState.formSent ? "sent" : ""} */}
<span
// css={styles(serverState.formSent)}
>
<h1>Message Sent</h1>
<p>I'll be in touch shortly. Regards, Daniel.</p>
</span>
{/* <MobileId id="contactM"/> */}
<h1 id="contactM">Need a Website?</h1>
<ContactInfo>
<p>For a free project consultation call, email or use the form below.</p>
<p>Mobile:<br/> 022 078 0868</p>
<p>Email:<br/> daniel#thoughtfulhq.com</p>
</ContactInfo>
<div>
<form
onSubmit={handleSubmit(onSubmit)}
// action="/api/sendgrid" method="POST"
>
<label htmlFor="name">
<p>Name:</p>
<input
type="text"
name="name"
required
{...register("Name", { required: true, maxLength: 100 })}
/>
</label>
<label htmlFor="email">
<p>Email:</p>
<input
type="email"
name="email"
required
{...register("Email", { required: true, pattern: /^\S+#\S+$/i })}
/>
</label>
<label htmlFor="message">
<p>Project Details:</p>
<textarea
name="message"
id="message"
rows="5"
required
{...register("Message", { required: true, maxLength: 2000 })}
/>
</label>
<button type="submit">Submit</button>
</form>
</div>
</ContactDiv>
</div>
</div>
)
}
export default Contact;
Twilio SendGrid developer evangelist here.
As we worked out in the comments, your SendGrid API key was being stored incorrectly in your .env file. Environment variables should be stored on a new line per variable with no spaces between the variable name and the value, like:
SENDGRID_API_KEY=SG.xxx
SENDGRID_AUTHORIZED_EMAIL=sender#example.com
As an extra note, you should not set an Authorization header on the response to your request to the front-end. This will expose your SendGrid API key to anyone who uses the form if they investigate the response headers. I would recommend removing the line:
res.set({"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`
});
from src/api/sendgrid.js.
I'm just trying to send HTML file upon POST request. I'm 100% sure it was working an hour ago. Since then, I cannot figure out why it's not working all of a sudden!
Server Router:
const express = require('express');
const router = express.Router();
const cors = require('cors');
const path = require('path');
const auth = require('../middleware/auth.js');
// HOME ROUTE
router.options('/', cors());
router.get('/', cors(), (req, res) => {
res.status(201).sendFile(path.resolve(__dirname, '../', '../', 'public', 'index.html'));
});
router.post('/', cors(), (req, res) => {
res.status(201).sendFile(path.resolve(__dirname, '../', '../', 'view', 'manager.html'));
});
There's no error from server.
index.html
<form method="POST" autocomplete="off">
<input id="username" type="text" name="username" placeholder="Username" onchange="updateUsername(event)"><br>
<input id="password" type="password" name="password" placeholder="Password" onchange="updatePassword(event)"><br>
<button onclick="submitFunc(event)">LOGIN</button>
</form>
<script>
let username_value = document.querySelector('#username').value;
let password_value = document.querySelector('#password').value;
function updateUsername(e) {
username_value = e.target.value;
}
function updatePassword(e) {
password_value = e.target.value;
}
async function submitFunc(e) {
e.preventDefault();
let response = await fetch('/', {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
body: JSON.stringify({
username: username_value,
password: password_value
})
});
console.log(response);
}
Please note that the login logic itself is not an issue. I altered my code a lot due to this issue I have.
Upon sending POST request to '/', This is the response that logs in client console:
So the fetching itself seems to work just fine. It's just that new HTML file is not replacing the current HTML file. How would I go about fixing this?
You need to actually read the response. await fetch(...) just gets the headers and leaves a readableStream sitting there with the content waiting for you to read the actual content with response.json() or response.text() depending upon the data type you're expecting.
Change to this:
async function submitFunc(e) {
e.preventDefault();
try {
let response = await fetch('/', {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
body: JSON.stringify({
username: username_value,
password: password_value
})
});
// this assumes the response is text or html,
// use response.json() if the response is json
let data = await response.text()
console.log(data);
} catch(e) {
console.log(e);
// decide what to do here if there was an error with the fetch() call
}
}
You can see the various different methods available for reading the body contents here on MDN.
Also, if you're making a request with fetch(), the response from your server will just come back to your Javascript in your web page. It will NOT automatically display in the browser. If you want it to display in your browser, then either let the form post natively (without Javascript) or you will have to manually code your Javascript to receive the response and then insert it into the page content yourself.
I am having some issues with a Post request. I am trying to post a js object, the http request works but it doesnt seems to get the values form the input fields, it posts instead an empty object! I have tried everything cant seems to find the error!
app.js
function addPoem() {
event.preventDefault();
let title = document.getElementById("titleInput").value;
let author = document.getElementById("authorInput").value;
let text = document.getElementById("textInput").value;
let newPoem = {
id: 8,
title: title,
author: author,
text: text,
};
makeRequest("/poems/", "post", newPoem);
}
async function makeRequest(url, reqMethod, body) {
const response = await fetch(url, {
//headers = { "Content-Type": "application/json" },
method: reqMethod,
body: JSON.stringify(body),
});
console.log(response);
const data = await response.json();
console.log(data);
}
index.html
<form method="post" action="/poems">
<h1>Send us your poem!</h1>
<input type="text" requirede name="title" id="titleInput"> <br>
<input type="text" required name="author" id="authorInput"> <br>
<br>
<input type="text" required name="text" id="textInput" style="width:500px;height:500px">
<br>
<button type="submit" onclick="addPoem()">Send</button>
</form>
server.js
// Post a poem
app.post("/poems", (req, res, next) => {
allPoems.push(req.body);
res.json({ status: "A new poem has been posted!" });
});
I think your method should work, because you post directly from the server.js method
But if you yang to use the function in app.js use this
form method="post" action="/poems"
To
form method="post" onsubmit="addPoem()"
So I am building a contact form with ReactJs (frontend) and AWS Lambda(NodeJS), DynamoDB to store the data and API Gateway for the api to post to.
I have some confusion with my Lambda function, when I test in the AWS Lambda function it works fine when I cross check with the DynamoDB table the data shows fine, So to me its working fine.
Next, when I have created my API endpoint and tested in PostMan once again I get 200 OK and the data shows in the table.
But if I test on https://resttesttest.com/ I am getting an error regarding Oh no! Javascript returned an HTTP 0 error. One common reason this might happen is that you requested a cross-domain resource from a server that did not include the appropriate CORS headers in the response. Better open up your Firebug... Also this is the case as I get errors on the frontend with error:
Access to fetch at 'https://bac1eal279.execute-api.eu-west-2.amazonaws.com/dev' from origin 'https://4aac7fcc99274cf38ccbeef575c7edc0.vfs.cloud9.eu-west-2.amazonaws.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I am abit confused, any idea's might help.
AWS Lambda Function/NodeJS
var AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region: "eu-west-2"});
exports.handler = (event, context, callback) => {
console.log("Processing...");
const params = {
Item: {
id: Date.now(),
message: event.message,
email: event.email
},
TableName: "primaryTable"
};
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify('Hello from Lambda function'),
};
docClient.put(params, function(err, data) {
if(err){
callback(err, null);
} else {
callback(null, data);
}
});
};
React Component
import React, { Component } from 'react';
class Contact extends Component {
constructor(props) {
super(props);
this.state = {
message: '',
email: ''
};
}
handleSubmit(e) {
e.preventDefault();
/*global fetch */
fetch('https://bac1eal279.execute-api.eu-west-2.amazonaws.com/dev',{
method: "POST",
body: JSON.stringify(this.state),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(
(response) => (response.json())
).then((response)=>{
if (response.status === 'success'){
alert("Message Sent.");
this.resetForm();
}else if(response.status === 'fail'){
alert("Message failed to send.");
}
});
}
onEmailChange(event) {
this.setState({email: event.target.value});
}
onMessageChange(event) {
this.setState({message: event.target.value});
}
render() {
return (
<div className="container mt-5">
<h3>Contact</h3>
<form id="contact-form" onSubmit={this.handleSubmit.bind(this)} method="POST">
<div className="form-group mt-4">
<label htmlFor="exampleInputEmail1">Email address</label>
<input type="email" className="form-control" aria-describedby="emailHelp" value={this.state.email} onChange={this.onEmailChange.bind(this)} />
</div>
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea className="form-control" rows="5" value={this.state.message} onChange={this.onMessageChange.bind(this)} />
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</div>
);
}
}
export default Contact;
I have tried with Axios and its still the same issue? I have left the endpoint in so that the error can be viewed and diagnosed.
Postman uses curl and CORS is for browsers only. you will need to whitelist your domain in your api-gateway and lambda function.
I know that a similar question has been asked many times, e.g. here.
However, I've tried applying various solutions from these answers without success.
Here's my client-side page:
<form id="submit_newuser" method="POST" action="/submit_newuser">
User name:
<input type="text" id="username" name="username" />
<br>
Phone number:
<input type="text" id="phonenumber" name="phonenumber" />
<br><br>
<input type="submit" />
</form>
<script>
document.getElementById("submit_newuser").addEventListener('submit',submit_newuser);
function submit_newuser(event){
event.preventDefault();
submit_newuserForm=document.getElementById("submit_newuser");
formData=new FormData(submit_newuserForm);
fetch("/submit_newuser",{
body:formData,
headers:{
"Content-Type": "application/json"
},
method:"post"
})
.then(response=>console.log(response))
.catch(err=>"submit_newuser: error: "+err);
}
</script>
And here's the relevant server-side code for the /submit_newuser endpoint:
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.post('/submit_newuser',function(req,res){
console.log("/submit_newuser: req.body: "+JSON.stringify(req.body));
var phonenumber=req.body.phonenumber;
var username=req.body.username;
var output="you submitted: "+username+" "+phonenumber;
console.log("/submit_newuser: text to send back: "+output);
res.send(output);
});
With this, when I submit data from the page, the server logs this error:
SyntaxError: Unexpected token - in JSON at position 0
When I change the Content-Type to "application/x-www-form-urlencoded", I get this console logging:
/submit_newuser: req.body: {"------WebKitFormBoundaryBDU4OcntAv7d5wWL\r\nContent-Disposition: form-data; name":"\"username\"\r\n\r\ntestUserName\r\n------WebKitFormBoundaryBDU4OcntAv7d5wWL\r\nContent-Disposition: form-data; name=\"phonenumber\"\r\n\r\ntestPhoneNumber\r\n------WebKitFormBoundaryBDU4OcntAv7d5wWL--\r\n"}
/submit_newuser: text to send back: you submitted: undefined undefined
Which indicates that the data is being posted to the server, but not properly parsed into req.body.
Solutions involving multer don't seem to apply here because I'm not uploading a file, but I'm not sure if I should nonetheless be using a different approach than posting a FormData object in the fetch() body.
I'm confused, and also wondering if there's a simpler way to do this. I just want to post form data to the server without triggering a page refresh, and be able to update page elements with the server response.
You can update your client-side code to send a JSON body like so, this will be parsed correctly by the server.
Because we're setting the content-type header to JSON on the client, we must send JSON data. We could send url encoded data or multipart/form-data, however we would have to change headers on the client to do this.
document.getElementById("submit_newuser").addEventListener('submit',submit_newuser);
function formDataToJson(formData) {
const obj = {};
formData.forEach((value, key) => {
obj[key] = value
});
return JSON.stringify(obj);
}
function submit_newuser(event){
event.preventDefault();
submit_newuserForm=document.getElementById("submit_newuser");
formData = new FormData(submit_newuserForm);
fetch("/submit_newuser",{
body: formDataToJson(formData),
headers: {
"Content-Type": "application/json"
},
method:"post"
})
.then(response=>console.log(response))
.catch(err=>"submit_newuser: error: "+err);
}
If you're trying to send client's request as formData then you must handle your backend-side to receive request as formData.
There is a npm module called multer for handling formData request type.
Anyway your code must change to something like this:
Client-side
<script>
document.getElementById("submit_newuser").addEventListener('submit',submit_newuser);
function submit_newuser(event){
event.preventDefault();
submit_newuserForm=document.getElementById("submit_newuser");
formData=new FormData(submit_newuserForm);
fetch("http://127.0.0.1:3000/submit_newuser",{
body:formData,
method:"post"
})
.then(response=>console.log(response))
.catch(err=>"submit_newuser: error: "+err);
}
</script>
Server-side
const multer = require('multer');
const upload = multer();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.post('/submit_newuser', upload.none(), function (req, res) {
console.log("/submit_newuser: req.body: " + JSON.stringify(req.body));
var phonenumber = req.body.phonenumber;
var username = req.body.username;
var output = "you submitted: " + username + " " + phonenumber;
console.log("/submit_newuser: text to send back: " + output);
res.send(output);
});
Use below sample ajax call for sending data from frontend.
var settings = {
"async": true,
"crossDomain": true,
"url": "http://localhost:3000/submit_newuser",
"method": "POST",
"data": {
"username": "test user",
"phonenumber": "03453512545"
}
}
$.ajax(settings).done(function (response) {
console.log(response);
});
sample postman request.