Firebase emulator: see outgoing HTTP traffic - firebase

I have a Cloud Function that calls to Chargebee. In index.ts:
const chargeBee = new ChargeBee();
...
chargeBee.configure({
site,
api_key: apiKey
});
...
export const finalizeSignup = https.onCall(
async (info: SignupInfo, ctx: CallableContext) => {
const cbCmd = chargeBee.hosted_page.retrieve(info.cbHostedPage);
const callbackResolver = new Promise<any>((resolve, reject) => {
// cbCmd.request returns a Promise that seems to do nothing.
// The callback works, however.
// Resolve/reject the Promise with the callback.
void cbCmd.request((err: any, res: any) => {
if (err) {
reject(err);
}
resolve(res);
});
});
// Calling Promise.resolve subscribes to the Promise.
return Promise.resolve(callbackResolver);
}
);
I am testing this function using the Firebase emulators, started via firebase emulators:start --only functions. Chargebee is responding strangely. They require the domain of their incoming requests to be whitelisted: my first guess is that the domain being used by my locally emulated Cloud Function is not whitelisted on the Chargebee side.
How do I see outgoing HTTP information sent by my locally emulated Cloud Function?

The connection is actually HTTPS, not HTTP.
The emulators provide no functionality to intercept network traffic of any form.
For HTTP: you have to apply your own tooling to monitor the HTTP traffic (ie Wireshark).
For HTTPS: possible to monitor using Wireshark, but impossible to analyze without knowing the SSL key. And in the setup above, where a third-party library is handling the request, there is currently no way to obtain the SSL key. I entered a feature request with Firebase to gauge the interest of developing a way to define an SSL key log when starting the Functions emulator, similar to Chrome. A user only identifying themselves as 'Oscar' told me in a private email that "I've already filed a feature regarding this topic to our engineering team regarding this matter, which will be discussed internally." So that tells us that (1) Firebase is aware that the feature is currently lacking, and (2) there is no progress to report on the feature.

Related

Firebase Cloud Messaging Notification not received in web app

I am using FCM for notification. FCM gets triggered on creation of data from the Firebase database. I received first message. After that other consecutive messages is not received. I'm running this in a local environment. Is the problem due to the below message "Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions" or any other issue. Do I need to get into a billing plan for receiving messages. Working in test environment and that is the reason not moving to billing plan. If the issue is not related to billing plan can someone point any other problem with the code.
Firebase function log
6:22:52.133 PM
sendFollowerNotification
Function execution started
6:22:52.133 PM
sendFollowerNotification
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions
6:22:52.143 PM
sendFollowerNotification
Function execution took 10 ms, finished with status: 'ok'
6:22:52.401 PM
sendFollowerNotification
1 messages were sent successfully
Node js code
exports.sendFollowerNotification = functions.database.ref('/notification/message/{gId}/{pId}')
.onCreate(async (change, context) => {
//console.log('Group id:', context.params.gId," Push ID:",context.params.pId, "Change",change);
const notificationData = change.val();
var topic = notificationData.topic;
var title = notificationData.title;
var body = notificationData.body;
var registrationTokens = notificationData.tokens;
const message = {
notification: {
title: title,
body: body
},
tokens: registrationTokens,
};
admin.messaging().sendMulticast(message)
.then((response) => {
// Response is a message ID string.
console.log(response.successCount + ' messages were sent successfully');
})
.catch((error) => {
console.log('Error sending message:', error);
});
});
That message does not indicate an error. It's just a warning letting you know that outbound networking does not work if your project is not on a payment plan. FCM messaging does not fall in this category - it should work.
The problem is that your code doesn't return a promise that resolves after all asynchronous work is complete. Right now, it returns nothing, and the function terminates immediately before the message is sent. Please read and understand the documentation about this.
Minimally, you will need to return the promise chain to let Cloud Functions know when the message is sent, and it's safe to terminate.
return admin.messaging().sendMulticast(message)
.then((response) => {
// Response is a message ID string.
console.log(response.successCount + ' messages were sent successfully');
})
.catch((error) => {
console.log('Error sending message:', error);
});
Note the return keyword above.
If the message still isn't being sent, then there is some other problem here that we can't see. You might not be handling your device tokens correctly.
I think this might answer your question: Why will I need a billing account to use Node.js 10 or later for Cloud Functions for Firebase?:
Because of updates to its underlying architecture planned for August 17, 2020, Cloud Functions for Firebase will rely on some additional paid Google services: Cloud Build, Container Registry, and Cloud Storage. These architecture updates will apply for functions deployed to the Node.js 10 runtime. Usage of these services will be billed in addition to existing pricing.
In the new architecture, Cloud Build supports the deployment of functions. You'll be billed only for the computing time required to build a function's runtime container.
On the other hand, the Service Firebase Clud Messaging itself is free:
Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices that allows you to deliver and receive messages and notifications on iOS, Android, and the web at no cost.
Given that you are using Node in your CFs, the Platform requires to you a Billing Account.

Nuxt SPA dynamic routes based on Firebase Firestore data

So I want to have a nuxt site hosted on Netlify where there's a child route whos slug is a firebase firestore document id.
Example:
https://www.example.com/users/steve
(where "steve" is the documentid)
So when the route is hit I would need to query firebase to see if it exists, and if not I would have to return a 404. Is this even possible? I can do it easy in .net or php, but I'm very unsure of a SPA.
Specifically what should I be looking for in the docs, if I can do this?
One solution is to implement an HTTPS Cloud Function that you would call like a REST API, sending an HTTP GET request to the functions endpoint.
As explained in the doc "Used as arguments for onRequest(), the Request object gives you access to the properties of the HTTP request sent by the client".
So you Cloud Function would look like:
exports.getUser = functions.https.onRequest((req, res) => {
// get the value of the user by parsing the url
const baseUrl = req.baseUrl;
//Extract the user from baseUrl
const user = ....
//query the Firestore database
admin.firestore().collection('users').doc(user).get()
.then(doc => {
if (doc.exists) {
res.status(200).end();
} else {
res.status(404).end();
}
});
See the get started page and the video series for more info on Cloud Functions.
Note that you can connect an HTTP function to Firebase Hosting, in such a way that "requests on your Firebase Hosting site can be proxied to specific HTTP functions".

Use Firebase onRequest() or Express app.use() for the Slack API

Goal
Use the #slack/interactive-message package with firebase-functions to listen and respond to Slack messages and dialogs.
Question
I'm not sure how to use the #slack/interactive-message listener with firebase.
1) Do I use Firebase's functions.https.onRequest(), and somehow pass the req from Slack to slackInteractions.action()?
OR
2) Do I use app.use("/app", slackInteractions.expressMiddleware()); If so, where do slackInteractions.action()s go?
OR
3) Something else?
Code
// Express
import express = require("express");
const app = express();
const cors = require("cors")({
origin: "*"
});
app.use("*", cors);
// Firebase Functions SDK
import functions = require("firebase-functions");
const slackbotConfig = functions.config().slackbot;
const { createMessageAdapter } = require("#slack/interactive-messages");
const slackInteractions = createMessageAdapter(slackbotConfig.signing_secret);
app.use("/app", slackInteractions.expressMiddleware());
// Express route
app.post("/go", (req, res) => {
console.log("Hello from Express!");
res
.status(200)
.send("Hello from Express!")
.end();
});
exports.app = functions.https.onRequest(app);
exports.helloWorld = functions.https.onRequest((_req, res) => {
console.log("Hello from Firebase!");
res
.status(200)
.send("Hello from Firebase!")
.end();
});
tl;dr
I'm new to the details of Express and using middleware. Examples of the #slack/interactive-message show...
slackInteractions.start(port).then(() => {
console.log(`server listening on port ${port}`);
});
...and with Firebase Cloud Functions, this bit isn't relevant. I'm not sure how listeners, requests, and responses are integrated between Firebase and #slack/interactive-message
creator of #slack/interactive-messages here 👋
In short, your solution number 2 seems correct to me. While I don't have experience with Firebase functions, I have a pretty good understanding of express, and I'll provide some more details.
What is express middleware?
Express middleware is a name for a kind of function that processes an incoming HTTP request. All middleware functions can, on a request-by-request basis, choose to pre-process a request (usually by adding a property to the req argument), respond to the request, or post-process a request (like calculate the timing between the request and the response). It can do any one or combination of those things, depending on what its trying to accomplish. An express app manages a stack of middleware. You can think of this as a list of steps a request might work through before a response is ready. Each step in that list can decide to offer the response so that the next step isn't even reached for that request.
The cors value in your code example is a middleware function. It applies some rules about which origins your Firebase function should accept requests from. It applies those rules to incoming requests, and when the origin is not allowed, it will respond right away with an error. Otherwise, it allows the request to be handled by the next middleware in the stack.
There's another middleware in your example, and that's a router. A router is just a kind of middleware that knows how to split an app up into separate handlers based on the path (part of the URL) in the incoming request. Every express app comes with a built in router, and you attached a handler to it using the app.post("/go", () => {}); line of code in your example. Routers are typically the last middleware in the stack. They do have a special feature that people often don't realize. What are these handlers for routes? They are just more middleware functions. So overall, you can think of routers as a type of middleware that helps you divide application behavior based on the path of a request.
What does this mean for slackInteractions?
You can think of the slackInteractions object in your code as a router that always handles the request - it never passes the request onto the next middleware in the stack. The key difference is that instead of dividing application behavior by the path of the request, it divides the behavior using the various properties of a Slack interaction. You describe which properties exactly you care about by passing in constraints to the .action() method. The only significant difference between a typical router and slackInteractions, is that the value itself is not the express middleware, you produce an express middleware by calling the .expressMiddleware() method. It's split up like this so that it can also work outside of an express app (that's when you might use the .start() method).
Putting it together
Like I said, I don't have experience with Firebase functions specifically, but here is what I believe you should start with as a minimum for a function that only handles Slack interactions.
// Firebase Functions SDK
import functions = require("firebase-functions");
const slackbotConfig = functions.config().slackbot;
// Slack Interactive Messages Adapter
const { createMessageAdapter } = require("#slack/interactive-messages");
const slackInteractions = createMessageAdapter(slackbotConfig.signing_secret);
// Action handlers
slackInteractions.action('welcome_agree_button', (payload, respond) => {
// `payload` is an object that describes the interaction
console.log(`The user ${payload.user.name} in team ${payload.team.domain} pressed a button`);
// Your app does some asynchronous work using information in the payload
setTimeout(() => {
respond({ text: 'Thanks for accepting the code of conduct for our workspace' });
}, 0)
// Before the work completes, return a message object that is the same as the original but with
// the interactive elements removed.
const reply = payload.original_message;
delete reply.attachments[0].actions;
return reply;
});
// Express
import express = require("express");
const app = express();
app.use("/", slackInteractions.expressMiddleware());
exports.slackActions = functions.https.onRequest(app);

Is it possible to determine the source of an incoming request?

Is it possible to know the hostname of the source of an incoming request to a cloud firestore document? I would like to write a database rule of the form allow write: if request.resource.data.source_host_name == some_predefined_value. This is a web application so I'm trying to find a good way to limit who gets to write to my database without using traditional auth methods.
That sort of rule is not possible with Cloud Firestore. It also wouldn't be very secure, as it's possible to spoof source IP addresses.
If you want to limit who can access your database, the only supported mechanism is through security rules and Firebase Authentication.
Here's my solution to logging a client's IP address when interacting with Firestore. I agree with Doug that this won't guard against IP spoofing. Nonetheless, it's helpful for my purposes.
The trick is to create intermediary API endpoints using Cloud Functions, which then interact with Firestore. Here's an example Google Cloud function.
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
exports.yourEndpoint = functions.https.onRequest(req, (res) => {
// Get the IP address from the headers in the request object
const ipAddress = getIPAddress(req);
// Do whatever you need in your firestore DB with ipAddress
admin.firestore().collection('ipAddresses').add({address: ipAddress})
.then((writeResult) => {
return res.status(200).send();
})
.catch((error) => {
return res.status(500).send();
});
});
// Helper function to extract the IP address from request headers
function getIPAddress(req) {
return (
req.headers["fastly-client-ip"] ||
req.headers["x-forwarded-for"] ||
req.headers["No IP, probably because we are in development mode"]
);
}
Note: you won't get an IP address in development mode, because the Firebase emulators don't create the proper headers.

How to run ElasticSearch on user's devices AND keep ElasticSearch server safe?

Writing a mobile app with Firebase being my backend, also using ES to power my search. I'm completely new to ES.
Suppose each user can publish articles, each of which contains some number of tags, which denotes what this article is about, kind of like questions asked here. Users can search for articles, with tags, and articles containing that tag will be displayed. I manage to do that with Cloud Function, so, the Cloud Function basically looks like this:
exports.articleSearch = functions.https.onRequest((req, res) => {
const { tag } = req.query;
const ElasticSearchConfig = {
uri: '..<my elastic cloud url>/articles/article/_search...',
method: 'GET',
body: ...,
json: true,
auth: {
username: '...<my elastic cloud username>...',
password: '...<my elastic cloud password>...'
}
};
// If succeeds, send results back to user, if not, send error back
request(ElasticSearchConfig).then((results) => ...)
.catch((error) => ...);
});
This works, however, it's a bit slow, because I'm not running ElasticSearch on user's devices, instead, through a cloud function. But if I do run the above code on user's devices, you noticed auth property of ElasticSearchConfig object, I'm basically giving everybody permissions to access and manipulate my ES server. How can I run the above code on user's devices and at the same time, prevent them from reading or writing anything without proper permission?
There's no secure way to do what your asking. Even if it was possible, you don't want that kind of processing client side draining the battery, especially on mobile. Your slow response from cloud functions may be caused from the function entering a timeout state, meaning it hasn't been called in a while.

Resources