Firebase SendEmail function works only on 3rd click - node.js

I am using firebase SendEmail function in my React app (for Contact Form). After all deployment, when I test it through my Contact Form, it doesn't work upon "Submit" request. But it works only when I click immediate after getting second failer message after timeouts. If I repeat new Submit later, it again doesn't work but only with 3rd click.
Here is the error message:
Access to fetch at 'https://us-central1-myapp.cloudfunctions.net/sendEmail' from origin 'https://www.mydomain.co' has been blocked by CORS policy: 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.
And then "Failed to load resource: net::ERR_FAILED".
When it works, no error message there of course.
Here is my code (C:...\root\functions\index.js):
//import needed modules
const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
//when this cloud function is already deployed, change the origin to 'https://your-deployed-app-url
const cors = require('cors')({ origin: 'https://www.mydomain.co' });
//create and config transporter
let transporter = nodemailer.createTransport({
host: 'mail.mydomain.co',
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: 'info#mydomain.co',
pass: '********'
},
});
//export the cloud function called `sendEmail`
exports.sendEmail = functions.https.onRequest((req, res) => {
//for testing purposes
console.log(
'from sendEmail function. The request object is:',
JSON.stringify(req.body)
);
//enable CORS using the `cors` express middleware.
cors(req, res, () => {
//get contact form data from the req and then assigned it to variables
const name = req.body.data.name;
const email = req.body.data.email;
const company = req.body.data.company;
const subject = req.body.data.subject;
const message = req.body.data.message;
//config the email message
const mailOptions = {
from: email,
to: `info#mydomain.com`,
subject: subject,
text: `Name: ${name}, Company: ${company} Message: ${message}`,
};
//call the built in `sendMail` function and return different responses upon success and
failure
return transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return res.status(500).send({
data: {
status: 500,
message: error.toString(),
},
});
}
return res.status(200).send({
data: {
status: 200,
message: 'sent',
},
});
});
});
});
Also, although Postman sends emails from localhost app, it absolutely doesn't send email when the app deployed (when used the function's online url: https://us-central1-myapp.cloudfunctions.net/sendEmail).
Any ideas about this issue, please?

Related

Nodemailer - massive delay or missing emails in production

I currently have a site using Nodemailer & Gmail that works fine in my local development environment - any email sends instantly to the desired address.
Sadly, in production, only the admin notifications are being sent and the user ones are taking a very long time to deliver or not delivering at all. Ones that have arrived successfully took around 1 hour. The receiving email for admin emails is of the same domain as the URL of the website which makes me consider whether it's a domain verification issue. Only external recipients seem to get the delay.
My code is as follows:
const nodemailer = require('nodemailer')
const gmailUser = process.env.GMAIL_USER
const gmailPass = process.env.GMAIL_PASS
const appTitle = process.env.APP_TITLE
const receivingEmail = process.env.MAIL_RECEIVE
const smtpTransporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: gmailUser,
pass: gmailPass
}
})
const emailConfirmation = (userEmail) => {
const userMailOptions = {
from: appTitle,
to: userEmail,
subject: `${appTitle} - User Confirmation`
text: 'User Confirmation'
}
const adminMailOptions = {
from: appTitle,
to: receivingEmail,
subject: `${appTitle} - Admin Confirmation`,
text: 'Admin Confirmation'
}
Promise.all([
smtpTransporter.sendMail(userMailOptions),
smtpTransporter.sendMail(adminMailOptions)
])
.then((res) => { return true })
.catch((err) => { console.log("Failed to send email confirmations: ", err); return false })
}
I then call the function in a POST handler as follows:
emailConfirmation(user.email)
Am I doing something wrong in my code, or is this likely to be some sort of domain verification error?
I ended up deciding to switch to a different mail provider and have since had no issues.

When I try to run a Post User Registration script in Auth0 it gives "Error! API Error. Please contact support if the problem persists" error

I am trying to use Auth0's actions for post user registration. When I try to test it via UI, it gives me an error like "Error! API Error. Please contact support if the problem persists" and in the console it only writes "Error: {}". The script I wrote for this action looks something like this:
const https = require('https');
const jsonwebtoken = require('jsonwebtoken');
/**
* #param {Event} event - Details about registration event.
*/
exports.onExecutePostUserRegistration = async (event) => {
const TOKEN_SIGNING_KEY = event.secrets.signingKey
const TOKEN_EXPIRATION_IN_SECONDS = 10
const payload = {
email: event.user.email,
name: event.user.given_name,
surname: event.user.family_name
};
const token = jsonwebtoken.sign(payload, TOKEN_SIGNING_KEY,
{ subject: "postUserRegistration",
expiresIn: TOKEN_EXPIRATION_IN_SECONDS });
console.log("Starting post user registration operations for user with email: ", payload.email);
return new Promise((resolve, reject) => {
const request = https.request(url, options,
(res) => {
if (res.statusCode === 200) {
resolve({statusCode: res.statusCode, headers: res.headers})
} else {
reject({
headers: res.headers,
statusCode: res.statusCode
})
}
res.on('error', reject);
});
request.on("error", function (e) {
console.error(e);
reject({message: {e}});
});
request.on("timeout", function () {
reject({message: "request timeout"});
})
request.end();
});
}
Can you help me about what exactly causes this problem?
In order to understand this problem I tried to assign the Promise to a variable and then see what it returned. The funny part was that it was "pending". It couldn't be "pending" in any way, because it everything was true it would be "resolved", else "rejected" and if there is a timeout/error from the request it would be "rejected".
It turns out that Auth0 has some limitations for actions and the endpoint called was stuck to our firewall which caused a timeout in Auth0 before an HTTPS timeout. As the request was broken halfway, it stayed as "pending".

How to Resolve Error Sending Email Using nodemailer googleapi?

Last week I initially posted this asking for help using nodemailer and googleapi. I'm trying to use nodemailer and googleapis to send an email. I have set up my project in https://console.cloud.google.com/ and have set my CLIENT_ID, CLIENT_SECRET, CLIENT_REDIRECT_URI and REFRESH_TOKEN in a .env and have confirmed that the values are being populated. In debug mode I have noticed the following error stack when I send the error:
'Error: invalid_grant\n at Gaxios._request (/Users/ENV/Tutoring-Invoice-Management-System/node_modules/gaxios/build/src/gaxios.js:130:23)\n at processTicksAndRejections
(node:internal/process/task_queues:96:5)\n
at async OAuth2Client.refreshTokenNoCache (/Users/ENV/Tutoring-Invoice-Management-System/node_modules/google-auth-library/build/src/auth/oauth2client.js:174:21)\n
at async OAuth2Client.refreshAccessTokenAsync (/Users/ENV/Tutoring-Invoice-Management-System/node_modules/google-auth-library/build/src/auth/oauth2client.js:198:19)\n
at async OAuth2Client.getAccessTokenAsync (/Users/ENV/Tutoring-Invoice-Management-System/node_modules/google-auth-library/build/src/auth/oauth2client.js:227:23)\n
at async sendMail (/Users/ENV/Tutoring-Invoice-Management-System/service/send-email.js:17:29)'
The code is below. I have edited it based on an answer to the question already. My question now is, why am I getting the invalid_grant error? Based on the formal documentation I have set everything up correctly in https://console.cloud.google.com/apis/credentials/oauthclient. But perhaps there is an issue there?
const nodemailer = require('nodemailer');
const { google } = require('googleapis');
require('dotenv').config();
console.log("CLIENT_ID: " + process.env.CLIENT_ID);
console.log("CLIENT_SECRET: " + process.env.CLIENT_SECRET);
console.log("CLIENT_REDIRECT_URI: " + process.env.REDIRECT_URI);
console.log("REFRESH_TOKEN: " + process.env.REFRESH_TOKEN);
const oAuth2Client = new google.auth.OAuth2(process.env.CLIENT_ID, process.env.CLIENT_SECRET, process.env.REDIRECT_URI);
console.log("oAuth2Client: " + oAuth2Client);
oAuth2Client.setCredentials({refresh_token: process.env.REFRESH_TOKEN})
async function sendMail() {
try {
const accessToken = await oAuth2Client.getAccessToken()
const transport = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: 'OAuth2'
}
});
const mailOptions = {
from: 'envolonakis#gmail.com',
to: 'envolonakis#gmail.com',
subject: "Test Email API Subject",
text: "Test Email API Text",
html: "<h1> Test Email API HTML </h1>",
auth: {
user: process.env.OWNER_EMAIL,
accessToken: accessToken.token
}
}
const result = await transport.sendMail(mailOptions);
return result;
} catch (error) {
console.log(error.stack);
return error;
}
}
sendMail()
From the official documentation, this is what you need to use:
try {
const accessToken = await oAuth2Client.getAccessToken()
const transport = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: 'OAuth2'
}
});
const mailOptions = {
from: process.env.OWNER_EMAIL,
to: process.env.RECIPIENT,
subject: "Test Email API Subject",
text: "Test Email API Text",
html: "<h1> Test Email API HTML </h1>",
auth: {
user: process.env.OWNER_EMAIL,
accessToken: accessToken.token
}
}
const result = await transport.sendMail(mailOptions);
return result;
} catch (error) {
return error;
}
One error that you have whilst using the google api authentication library is with the token. You were passing the complete token object to the auth configuration of nodemailer instead of just the access token string. Another thing to keep in mind, adding or removing parameters to the auth configuration of nodemailer will lead to different errors.
Using #Morfinismo 's solution in addition to creating a new oAuth on https://developers.google.com/oauthplayground resolved my issue.

Google Suite - Google API access - Client is unauthorized to retrieve access tokens using this method

I am struggling for days with the set up in trying to access GMail Google API from a node.js script using googleapis lib. I succeeded once but I cannot remember how I did it , I tried to reset a project, service-account and G-Suite Domain wide delegation following the Google doc ..
Here is what I did :
In my GCP Console console,
1. Existing organisation : lechorodescharentes.org
2. In this organisation , I created a project : choro-dev
3. In this project I created a service account : choro-dev-postoffice
with choro-dev-postoffice with role TokenGenerator
and enabled the Google Apps Domain-wid Delegation
downloaded the new private key ( JSON file )
4. I enabled the GMail API ( from Libray menu)
In my G-Suite domain's admin console,
5. I added the following copes for this service account's ClientID
"https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/admin.directory.group"
Node.js client
I am trying to access the GMail API with the following Firebase function code using the node.js googleapis library
with server -server authentication using service account
see node.js client code
In this code, I have 2 authentication functions
connect() : to a JSON Web Token
authorize() : to request an access token from the Google OAuth 2.0 Authorization Server
Deployed the Firebase function
Run the function
Got the JWT client displayed
Function ended with error :
{"infos":"unauthorized_client: Client is unauthorized to retrieve access tokens using this method."}
node.js client code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const {google} = require('googleapis');
const nodemailer = require('nodemailer')
const _ = require('lodash');
const KEY = require('./service-key.json');
function connect () {
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
_.values(KEY.scopes), // scopes as authorized in G-Suite admin
KEY.admin_email . // impersonated user
);
jwtClient.authorize((err) => {
if(err) {
reject(err);
} else {
resolve(jwtClient); // returns client
}
});
});
}
// Send a message to the contact user
function sendMessage (client, sender, msg) {
return new Promise((resolve, reject) => {
var transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: 'OAuth2',
user: KEY.admin_email,
serviceClient: KEY.client_id,
privateKey: KEY.private_key,
accessToken: client.access_token,
refreshToken: client.refresh_token,
expires: client.expiry_date
}
});
const mailOptions = {
from: 'SITE CONTACT<' + sender + '>',
to: KEY.contact_email,
subject: 'Message',
text: 'From: ' + sender + '\n\n' + msg,
html: '<h1>Message</h1><p>From: ' + sender + '</p><p>' + msg + '</p>'
};
transporter.sendMail(mailOptions, (err, response) => {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
}
function newContactMessage (from, msg) {
return connect()
.then(client => {
return sendMessage(client, from, msg);
});
}
exports.sendContactMessage = functions.https.onRequest((req, res) => {
const sender_email = 'dufourisabelle#orange.fr';
const sender_msg = 'just a test message to contact the site owner.'
newContactMessage(sender_email, sender_msg).then(() => {
return {status: 200};
}, error => {
return {status: error.status, infos: error.message};
}).then(response => {
return res.send(response);
}).catch(console.error);
});
What could I add to it ? I'll try to re-initiate the all process and pray ... ??

POST http://localhost:3000/api/contactus 404 (Not Found) !!! I do not understand

I'm very new to web development. I'm trying to make a contact us form which when a user clicks submit the contents of the form should be sent to an email.
I followed this youtube video : https://www.youtube.com/watch?v=EPnBO8HgyRU
When I try to post to my backend url (http://localhost:3001/api/contactus) via Postman it does send an email but all the 'req.body's come back undefined in the email. When I post from the frontend contact us form, I get this error in the console: console error
I do not understand what I am doing wrong. As I said I'm new to web development. I'm learning as I do so I have no idea what I don't know hence I believe it is a very simple fix probably but it is beyond me.
Here is my frontend code for the form I think things are going wrong when I bring in 'axios'.
async handleSubmit(e) {
e.preventDefault();
const err = this.validateForm();
if (err) {
if (this.state.userName.length < 2) {
this.setState({
userNameError: true,
});
}
if (this.state.email.indexOf("#") === -1) {
this.setState({
emailError: true,
});
}
if (this.state.subject.length < 2) {
this.setState({
subjectError: true,
});
}
if (this.state.message.length < 2) {
this.setState({
messageError: true,
});
}
}
if (!err) {
const finalForm = {
userName: this.state.userName,
email: this.state.email,
subject: this.state.subject,
message: this.state.message,
};
if (this.state.imagePreviewUrl) {
let newFileName = this.state.fileName.replace(/^C:\\fakepath\\/, "");
finalForm.attachments = [{
filename: newFileName,
content: this.state.imagePreviewUrl.split("base64,")[1],
encoding: 'base64',
}]
}
const contactus = await axios.post('/api/contactus', {finalForm})
this.handleFormClear(e);
this.setState({
formSubmit: true,
});
}
}
Also I want const contactus to take in 'finalForm' for the 'req.body's but for some reason that does not work. Help :(
Here is my backend code:
const express = require('express')
const bodyParser = require('body-parser')
const nodemailer = require('nodemailer')
const Form = express()
Form.use(bodyParser.json())
Form.use(bodyParser.urlencoded({ extended: false}))
Form.post('/api/contactus', (req, res) => {
console.log(req.body);
const htmlEmail = `
<h3>Contact Details</h3>
<ul>
<li>Name:${req.body.userName}</li>
<li>Email:${req.body.email}</li>
<li>Subject${req.body.subject}</li>
</ul>
<h3>Messages</h3>
<p>${req.body.message}</p>
`
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: 'xxxxxxxx#gmail.com',
pass: 'xxxxxxxx',
}
});
const mailOptions = {
from: req.body.userEmail,
to: 'xxxxxxx#gmail.com',
subject: req.body.subject,
text: req.body.userMessage + '\n\n' + "Send your response to: " + req.body.userEmail,
html: htmlEmail,
attachments: req.body.attachments && req.body.attachments
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log(error.message)
} else {
console.log('Message Sent: %s' , info.message)
console.log('Message URL: %s', nodemailer.getTestMessageUrl(info))
}
});
})
const PORT = process.env.PORT || 3001
Form.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`)
})
Please help. Thank you!
New error image: updated error
as I see from your error, you are calling localhost:3000 instead of localhost:3001
I followed this tutorial as well and got a similar problem. What solved it for me was running my client and serve simultaneously using the command npm run dev in the terminal. This is assuming in your projectName\package.json there is a script for dev (its in the tutorial around 5:20 https://www.youtube.com/watch?v=EPnBO8HgyRU&t=542s). If that doesn't immediately solve your problem try resolving any other warnings or errors that appear in your terminal and rerun the command.
The error in the image linked at the very bottom of the original post shows the problem:
Failed to load http://localhost:3001/api/contactus: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 404.
This is a cross-origin resource sharing issue. Essentially, you are trying to access a resource from one domain (localhost:3001) from another (localhost:3000) when you haven't enabled this functionality in your Express JS server.
You can learn how to set up Express JS to allow CORS here:
https://www.w3.org/wiki/CORS_Enabled#In_ExpressJS
you need to use 3001 port
axios.post('http://localhost:3001/api/contactus', {finalForm})
You'll fix it like this:
Go to your CLIENT package.json and below "private":"true", you will write the following: "proxy":"http://localhost:3001",
The result should look like this:
"name": "canitas-rental",
"version": "0.1.0",
"private": true,
"proxy":"http://localhost:3001",
"dependencies": etc

Resources