Cloud Functions for Firebase - action on email verified - node.js

Im trying to create a Cloud Function trigger that will execute after email has been verified.
In the Cloud Functions samples I could only find examples on triggers for onCreate and onDelete.
Within the documentation I found something about creating custom action handlers but I don't actually want to replace the standard email verification dialog they have by default, I just want to change the property of a "user" after the email is verified.
Does anyone have any experience with this, and is this even possible? Or is my only option to create my custom verification view/dialog webpage?

I faced this problem and took me a long time to figure it out how to solve so I hope this could help anyone that could get stuck into this too:
1 -> I created a function that was triggered with onCreate() for a new user
exports.sendConfirmationEmail = functions.auth.user()
.onCreate((user) => {
const actionCodeSettings = {
url: 'https://appNextURL.com/',
handleCodeInApp: false//ensure that the link will open into browser
};
return admin.auth().generateEmailVerificationLink(user.email, actionCodeSettings)
.then(async (link) => {
await db.collection('users').doc(user.uid).set({
verificationLink: link,
emailVerified: false
}, {merge: true});
return sendCustomVerificationEmail(user.email, user.displayName, link);
})
.catch((err) => {
console.error("Error:", err);
return Promise.reject(err);
});
});
The generateEmailVErificationLink() will generate the link based on the link we will save on step 3.
The function sendCustomVerificationEmail() is just an internal function that overcomes the standard email firebase send
2 -> Then I created a function that will receive a manual http trigger with the data that would be generated automatically by firebase when sending an automatic email
exports.verifyEmail = functions.https.onRequest((req, res) => {
const {mode, oobCode, apiKey, continueUrl, lang} = req.query;
const link = "https://us-central1-projectId.cloudfunctions.net/verifyEmail/?mode=" + encodeURIComponent(mode) + "&oobCode=" + encodeURIComponent(oobCode) + "&apiKey=" + encodeURIComponent(apiKey) + "&continueUrl=" + encodeURIComponent(continueUrl) + "&lang=" + encodeURIComponent(lang);
return db.collection("users")
.where("verificationLink", "==", link)
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (user) {
const userData: UserData = user.data();
console.log("email verified: ", userData.userId);
return admin.auth().updateUser(userData.userId, {
emailVerified: true
}).then(function (userRecord) {
return db.collection('users').doc(userData.userId).set({emailVerified: true}, {merge: true});
});
});
return res.sendStatus(200).end();
}).catch(function (err) {
console.log("error:", err);
return res.sendStatus(403).end();
});
});
As I saved the link in the onCreate() I can now query that link to get who is the user that I am authenticating
3 -> the third step is to change the link in to Firebase Authentication template to the link generated into the 2nd step:
Navigate to Authentication>Templates:
Click on edit icon> Click on customize action URL:
Navigation
Paste the link generated into the step 2 and save:
Save link
Now every link generated automatically will go trought that function you created on step 2 and you will be able to handle the actions you want to happen.
I hope I could be clear.

you could still check for the verification status (at least) on Android with interface UserInfo method isEmailVerified(); eg. in order to send another verification email upon successful login, in case the current user has not yet verified the email address - and show the login screen again. one could as well HTTP trigger a cloud function or update values in the Firebase directly, through the client library. this might also apply to other platform clients, where one can check for the verification status.
this would not be exactly the event when the email just had been verified, but upon each single login attempt one knows the verification status and this value might be merely relevant on the client-side.

Create a publish button so your users trigger your cloud function
Instead of firing the cloud function immediately upon auth.emailVerified, I'm giving my users a 'Publish Profile' button which fires an http cloud function (passing in user.uid). This function looks up the user auth using the passed in user.uid
if user.uid && auth.emailVerified
write auth.emailVerified to each user.post
By default, post document "post.emailVerified" fields start out false, and cannot be written to except via adminFirestore in a cloud function.

Related

Wait for firebase email success/failure before sending response

In my firebase app i've made it so a user can send an email to another user. I've got a node server listening for these requests and sending them through FireBase and the Trigger Email extension. Once i insert a document like this --
admin
.firestore()
.collection("mail")
.add({
to: `${req.body.toEmail}`,
message: {
subject: "here is a subject",
html: `here is a message`,
},
})
.then((response) => {
return res.status(200).json({
message: "email sent!"
});
Trigger Email does some magic (I think it's utilizing a cloud function) to then begin to update the document once it's created.
The document will get updated with a property called state that will either be ERROR or SUCCESS. I need to wait until that field gets added and updated before returning a response to the client.
Anyone dealt with this before?
The Trigger Email extension indeed use Cloud Functions and all functions instances run independent of each other. One workaround this would be to return ID of new document created in mail collection and listen changes to that document on client side:
return res.status(200).json({
message: response.id // ID of new document
});
Then on your client side you can listen for any updates to this document:
db.collection("mail").doc("THE_MAIL_ID")
.onSnapshot((doc) => {
const { state } = doc.data()
if (state === "SUCCESS") {
alert("Email Sent")
} else if (state === "ERROR") {
alert("Failed to send email")
} else {
alert("Email yet to be sent")
}
});
If the "state" field is missing then the trigger email extension has no yet completed and updated back the mail document so you'll have to wait until another update is received by the listener. You could use the same logic in the first function itself and return the response once the document is updated but it'll just lead to additional Cloud function time charges might result in an error just in case you hit the timeout for some reason.

Can we access request header on Firebase Auth triggers i.e onCreate?

How can one access the IP and country in firebase auth onCreate trigger? Is there any other way to get this info?
For the front end, I'm using firebase-ui and for user register using the following method.
app.auth().createUserWithEmailAndPassword(email,password)
Cloud Functions code:
exports.processSignUp = functions.auth.user().onCreate(async user => {
return admin.auth().setCustomUserClaims(user.uid, {
clientIp: 'xxx.xxx.xx.xx', // required here headers['x-forwarded-for']
country: 'countryName' // required here headers['x-appengine-country']
})
.then(() => {
})
.catch(error => {
console.log(error);
});
});
The Cloud Function is triggered by the Google/Firebase infrastructure, and thus the headers are the values from that infrastructure. No information about the user/device that called createUserWithEmailAndPassword is available beyond what is passed in the user object.
If you need more information, consider implementing a callable function that you call directly from your code passing both the credentials (that you now pass to createUserWithEmailAndPassword) and the additional information you need, and then creating the user in the Cloud Functions code itself.

How to add an extension number in twilio click to call using node js

As per the documentation here & the github source code here, I have cloned the application, its working perfectly.
Suppose if my sales person having some extension then how can I give that extension in this script. Normally, using senddigit I can pass the extension in twilio but I dont know how to implement that with this salesNumber.
twilioClient.createCall(salesNumber, phoneNumber, headersHost)
.then((result) => {
response.send({message: result});
})
.catch((error) => {
response.status(500).send(error);
});
Please some one help on this.
I think you're looking at the wrong code snippet here. The code above doesn't call the Twilio client directly. Instead, it calls the helper function from this file to initiate the call.
Once the user picks up, they will be connected to the sales person via TwiML in this function:
voiceResponse: (salesNumber, Voice = VoiceResponse) => {
let twimlResponse = new Voice();
twimlResponse.say('Thanks for contacting our sales department. Our ' +
'next available representative will take your call. ',
{ voice: 'alice' });
twimlResponse.dial(salesNumber);
return twimlResponse.toString();
}
In this function, you'll be able to send digits along as mentioned here.

Bot Framework Node.js ad hoc message TO A SPECIFIC USER

I have been staring at this for hours and can't find a solution and that is even though by all suggestions it SHOULD be quite easy - https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-proactive-messages.
I have created a simple code which will "register" the user and save their data in my cosmosDatabse on Azure. That works perfectly.
//ON "register" SAVE USER DATA AND SAY REGISTERED MESSAGE
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
//REGISTERED MESSAGE
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
But how can I then access that specific user and send him a message if, say, a condition is met? (in fact on HTTP request)
I am fairly certain we have to write the conversation ID or user ID somewhere. The question is where?
function startProactiveDialog(address) {
bot.beginDialog(address, "A notification!");
}
This is how simple I think it should be. But where do you specify the user then?
You've saved the address of the user inside of your database by saving it to session.userData.savedAddress. When the event triggers, perform a query to your database that checks for the users that meet two criteria.
They're registered to listen for the event
Their address has been saved inside of the database.
In your case, you can save a property to the session.userData object, a property that lists which events they're listening for. If you just need to send a message to the user, then you can simply use bot.loadSession(savedAddress) to ping the user.
Edit:
So instead of looking specifically by user ID, you should send a query to your CosmosDB that looks for entries that have a "listen-to" Boolean-type flag corresponding to the event.
You're not worrying about the user ID at first, you're just retrieving all entries with a query that would (broadly speaking) look like this:
SELECT * FROM BotState WHERE data LIKE 'listenForEvent=1.
So to setup your session.userData so that the above theoretical query would work, you would need to modify that snippet of code in your question to something like the following:
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
session.userData.listenForEvent = 1 // Our property we're going to look for.
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
Actually, the savedAddress should be an instance of IAddress, and also, the function loadSession(address: IAddress, callback: (err: Error, session: Session) => void): void; and address(adr: IAddress): Message; under Message class all require IAddress as the parameter.
So first of all, you should save the entire address json object in cosmosDB for later using.
As botbuilder for Node.js is built on Restify or Express, you can build an addition route for your user to trigger and send proactive messages. The work flow could be following:
Guide user to register & Save the user's address object with the account mapping in your DB
Create a Route in Restify or Expressjs for trigger the proactive message:
server.get('/api/CustomWebApi', (req, res, next) => {
//find the user's address in your DB as `savedAddress`
var msg = new builder.Message().address(savedAddress);
msg.text('Hello, this is a notification');
bot.send(msg);
res.send('triggered');
next();
}
);
or if you want to leverage loadSession
server.get('/api/CustomWebApi', function (req, res, next) {
bot.loadSession(savedAddress, (err, session) => {
if (!err) {
session.send('Hello, this is a notification')
session.endConversation();
}
})
res.send('triggered');
next();
});
I created a users.json file, to which I save all the users. It works the way I need it to. I guess database would be better, but I don't really have a clue where to begin with that. Database is a whole new chapter I have not encountered yet, so it doesn't make sense to work on it when the project needs are resolved.

Facebook API Nodejs

I'm trying post a message as admin of a facebook page, but my code keeps posting as my personal account to the "wall" of the page, not as the page itself.
When authorizing my application I ask for the right permissionscopes, and accept them. Although, when I check my permissions with /{USER-ID}/permissions, it says that I've denied the application to manage my pages. Any ideas why?
Here's my code for posting:
var parameters = {};
parameters.message = "Hello world, this is dog.";
parameters.access_token = req.session.access_token;
FB.api('{PAGE-ID}/feed', 'post', parameters, function (result) {
if (!result) {
return res.send(500, 'error');
} else if (result.error) {
if (result.error.type == 'OAuthException') {
result.redirectUri = FB.getLoginUrl({ scope: 'publish_actions,manage_pages,publish_pages', state: encodeURIComponent(JSON.stringify(parameters)) });
}
console.log(result);
return res.send(500, result);
}
res.send(result);
});
And yes, of course I use my page id instead of the placeholder in my code above.
*Update: It seems like the permissions aren't even asked for when authorizing the application. I added the email scope to the list and it appears when I re-authorize the application.
Could this have anything to do with my application not beeing approved?
"Submit for Login Review
Some of the permissions below have not been approved for use by Facebook.
Submit for review now or read more."

Resources