I deployed my Firebase code that sends a test email using this guide, and (finally) successfully invoked the code from within my React Native app by trying to follow the example snippet in the React Native Firebase docs.
However, when I run the function, React Native gives back a Possible Unhanlded Promise Rejection error with the following logs (which shows the detailed Component stack), and I don't receive the test email:
WARN Possible Unhandled Promise Rejection (id: 0):
Error: NOT FOUND
Error: NOT FOUND
http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:161257:60
invokePassiveEffectCreate#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:19749:32
invokeGuardedCallbackProd#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:6272:21
invokeGuardedCallback#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:6376:42
flushPassiveEffectsImpl#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:19819:36
unstable_runWithPriority#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:54580:30
http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:19604:36
workLoop#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:54531:48
flushWork#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:54506:28
_flushCallback#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:54216:24
_callTimer#http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=org.coreApp2:28655:17
And here are the function logs provided in the Firebase console:
3:56:49.762 PM
sendMail
got here
3:56:55.222 PM
sendMail
{"#type":"type.googleapis.com/google.cloud.audit.AuditLog","status":{},"authenticationInfo":{"principalEmail":"MyEmail#gmail.com"},"serviceName":"cloudfunctions.googleapis.com","methodName":"google.cloud.functions.v1.CloudFunctionsService.UpdateFunction","resourceName":"projects/hb-warranty/locations/us-central1/functions/sendMail"}
In my actual logs, {"principalEmail":"MyEmail#gmail.com"} shows the actually valid Gmail address from my code.
I feel like I'm missing some error handling in my code, and I'm not sure how to debug my code using the current logs.
What changes should be made to my code to improve error handling, and successfully send an email via my Firebase Cloud Function?
Backend Cloud function (/MyApp/functions/index.js):
const functions = require("firebase-functions");
const admin = require('firebase-admin')
const nodemailer = require('nodemailer')
const cors = require('cors')({origin: true});
admin.initializeApp();
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'MyEmail#gmail.com',
pass: 'MyPass'
}
});
console.log('got here');
// exports.sendMail = functions.https.onRequest((req, res) => {
// cors(req, res, () => {
exports.sendMail = functions.https.onCall((data) => {
cors(data, () => {
const dest = data.dest;
const mailOptions = {
from: 'Test Email <MyEmail#gmail.com>',
to: dest,
subject: 'I am squash',
html: `<p style="font-size: 16px;">Squash Richard</p>
<br />
<img src="https://i.etsystatic.com/6129578/r/il/858dbc/703555889/il_570xN.703555889_14z1.jpg" />`
};
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return erro.toString();
}
return 'Sent';
});
});
});
Code from this tutorial. In my actual code, user: MyEmail#gmail.com and from: 'Test Email <MyEmail#gmail.com>' actually use a valid Gmail account, and the account authorizes less secure apps.
Frontend React Native (/MyApp/Email.js):
import React, {useEffect} from 'react';
import { View, Text, StyleSheet } from 'react-native';
import functions from '#react-native-firebase/functions';
function EmailScreen() {
useEffect(() => {
functions().httpsCallable('sendMail')({dest: 'MyOtherEmail#gmail.com'})
})
return(
<View>
<Text>This is the email screen.</Text>
</View>
)
}
export default EmailScreen;
Code from the docs linked above
Gmail uses two factor authentication when Gmail servers doesn't recognize the usual ip address from which the account is accessed from. When deploying to the firebase server this can occur as an issue trying to access your account from a unrecognized ip address. The code will still work running in your local machine. To send transactional emails you have to use sendgrid or zoho mails and there are several other options also
Related
I'm running a Firebase cloud function whose purpose is to handle the backend of a "contact me" form. The Express framework is being used for middleware functions. When the submit button is clicked, a POST request is made to the /submit endpoint.
index.js in the functions folder is as below:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const bodyParser = require("body-parser");
const express = require("express");
const emailRoute = require("./routes/email");
// Initialize Firebase in order to access its services.
admin.initializeApp();
const app = express();
// Automatically allow cross-origin requests.
app.use(cors({ origin: true }));
app.use(bodyParser.urlencoded({extended: false}));
app.use(emailRoute);
// Expose Express API as a single Cloud Function.
exports.app = functions.https.onRequest(app);
The imported router from email.js is as follows:
const nodemailer = require("nodemailer");
const express = require("express");
const router = express.Router();
router.post("/submit", (req, res) => {
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "email#gmail.com",
pass: "password",
},
});
const myEmail = {
to: "myemail#gmail.com",
subject: `A new message from ${req.body.name}`,
text: `${req.body.name} sent the following message:
\n\n ${req.body.message}
\n\n Senders email: ${req.body.email}`,
};
const sendersEmail = {
to: req.body.email,
subject: "A copy of your message to me",
text: `You just sent me the following message:\n\n${req.body.message}`,
};
console.log("SUBMIT REQUEST PROCESSING");
transporter.sendMail(myEmail);
transporter.sendMail(sendersEmail);
res.redirect("/#contact");
console.log("PROCESSING COMPLETE");
});
module.exports = router;
There is no issue when running this in the local environment - the email gets sent to both parties. However, when run in the hosted environment as a Firebase function the following error is thrown: Error: Process exited with code 16 (as displayed in the function logs section of the Firebase console). A previous SO answer indicates an uncaughtException or unhandledRejection.
Before the error, both console.log() statements are logged. Then the function finishes with a 302 status code (as it does when run locally and successfully). After that, there is the unhandled rejection followed by Error: Invalid login and a link to my Google account and a statement "Please log in via your web browser and then try again".
Could this be a Firebase security measure against automated mailing that nodemailer is attempting to execute?
I needed to both enable less secure apps and display unlock captcha on the gmail account, only the former of which I had previously done.
I also needed to set the gmail.email and gmail.password Google cloud environment variables. This can be done with the following shell command: firebase functions:config:set gmail.email="myusername#gmail.com" gmail.password="secretpassword"
A great resource I found on this is an official Firebase function sample that utilises Nodemailer, where the above is covered.
The issue is in logging to your Google account. Have you enabled less secure apps in your Google account? Or better I would recommend to use some other authentication, as described here:
https://support.google.com/a/answer/176600?hl=en
The code snippet shown below is receiving a "Forbidden" error:
Error: Forbidden
at axios.then.catch.error (node_modules/#sendgrid/client/src/classes/client.js:105:29)
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
import * as functions from 'firebase-functions';
import * as sgMail from '#sendgrid/mail';
export const onAuctionUpdate = functions.firestore
.document('auctions/{id}')
.onUpdate(async (change, context) => {
sgMail.setApiKey(
'SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
);
const emailMsg = {
to: 'xxxxxxxxxxxxxxxxxxxxxxxx',
from: 'xxxxxxxxxxxxxxxxxxxxxxxx',
subject: 'xxxxxxxxxxxxxxxxxxxxxxxx',
html: 'Hello <strong>body</strong>'
};
return sgMail.send(emailMsg);
});
Although masked in the code snippet, as the "from" email address, I am using the email account I used when registering with SendGrid.
Any suggestions would be greatly appreciated.
SendGrid provides 2 options for sending emails, using API and SMTP.
I was using an API Key generated from the SMTP option and should have been using the one from the API option.
Thanks
I've a project in which I used to authenticate the users with firebase-auth.In my project users can not create their accounts on their own.Only admin have the privilege to add the user accounts.
In order to use onAuthStateChanged() function I must use firebase-auth in my page.But the issue is because of using firebase-auth on client side one can esaily create accounts by running createUserWithEmailAndPassword() function on the console without having the admin privilege.
Now how can I restrict the people from using createUserWithEmailAndPassword() function on client side?
The only way you can stop clients apps from creating accounts is to disable all authentication providers for your project in the Firebase console. You could write an auth onCreate Cloud Function that attempts to figure out if a new account was created by client or admin code if you want to try to delete it immediately.
I think you can add a claim once the user is added, via a cloud function, which requires authorization, so that if the user doesn't have that claim he can't use the app or can't login.
In 2022 with Firebase Auth with Identity Platform and blocking functions, we can accomplish that the following way:
Create an HTTP function that receives email, password and displayName, and creates user using firebase-admin:
import { https } from 'firebase-functions';
import { getAuth } from 'firebase-admin/auth';
import cors from 'cors';
const auth = getAuth();
// Register an HTTP function with the Functions Framework
export const signupUser = https.onRequest((req, res) => {
const options = {
origin: 'http://localhost:3000'
};
cors(options)(req, res, () => {
console.log('all good');
auth
.createUser({
email: 'example#email.com',
emailVerified: false,
password: 'secretPassword',
displayName: 'John Doe',
disabled: false,
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch((error) => {
console.log('Error creating new user:', error);
});
// Send an HTTP response
res.send('OK');
});
});
Modify response and origin in CORS as you need.
Now create a blocking beforeCreate function and check for user's display name, if there is no display name, throw an error:
import { auth } from "firebase-functions";
import { initializeApp, applicationDefault } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import postmark from 'postmark';
const app = initializeApp({
credential: applicationDefault(),
projectId: 'your_project_id',
});
const tnc = getAuth(app);
export const signUp = auth
.user().beforeCreate((user, context) => {
if (!user.displayName) {
throw new auth.HttpsError('permission-denied');
}
});
This will work because there is no way to include "display name" when signing up via client side
So you, in short, point is to create a Cloud Function that will register users and make sure to add the check to beforeCreate for something that you know is only possible to do on server-side via firebase-admin sdk.
EDIT: CORRECTION
Just found out you can now disable client side signup from Firebase Console if you have Auth + Identity Platform
I am using Firebase functions with nodemailer to send email from a contact form on my website.
I am on the free plan, and as I understood Gmail API is considered to be a Google service and not general internet request, so it should work fine.
Here is my Typescript code
import * as functions from 'firebase-functions';
import * as nodemailer from 'nodemailer';
import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore';
export const sendMessage = functions.firestore.document('/emails/{pushKey}').onCreate((snap: DocumentSnapshot) => {
const form = snap.data();
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: 'myemail#gmail.com',
clientId: 'xxxxxxxxxxxxx',
clientSecret: 'xxxxxxxxx',
refreshToken: 'xxxxxxxxx'
},
debug: true
});
const mailOptions = {
from: 'sender#gmail.com',
to: 'receiver#gmail.com',
subject: `Message from ${form.name} <${form.email}>`,
html: form.message
};
return transporter.sendMail(mailOptions)
.then(() => {
console.log('Email sent.');
}).catch((err) => {
console.log(err);
});
});
However, I get in the console log
sendMessage Function execution took 2908 ms, finished with status: 'ok'
sendMessage Function returned undefined, expected Promise or value
sendMessage Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions
sendMessage Function execution started
Am I using any external network here!
The following code works
import * as functions from 'firebase-functions';
import * as nodemailer from 'nodemailer';
import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore';
const gmailEmail = encodeURIComponent(functions.config().gmail.email);
const gmailPassword = encodeURIComponent(functions.config().gmail.password);
const mailTransport = nodemailer.createTransport(`smtps://${gmailEmail}:${gmailPassword}#smtp.gmail.com`);
export const sendMessage = functions.firestore.document('/emails/{pushKey}').onCreate((snap: DocumentSnapshot) => {
const form = snap.data();
const mailOptions = {
to: 'receiver#gmail.com',
subject: `Message from ${form.name} <${form.email}>`,
html: form.message
};
return mailTransport.sendMail(mailOptions)
.then(() => console.log('worked!'))
.catch(e => console.log(e));
});
But this way is unsecured, and It requires me to allow less secure apps on my Gmail account.
How can I use Gmail and OAuth2 with the free Firebase plan?
So you cannot make outbound requests on the Firebase free plan. You need to upgrade your plan to make outbound requests, and I believe since you are using 'nodemailer' that is the part trying to make an outbound request. Here is another question with comments about upgrading to use a mail service: How can I use nodemailer with Cloud Functions for Firebase?
I believe Firebase also made the Blaze plan free until you go past the quota of the free plan, so it really won't cost anything until you step past the free quota specified in the pricing (https://firebase.google.com/pricing/).
I'm trying to send the verification email after the user is created. Since there's no way on Firebase itself, I'm trying it with cloud functions.
I cannot really find a lot of documentation about it. What I tried to do so far is:
exports.sendEmailVerification = functions.auth.user().onCreate(event => {
return user.sendEmailVerification()
});
But I get the error that user is not defined.
How can I create this function?
Thanks!
There are two possibilities to send an "email verification" email to a user:
The signed-in user requests that a verification email be sent. For that, you call, from the front-end, the sendEmailVerification() method from the appropriate Client SDK.
Through one of the Admin SDKs, you generate a link for email verification via the corresponding method (e.g. auth.generateEmailVerificationLink() for the Node.js Admin SDK) and you send this link via an email sent through your own mechanism. All of that is done in the back-end, and can be done in a Cloud Function.
Note that the second option with the Admin SDKs is not exactly similar to the first option with the Client SDKs: in the second option you need to send the email through your own mechanism, while in the first case, the email is automatically sent by the Firebase platform
If you'd like that ability to be added to the Admin SDK, I'd recommend you file a feature request.
This is how I implemented it successfully using Firebase cloud functions along with a small express backend server
Firebase Cloud function (background) triggered with every new user created
This function sends a "user" object to your api endpoint
const functions = require('firebase-functions');
const fetch = require('node-fetch');
// Send email verification through express server
exports.sendVerificationEmail = functions.auth.user().onCreate((user) => {
// Example of API ENPOINT URL 'https://mybackendapi.com/api/verifyemail/'
return fetch( < API ENDPOINT URL > , {
method: 'POST',
body: JSON.stringify({
user: user
}),
headers: {
"Content-Type": "application/json"
}
}).then(res => console.log(res))
.catch(err => console.log(err));
});
Server Middleware code
verifyEmail here is used as middleware
// File name 'middleware.js'
import firebase from 'firebase';
import admin from 'firebase-admin';
// Get Service account file from firebase console
// Store it locally - make sure not to commit it to GIT
const serviceAccount = require('<PATH TO serviceAccount.json FILE>');
// Get if from Firebase console and either use environment variables or copy and paste them directly
// review security issues for the second approach
const config = {
apiKey: process.env.APIKEY,
authDomain: process.env.AUTHDOMAIN,
projectId: process.env.PROJECT_ID,
};
// Initialize Firebase Admin
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
// Initialize firebase Client
firebase.initializeApp(config);
export const verifyEmail = async(req, res, next) => {
const sentUser = req.body.user;
try {
const customToken = await admin.auth().createCustomToken(sentUser.uid);
await firebase.auth().signInWithCustomToken(customToken);
const mycurrentUser = firebase.auth().currentUser;
await mycurrentUser.sendEmailVerification();
res.locals.data = mycurrentUser;
next();
} catch (err) {
next(err);
}
};
Server code
// Filename 'app.js'
import express from 'express';
import bodyParser from 'body-parser';
// If you don't use cors, the api will reject request if u call it from Cloud functions
import cors from 'cors';
import {
verifyEmail
} from './middleware'
app.use(cors());
app.use(bodyParser.urlencoded({
extended: true,
}));
app.use(bodyParser.json());
const app = express();
// If you use the above example for endpoint then here will be
// '/api/verifyemail/'
app.post('<PATH TO ENDPOINT>', verifyEmail, (req, res, next) => {
res.json({
status: 'success',
data: res.locals.data
});
next()
})
This endpoint will return back the full user object and will send the verification email to user.
I hope this helps.
First view the documentation by Firebase here.
As the registration phase completes and result in success, trigger the following function asynchronously :
private void sendVerification() {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
user.sendEmailVerification().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
system.print.out("Verification Email sent Champion")
}
}
});
}
The user will now be provided with a verification Email. Upon clicking the hyper linked the user will be verified by your project server with Firebase.
How do you determine whether or not a user did verify their Email?
private void checkEmail() {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user.isEmailVerified()) {
// email verified ...
} else {
// error : email not verified ...
}
}
Sadly, you may not customize the content/body of your verification Email ( I have been heavily corresponding with Firebase to provide alternative less hideous looking templates ). You may change the title or the message sender ID, but that's all there is to it.
Not unless you relink your application with your own supported Web. Here.
Since the release of the Version 6.2.0 of the Node.js Admin SDK on November 19, 2018 it is possible to generate, in a Cloud Function, a link for email verification via the auth.generateEmailVerificationLink() method.
You will find more details and some code samples in the documentation.
You can then send an email containing this link via Mailgun, Sendgrid or any other email microservice. You'll find here a Cloud Function sample that shows how to send an email from a Cloud Function.
If you want to let Admin SDK do it, as of now there is no option other than generating the email verification link and sending with your own email delivery system.
However
You can write a REST request on cloud functions and initiate the email verification mail this way.
export async function verifyEmail(apiKey : string, accessToken : string) {
// Create date for POST request
const options = {
method: 'POST',
url: 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode',
params: {
key: apiKey
},
data: {
requestType : "VERIFY_EMAIL",
idToken : accessToken
}
};
return await processRequest(options); //This is just to fire the request
}
As soon as you signup, pass the access token to this method and it should send a mail to the signup user.
apiKey : Is the "Web API key" listed in General tab of your project settings in firebase console
access token : Access token of the current user (I use signup rest api internally so there is an option to request token in response)