Trying to run authy-client with Firebase Cloud Functions - firebase

I've been trying to get authy-client to run with Firebase Cloud Functions but I keep running into a ValidationFailedError. I've been testing the examples the author supplied at https://www.npmjs.com/package/authy-client with no luck.
For my Firebase function, I've been trying this:
const Client = require('authy-client').Client;
const client = new Client({ key: 'my API key here' });
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify/status')
.onCreate(event => {
const sender = client.registerUser({
countryCode: 'US',
email: 'test#tester.com',
phone: '4035555555'
}).then( response => {
return response.user.id;
}).then( authyId => {
return client.requestSms({ authyId: authyId });
}).then( response => {
console.log(`SMS requested to ${response.cellphone}`);
throw Promise;
});
return Promise.all([sender]);
});
But I get this error:
ValidationFailedError: Validation Failed
at validate (/user_code/node_modules/authy-client/dist/src/validator.js:74:11)
at _bluebird2.default.try (/user_code/node_modules/authy-client/dist/src/client.js:632:31)
at tryCatcher (/user_code/node_modules/authy-client/node_modules/bluebird/js/release/util.js:16:23)
at Function.Promise.attempt.Promise.try (/user_code/node_modules/authy-client/node_modules/bluebird/js/release/method.js:39:29)
at Client.registerUser (/user_code/node_modules/authy-client/dist/src/client.js:617:34)
at exports.sendVerificationCode.functions.database.ref.onCreate.event (/user_code/index.js:24:25)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:59:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:53:36)
at /var/tmp/worker/worker.js:695:26
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
I am new to Firebase' cloud functions so I may be overlooking something obvious but from what I've read, then() statements and the function itself needs to return/throw a Promise so I hacked that together.
I've also made sure that authy-client is included in the dependencies in the package.json file.
I originally signed up for the Twilio trial which gave me an option to create an application with Authy. Needing to be sure, I also signed in to Authy to check if the API key is the same, and they are. So I don't think the validation error is due to the API key.
Any help would be appreciated. Thank you.

Thank you all for your answers. I was finally able to figure out a solution. It had nothing to do with the code, well throw Promise was a mistake but it was apparently a problem with DNS resolutions that was resolved when I set up billing on Firebase.
As for my final code, as I originally wanted to use authy-client for phone verification, it looks something like this:
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify')
.onCreate(event => {
const status = event.data.child('status').val();
const phoneNum = event.data.child('phone').val();
const countryCode = event.data.child('countryCode').val();
var method;
if(status === 'pendingSMS')
method = 'sms';
else
method = 'call';
// send code to phone
const sender = authy.startPhoneVerification({ countryCode: countryCode, phone: phoneNum, via: method })
.then( response => {
return response;
}).catch( error => {
throw error;
});
return Promise.all([sender]);
});

throw Promise doesn't really mean anything. If you want to capture problems that can occur anywhere in your sequence of promises, you should have a catch section. The general form is this:
return someAsyncFunction()
.then(...)
.then(...)
.catch(error => { console.error(error) })
That doesn't necessarily fix your error, though. That could be coming from the API you called.

Twilio developer evangelist here.
Doug is right about throw Promise, that will definitely need to be changed.
However, the error seems to be coming before that and from the API. Specifically, the stack trace tells us:
at Client.registerUser (/user_code/node_modules/authy-client/dist/src/client.js:617:34)
So the issue is within the registerUser function. The best thing to do is try to expose more of the error that is being generated from the API. That should give you the information you need.
Something like this should help:
const Client = require('authy-client').Client;
const client = new Client({ key: 'my API key here' });
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify/status')
.onCreate(event => {
const sender = client.registerUser({
countryCode: 'US',
email: 'test#tester.com',
phone: '4035555555'
}).then( response => {
return response.user.id;
}).then( authyId => {
return client.requestSms({ authyId: authyId });
}).then( response => {
console.log(`SMS requested to ${response.cellphone}`);
}).catch( error => {
console.error(error.code);
console.error(error.message);
throw error;
});
});
Let me know if that helps at all.

Related

How to load 2 different Firestore docs in one 'onUpdate' Cloud Function?

I am trying to make an "onUpdate" function that loads the document that has been updated. Then I want to load another document using the data received by the wildcards. So to summarize I want to access the document that was updated and one more that is in the same collection.
I want : /userProfiles/{doc1}/employees/{doc2} AND /userProfiles/{doc1}.
I can get them both but when I try to use the data from one, it doesn't read the previous data and gives me a ReferenceError.
The end goal is to use both these docs to send an email with nodemailer. Thanks for any help.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require('nodemailer');
admin.initializeApp();
exports.testLog = functions.firestore
.document('/userProfiles/{doc1}/employees/{doc2}')
.onUpdate((change, context) => {
var info = [];
const doc1 = context.params.doc1;
const doc2 = context.params.doc2;
const db = admin.firestore();
return (
db
.collection("userProfiles")
.doc(`${doc1}`)
.get()
.then(doc => {
var email = doc.data().email;
var phone = doc.data().phone;
info.push(doc.data());
console.log(email, phone); // sees and gets info
return email, phone;
}),
db
.collection("userProfiles")
.doc(`${doc1}`)
.collection(`employees`)
.doc(`${doc2}`)
.get()
.then(doc => {
info.push(doc.data());
var Status = doc.data().Status;
console.log(phone, `${Status}`); //phone is undefined
if (`${Status}` === "Alarm") {
// replace with variables from the users settings page
console.log(`${info.phone}`); // phone is undefined
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: "xxxxxx#gmail.com",
pass: "xxxxxxxxxx"
}
});
// send mail with defined transport object
let mailOptions = {
from: '"Fred Foo 👻" <foo#example.com>',
to: `${info.phone}`, // tried phone as well
subject: "Hello ✔",
text: "216+?",
};
transporter.sendMail(mailOptions, error => {
if (error) {
return console.log(error);
} else {
return console.log("message sent");
}
});
}
console.log(Status);
// return
return console.log("im after the if statement. No alarm triggered");
})
.then(message => console.log(message.sid, "success"))
.catch(err => console.log(err))
);
});
So I want to get the phone number and the Status in these 2 images
The error that is returned:
ReferenceError: phone is not defined
There are two things that aren't quite working the way you expect leading to your problem:
The handling of promises isn't really passing data the way you expect -- in particular, the variables phone and email exist only in one promise handler, they aren't global in scope, so phone and email aren't being passed down the promise chain.
You don't actually need to ever read the second document, as the content is passed to you in the function itself. This actually greatly simplifies the overall thing you are doing, and makes dealing with the first point nearly trivial, since you can skip the second database call.
Look at this code where I have omitted the messaging code for clarity and just left in place most of the log messages:
exports.firestoreOnUpdateTest = functions.firestore
.document('/userProfiles/{doc1}/employees/{doc2}')
.onUpdate((change, context) => {
// var info = []; I have removed this list, it is not necessary
const doc1 = context.params.doc1;
// no need to get the doc2 parameter, as we are handed the doc itself by the function call.
const doc2content = change.after.data();
const db = admin.firestore();
return (
db
.collection("userProfiles")
.doc(`${doc1}`)
.get()
.then(doc => {
const doc1content = doc.data();
const email = doc1content.email;
const phone = doc1content.phone;
console.log(email, phone); // sees and gets info
console.log(`No need to fetch doc2, as I already have it: ${JSON.stringify(doc2content)}`);
const Status = doc2content.Status;
console.log(`email for user is still: ${email}`); // email is now defined
console.log(phone, `${Status}`); // phone is now defined
if (`${Status}` === "Alarm") {
console.log(`${phone}`); // phone is now defined
return console.log('message would be sent here - code omitted')
}
console.log(Status);
return console.log("im after the if statement. No alarm triggered");
})
.catch(err => console.error(err))
);
});
In the new version, we just store the content from the document that triggered us, including the Status parameter. We then fetch the document with the content we need -- at the higher level in the tree. Once that document is returned, we just process it and combine with the data from doc2. All the fields are now defined (assuming, of course, the database objects are well-formed).
Your messaging code would be re-inserted right were the obvious log message is.
Finally, the info list I don't think is necessary now, so I've removed it. Instead, I recommend you build what you need as you construct the message itself from the data already on hand. That said, your original code wasn't accessing it correctly (that is, as a list) anyway and may have been confusing you further.
Finally, I haven't addressed the use of the Nodemailer module as the question focused primarily on the undefined fields, but I suspect your original code may not be entirely correct either -- as it doesn't either return a promise back from sendMail() or perform an await on that call (and make the entire function async), so you will need to look at that more closely.

Firebase functions just dying out with no explanation or error

I'm trying to implement a messaging application using Firebase Firestore and Firebase Cloud Functions.
In essence, chat messages are stored as individual documents in a subcollection. At first, I implemented this as directly adding a document from the client and listening on the collection and updating the clients when a change happens but later I decided to switch to using Cloud functions so that I can add some functionality that's better done on the server side(filtering etc.).
So I created a function for sending messages, which creates the documents on behalf of the users when the users call the function from the app(i.e. tap the send button).
The function worked and I was able to monitor the processes through the logs. Unfortunately, the functions began to die out without error, the console was reporting that the functions are executed successfully and it usually took less than a second to execute.
I suspect that it has something to do with the promises that probably continue to run but this is the same code that was working but failing today.
If I try a few more times, the functions seem to be working again. Do I need to keep the functions "warm"? Are cloud functions not reliable enough to handle this kind of tasks? When I say my user that a message is sent, I need to be able to confirm that it is sent and communicate it with the users if it failed.
It's hard to debug the issue because no errors are thrown(not even info message, it's just as if didn't happen), it just says that the function successfully finished execution and nothing happened.
Am I missing something here? Thank you.
exports.sendMessage = functions.https.onCall((data, context) => {
if (context.auth.uid == undefined) {
console.warn("SEND MESSAGE: USER NOT SIGNED IN");
return;
}
console.log("Sending message:", data)
const matchId = data["matchId"];
const message = data["message"]
const uid = context.auth.uid
admin.firestore().collection(MatchingUsers).doc(matchId).collection(UserMessages).add({
type: "text",
from: uid,
message: message,
timestamp: admin.firestore.Timestamp.now()
}).then(result => {
console.log("Message sent")
}).catch(err => {
console.log("Error sending mesage:", err)
})
})
As explained in the documentation of the HTTP Callable Cloud Functions:
To return data after an asynchronous operation, return a promise.
Then follows an example:
const sanitizedMessage = sanitizer.sanitizeText(text); // Sanitize the message.
return admin.database().ref('/messages').push({
text: sanitizedMessage,
author: { uid, name, picture, email },
}).then(() => {
console.log('New Message written');
// Returning the sanitized message to the client.
return { text: sanitizedMessage };
})
So you need to adapt your code as follows:
exports.sendMessage = functions.https.onCall((data, context) => {
if (context.auth.uid == undefined) {
console.warn("SEND MESSAGE: USER NOT SIGNED IN");
//Here send back an error as explained here: https://firebase.google.com/docs/functions/callable#handle_errors
}
console.log("Sending message:", data)
const matchId = data["matchId"];
const message = data["message"]
const uid = context.auth.uid
//Note the return on next line
return admin.firestore().collection(MatchingUsers).doc(matchId).collection(UserMessages).add({
type: "text",
from: uid,
message: message,
timestamp: admin.firestore.Timestamp.now()
}).then(result => {
console.log("Message sent");
return { text: "Message sent" };
}).catch(err => {
console.log("Error sending mesage:", err);
//Here, again, send back an error as explained here: https://firebase.google.com/docs/functions/callable#handle_errors
})
})
If you don't want to return a value to the client, you could do as follows, returning null when the Promise returned by the add() asynchronous method resolves. (Not tested but it should work).
exports.sendMessage = functions.https.onCall((data, context) => {
if (context.auth.uid == undefined) {
console.warn("SEND MESSAGE: USER NOT SIGNED IN");
return null;
}
console.log("Sending message:", data)
const matchId = data["matchId"];
const message = data["message"]
const uid = context.auth.uid
//Note the return on next line
return admin.firestore().collection(MatchingUsers).doc(matchId).collection(UserMessages).add({
type: "text",
from: uid,
message: message,
timestamp: admin.firestore.Timestamp.now()
}).then(result => {
console.log("Message sent"); //Actually, if you don't need this console.log() you can remove this entire then() block, returning the promise from add() is enough
return null;
}).catch(err => {
console.log("Error sending mesage:", err);
return null;
})
})

Access all children on node and update them for Firebase Function

I detected some recursion on one of the nodes of my realtime database and I want to delete (or set tu null) that specific node. This is my firebase function so far:
exports.cleanForms = functions.https.onRequest((req, res) => {
const parentRef = admin.database().ref("forms");
return parentRef.once('value').then(snapshot => {
snapshot.forEach(function(child) {
admin.database().ref('forms/'+child.key+'/user/forms').set(null);
});
});
});
Basically it should iterate all the records inside the forms node and delete its user/forms property.
But calling that function by going to this url: https://.cloudfunctions.net/cleanForms gives me this error:
Error: could not handle the request
And this is what I see on the logs:
10:47:57.818 PM cleanForms Function execution took 13602 ms, finished
with status: 'connection error'
The forms node has less than 3,000 records but as I mentioned before, it has some recursion on it. I don't know if it is failing due to its size or something related to that.
You are using an HTTPs Cloud Function: therefore you must "send a response to the client at the end" (Watch this official video by Doug Stevenson for more detail: https://youtu.be/7IkUgCLr5oA).
In your case, the "end" of the function will be when ALL of your set() asynchronous operations will be "done". Since the set() method returns a Promise, you have to use Promise.all() (again, watch this official video: https://youtu.be/d9GrysWH1Lc ).
So the following should work (not tested however):
exports.cleanForms = functions.https.onRequest((req, res) => {
const parentRef = admin.database().ref("forms");
parentRef.once('value')
.then(snapshot => {
const promises = [];
snapshot.forEach(child => {
promises.push(admin.database().ref('forms/'+child.key+'/user/forms').set(null));
});
return Promise.all(promises)
.then(results => {
response.send({result: results.length + ' node(s) deleted'});
})
.catch(error => {
response.status(500).send(error);
});
});

Integrating FireStore/Stripe/iOS/Cloud Functions

I am a newbie to all the technologies mentioned. So, I apologize in advance if this question shouldn't be here.
So I am trying to write the firestore equivalent of the example stripe-firebase realtime database-cloud functions integration provided by firebase. It is located here: https://github.com/firebase/functions-samples/blob/master/stripe/functions/index.js
I successfully converted the exports.createStripeCustomer function. But I am having trouble with addPaymentSource.
This is what I have so far:
exports.addPaymentSource = functions.firestore.document('Users/{userId}/paymentSources/{paymentID}').onWrite((change, context) => {
let newPaymentSource = change.after.data();
let token = newPaymentSource.token;
return functions.firestore.document(`Users/${context.params.userId}`).get('customer_data')
.then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {newPaymentSource});
}).then((response) => {
return change.after.ref.parent.set(response);
}, (error) => {
return change.after.ref.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: context.params.userId});
});
});
Deployment happens successfully, but the function fails with the following error.
TypeError: functions.firestore.document(...).get is not a function
at exports.addPaymentSource.functions.firestore.document.onWrite (/user_code/index.js:73:77)
at Object. (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:112:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:82:36)
at /var/tmp/worker/worker.js:728:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
Clearly 'get' doesn't seem to be a function. I got this function from the following reference document. https://firebase.google.com/docs/reference/functions/functions.firestore.DocumentSnapshot
I seem to be missing something. I have been stuck on this for a couple of days. So, any help would be greatly appreciated. Thanks in advance.
use firebase-admin
const admin = require('firebase-admin');
exports.addPaymentSource = functions.firestore.document('Users/{userId}/paymentSources/{paymentID}').onWrite((change, context) => {
let newPaymentSource = change.after.data();
let token = newPaymentSource.token;
return admin.firestore().doc(`Users/${context.params.userId}`).get('customer_data')
// what you have to do
});
});
The functions package is used for triggering functions. You can use the following methods:
onCreate
onUpdate
onDelete
onWrite
(more here: https://firebase.google.com/docs/firestore/extend-with-functions)
To use the get() method, you'll want to use the firebase-admin package - you can then access firestore from admin.firestore()
(firebase-admin: https://www.npmjs.com/package/firebase-admin)

FIrebase Firestore onCreate Cloud Function Event Params Undefined

I have tried following Firebase's documentation and other SO posts to access a parameter value for a cloud function I've successfully deployed.
Unfortunately I've still been receiving a
Type Error: cannot read property 'id' of undefined
I've logged event.params and it is outputting as undefined, so I understand the issue, but am unsure how, syntactically, I'm supposed to derive the param value.
Below is my js code for reference:
exports.observeCreate = functions.firestore.document('/pathOne/{id}/pathTwo/{anotherId}').onCreate(event => {
console.log(event.params);
//event prints out data but params undefined...
const data = event.data()
var id = event.params.id;
return admin.firestore().collection('path').doc(id).get().then(doc => {
const data = doc.data();
var fcmToken = data.fcmToken;
var message = {
notification: {
title: "x",
body: "x"
},
token: fcmToken
};
admin.messaging().send(message)
.then((response) => {
console.log('Successfully sent message:', response);
return;
})
.catch((error) => {
console.log('Error sending message:', error);
return;
});
return;
})
})
You're using the pre-1.0 API for the firebase-functions module, but the acutal version of it you have installed is 1.0 or later. The API changed in 1.0. Read about the changes here.
Firestore (and other types of) triggers now take a second parameter of type EventContext. This has a property called params that contains the data that used to be in event.params.
exports.observeCreate = functions.firestore.document('/pathOne/{id}/pathTwo/{anotherId}').onCreate((snapshot, context) => {
console.log(context.params);
console.log(context.params.id);
});
Please also read the documentation for the most up-to-date information about Firestore triggers.

Resources