I would like to send emails in my React Native using Firebase Cloud Functions. Users should be able to send an email for reporting issues/feedback in the app. I have created a text input and a button in my react native app. The user should be able to specify their issue/feedback in the text input box and when they press the button I will receive their response as an email in my gmail or hotmail account. Can I achieve this using onCreate in Firebase Cloud Functions? If so how can I achieve this? What would the onCreate method look like as well as the button function in react native? I am very new to react native and firebase cloud functions. Unfortunately, I haven't seen any links about this.
Thank you.
Please see below:
const nodemailer = require('nodemailer');
const email = functions.config().email_credentials.email;
const password = functions.config().email_credentials.password;
const mailTransport = nodemailer.createTransport(`smtps://${email}:${password}#smtp.gmail.com`);
functions.database.ref('/feedbacks/{currentId}').onCreate((snapshot, context) => {
const feedback = snapshot.val().feedback;
const name = snapshot.val().name;
const mailOptions = {
from: snapshot.val().email,
replyTo: snapshot.val().email,
to: functions.config().email_credentials.email,
subject: `Feedback from `+name,
html: feedback,
};
try {
mailTransport.sendMail(mailOptions);
} catch(error) {
console.log(error);
}
return null;
});
Realtime database:
Your cloud function could look like this:
import * as functions from "firebase-functions";
import admin from "firebase-admin";
import nodemailer from "nodemailer";
const { email, password } = functions.config().gmail;
const mailTransport = nodemailer.createTransport(
`smtps://${email}:${password}#smtp.gmail.com`
);
export default functions.database
.ref("/feedbacks/{uid}")
.onCreate(async (eventSnapshot, context) => {
const data = eventSnapshot.val();
const { feedback } = data;
const mailOptions = {
from: functions.config().email_credentials.email,
replyTo: functions.config().email_credentials.email,
to: snapshot.val().email,
subject: `Feedback from `+name,
html: feedback,
};
await mailTransport.sendMail(mailOptions);
return null;
});
Make sure to save your email credentials under the firebase cloud function configs and NOT in the code. If you put it anywhere in the code it could potentialy been read by someone in some time. This is very importand.
In your Gmail ensure "Unsercure Apps" are enabled. More about it here.
Now if someon adds some data to the path feeds and email will be send.
Don't forget to deplyo your function with the configs.
I setup a Twitch OAuth integration using the Instagram example, now I can login into my app by opening the popup.html page that the example gave me.
Here's my adapted code:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const { AuthorizationCode } = require('simple-oauth2');
const fetch = require('node-fetch');
// Firebase Setup
const admin = require('firebase-admin');
// #ts-ignore
const serviceAccount = require('./service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`,
});
const OAUTH_REDIRECT_URI = `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`;;
const OAUTH_SCOPES = 'user:read:email';
/**
* Creates a configured simple-oauth2 client for Twitch.
*/
function twitchOAuth2Client() {
// Twitch OAuth 2 setup
// TODO: Configure the `twitch.client_id` and `twitch.client_secret` Google Cloud environment variables.
const credentials = {
client: {
id: functions.config().twitch.client_id,
secret: functions.config().twitch.client_secret,
},
auth: {
tokenHost: 'https://id.twitch.tv',
tokenPath: '/oauth2/token',
authorizePath: '/oauth2/authorize',
},
options: {
bodyFormat: 'json',
authorizationMethod: 'body',
},
};
return new AuthorizationCode(credentials);
}
/**
* Redirects the User to the Twitch authentication consent screen. Also the 'state' cookie is set for later state
* verification.
*/
exports.redirect = functions.https.onRequest((req, res) => {
const authorizationCode = twitchOAuth2Client();
cookieParser()(req, res, () => {
const state = req.cookies.__session || crypto.randomBytes(20).toString('hex');
console.log('Setting verification state:', state);
res.cookie('__session', state.toString(), { maxAge: 3600000, httpOnly: true });
const redirectUri = authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
/**
* Exchanges a given Twitch auth code passed in the 'code' URL query parameter for a Firebase auth token.
* The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie.
* The Firebase custom auth token, display name, photo URL and Twitch acces token are sent back in a JSONP callback
* function with function name defined by the 'callback' query parameter.
*/
exports.token = functions.https.onRequest((req, res) => {
const authorizationCode = twitchOAuth2Client();
try {
cookieParser()(req, res, async () => {
try {
console.log('Received verification state:', req.cookies.__session);
console.log('Received state:', req.query.state);
if (!req.cookies.__session) {
throw new Error(
'State cookie not set or expired. Maybe you took too long to authorize. Please try again.'
);
} else if (req.cookies.__session !== req.query.state) {
throw new Error('State validation failed');
}
} catch (error) {
return res.jsonp({ error: error.toString() });
}
let accessToken;
try {
console.log('Received auth code:', req.query.code);
const options = {
client_id: functions.config().twitch.client_id,
client_secret: functions.config().twitch.client_secret,
code: req.query.code,
grant_type: 'authorization_code',
redirect_uri: OAUTH_REDIRECT_URI,
};
console.log('Asking token with options', JSON.stringify(options));
accessToken = await authorizationCode.getToken(options);
console.log('Auth code exchange result received');
const twitchUser = await getTwitchUser(accessToken.toJSON().access_token);
// Create a Firebase account and get the Custom Auth Token.
const firebaseToken = await createFirebaseAccount(twitchUser);
// Serve an HTML page that signs the user in and updates the user profile.
return res.jsonp({ token: firebaseToken });
} catch (error) {
return res.jsonp({ error: error.toString() });
}
});
} catch (error) {
return res.jsonp({ error: error.toString() });
}
});
/**
* Creates a Firebase account with the given user profile and returns a custom auth token allowing
* signing-in this account.
*
* #returns {Promise<string>} The Firebase custom auth token in a promise.
*/
async function createFirebaseAccount(twitchUser) {
// The UID we'll assign to the user.
const uid = `twitch:${twitchUser.id}`;
// Save the access token to the Firebase Database.
const db = admin.firestore();
const databaseTask = db.collection('users').doc(uid).set(twitchUser);
// Create or update the user account.
const userCreationTask = admin
.auth()
.updateUser(uid, {
displayName: twitchUser['display_name'],
photoURL: twitchUser['profile_image_url'],
email: twitchUser['email'],
})
.catch((error) => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
return admin.auth().createUser({
uid: uid,
displayName: twitchUser['display_name'],
photoURL: twitchUser['profile_image_url'],
email: twitchUser['email'],
});
}
throw error;
});
// Wait for all async task to complete then generate and return a custom auth token.
await Promise.all([userCreationTask, databaseTask]);
// Create a Firebase custom auth token.
const token = await admin.auth().createCustomToken(uid);
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
}
async function getTwitchUser(accessToken) {
console.log('Fetching Twitch user with access_token', accessToken);
try {
const response = await fetch('https://api.twitch.tv/helix/users', {
method: 'GET',
headers: {
'Client-Id': functions.config().twitch.client_id,
Authorization: 'Bearer ' + accessToken,
},
});
const data = await response.json();
return { ...data.data[0], access_token: accessToken };
} catch (error) {
console.error(error);
}
}
I'd like, though, to login into Twitch using the firebase.auth().signInWithRedirect() method that I already use for Facebook and Google, unfortunately I can't find any documentation about this, and the Facebook provider source code refers to some externs.* resources so I'm not sure how to adapt it for my own needs.
Right now I have two endpoints/cloud functions: _twitchRedirect and _twitchToken, what should I do to integrate them with signInWithRedirect?
I was similarly curious, so spent a little time playing around with things today.
In short, when using Firebase Auth, I believe the providerId will need to be one of the existing supported providers.
If you upgrade to using the Google Cloud Identity Platform though, I believe you will be able to configure custom providers, and then use this function to authenticate:
https://cloud.google.com/identity-platform
We can see that firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) are used with a number of the providers here, eg.
https://cloud.google.com/identity-platform/docs/web/apple
https://cloud.google.com/identity-platform/docs/web/microsoft
In addition to these provider choices that we get with the standard Firebase Auth, Google Cloud Identity Platform allows us to also add SAML and OpenID Connect (OIDC) integrations:
https://cloud.google.com/identity-platform/docs/web/saml
https://cloud.google.com/identity-platform/docs/web/oidc
When adding a new identity provider using either of these, we are able to specify the 'Provider ID' to use (prefixed with either saml. or oidc.). This custom provider ID is then used with firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) as described above.
For example, if I created a new identity provider with an ID of oidc.foo, my integration code would end up looking like:
const provider = new firebase.auth.OAuthProvider('oidc.foo');
firebase.auth().signInWithPopup(provider)
.then((result) => {
// result.credential is a firebase.auth.OAuthCredential object.
// result.credential.providerId is equal to 'oidc.foo'.
// result.credential.idToken is the OIDC provider's ID token.
})
.catch((error) => {
// Handle error.
});
Based on my understanding of this, I believe we will only currently be able to add custom providers this way if they conform to the OpenID Connect (OIDC) standard (including the OIDC Discovery part, which uses a /.well-known/openid-configuration URL):
Note: If your OIDC provider doesn't comply with the OIDC specification for discovery, it won't work with Identity Platform.
So to my knowledge, the best way to implement 'normal' OAuth2 providers currently is the custom backend function flow you used above (based on the Firebase Auth examples).
As part of figuring this out, I decided to see what would happen if I used a provider ID that didn't match anything configured in my account (this is a fairly verbose step by step, and the main answer is already included above, but this may help provide some more context/help someone out, so including it here)
var provider = new firebase.auth.OAuthProvider("foo.example.com");
firebase
.auth()
.signInWithRedirect(provider)
.then((result) => console.log("OAuthProvider:", result))
.catch((error) => console.log("OAuthProvider::error:", error));
firebase
.auth()
.getRedirectResult()
.then((result) => console.log("RedirectResult:", result))
.catch((error) => console.log("RedirectResult::error:", error));
At first I go this auth/auth-domain-config-required error:
OAuthProvider::error: {
"code": "auth/auth-domain-config-required",
"message": "Be sure to include authDomain when calling firebase.initializeApp(), by following the instructions in the Firebase console."
}
I figured maybe this should be set to the OAuth provider I was wanting to login to, so I set authDomain in my firebase config to foo.myauthprovider.com, but when I called signInWithRedirect, it tried to load the following URL (where the apiKey is the API key of my firebase project), which failed to load:
https://foo.myauthprovider.com/__/auth/handler?apiKey=REDACTED&appName=%5BDEFAULT%5D&authType=signInViaRedirect&providerId=foo.example.com&redirectUrl=http%3A%2F%2Flocalhost%3A3000%2F&v=7.14.5
This /__/auth/handler URL is part of Firebase Auth's reserved URLs, which you can read more about at:
https://firebase.google.com/docs/hosting/reserved-urls#auth_helpers
And is explained a little better in this StackOverflow answer, but is basically what Firebase Auth uses to handle OAuth callbacks to avoid needing to expose sensitive credentials on the frontend, and so users don't need to implement their own handlers all the time):
Why does Firebase auth uses a "middleware" redirect before returning to my app?
Changing authDomain to the actual custom domain of my firebase project fixed that issue, and then resulted in the following auth/operation-not-allowed error when I tried to redirect:
RedirectResult::error: u {code: "auth/operation-not-allowed", message: "The identity provider configuration is not found.", a: null}
I'm working on a chat application and I currently have 6 different chat channels with a different firebase database for each of them. Every channels have firebase rules that define if an user can read and write in this channel. I want to send notifications to users when a new message is posted, but only the users that are part of the specified channel.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const _ = require('lodash');
admin.initializeApp(functions.config().firebase);
exports.sendNewMessageNotification = functions.database.ref('/GeneralMessage').onWrite(event => {
const getValuePromise = admin.database()
.ref('GeneralMessage')
.orderByKey()
.limitToLast(1)
.once('value');
return getValuePromise.then(snapshot => {
console.log(_.values(snapshot.val())[0]);
const { text } = _.values(snapshot.val())[0];
const payload = {
notification: {
title: 'New msg',
body: text,
}
};
return admin.messaging()
.sendToTopic('GeneralMessage', payload);
});
});
That's the code I currently have using firebase cloud function in /functions/index.js. When I send a message in the app, I have this output in firebase : Firebase cloud functions but the notifications isn't working in app.
I'm using firebase cloud function to add users to my database after they register.
I want to add (when user is created) a nickname for example.
In the register form, there is a box for nickname, but how can I send it the firebase function so it would be added to the user in the database?
This is firebase function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const ref = admin.database().ref()
exports.createUserAccount = functions.auth.user().onCreate(event=>{
const uid = event.data.uid
const email = event.data.email
const photoUrl = event.data.photoUrl || 'https://vignette1.wikia.nocookie.net/paulblartmallcop/images/9/9c/Person-placeholder-male.jpg/revision/latest?cb=20120708210100'
const newUserRef = ref.child(`/users/${uid}`)
return newUserRef.set({
photoUrl: photoUrl,
email: email,
})
});
The register form is in another file (register.js), how can I send data from there to the function?
I do not call createUserAccount anywhere, it is triggered when this function happens:
handlePress = (navigation)=>{
if(this.state.password == this.state.verify){
firebaseRef.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).then((newUser)=>{
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Home'})
]
})
navigation.dispatch(resetAction)
}).catch(function(error){
console.log(error);
});
}else{
//password not match, show error.
}
}
Thanks in advance!
You don't have to use a firebase cloud function to add a nickname to the user (If the user is already created).
Just call from your js :
ref.child("users").child(uid).child("nickname").set(nickname);
Other solution
You can create the user only when the nickname is filled. You can save it in the user.displayName and access to it from your onCreate trigger.
exports.createUserAccount = functions.auth.user().onCreate(event=>{
const user = event.data; // The Firebase user.
const uid = user.uid;
const email = user.email;
const nickname = user.displayName;
const photoUrl = user.photoUrl || 'https://vignette1.wikia.nocookie.net/paulblartmallcop/images/9/9c/Person-placeholder-male.jpg/revision/latest?cb=20120708210100';
const newUserRef = ref.child('/users/${uid}');
return newUserRef.set({
nickname: nickname,
photoUrl: photoUrl,
email: email
});
});
I am using Cloud Functions for Firebase and Nodemailer and putting together code to fire welcome email. Following is the code I have:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const nodemailer = require('nodemailer');
const gmailEmail = encodeURIComponent(functions.config().gmail.email);
const gmailPassword = encodeURIComponent(functions.config().gmail.password);
const mailTransport = nodemailer.createTransport(
`smtps://${gmailEmail}:${gmailPassword}#smtp.gmail.com`);
const APP_NAME = 'Test';
exports.sendWelcomeEmail = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
const email = user.email; // The email of the user.
const displayName = user.displayName; // The display name of the user.
return sendWelcomeEmail(email, displayName);
});
function sendWelcomeEmail(email, displayName) {
const mailOptions = {
from: '"Test" <noreply#test.com>',
to: email
};
// The user subscribed to the newsletter.
mailOptions.subject = `Welcome to hell!`;
mailOptions.text = `Hey I hope you will enjoy our service.`;
return mailTransport.sendMail(mailOptions).then(() => {
console.log('New welcome email sent to:', email);
});
}
I do have Allow Less secure Apps turned on
I do see my gmail address and password in the config. I have verified this by typing firebase functions:config:get in the command line
I am getting the following error
I solved it just using the correct email address in from: the one you put in the config.
from: '"Test" <youremail#gmail.com>',