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.
Related
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.
I'm using webhooks in a Next.js app to handle incoming customer payment events from Stripe. Similar to the example in the docs, the first thing my webhook does is verify the signature of the incoming event:
const webhookSecret = process.env.SECRET_STRIPE_WEBHOOK
const webhookHandler = async (req, res) =>
{
if (req.method === 'POST')
{
const buf = await buffer(req)
const sig = req.headers['stripe-signature']
let event
try
{
event = stripe.webhooks.constructEvent(buf.toString(), sig, webhookSecret)
}
catch (error)
{
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
// On error, log and return the error message
if (error instanceof Error) console.log(error)
console.log(`Error message: ${errorMessage}`)
res.status(400).send(`Webhook Error: ${errorMessage}`)
return
}
// The rest of the webhook
}
}
However, the docs also mention to only trust events coming from these IP addresses, and provides a list of IP addresses from which webhook events may originate.
For security purposes, is it necessary to also check the IP address of the incoming event, or does the above implementation of verifying the signature cover all bases? The docs explain that verifying signatures through Stripe's libraries does protect against replay attacks, but they do still mention verifying IP addresses in the same sentence.
If verifying IP addresses is a necessary or prudent thing to do, how can I add that to the above implementation? I wasn't able to find examples on the Stripe docs.
Verifying the IP address is an additional layer of security to guarantee the request comes from Stripe. Years ago it was the best approach as webhook signatures didn't exist but since they shipped this feature I think most developers have defaulted to that approach. An attacker can't calculate the signature themselves though they could find a way to replay the requests. I would say most integrations do not bother with the IP allow list.
If you were to implement this, you'd need to look at the IP address associated with the request in your route server-side. This is done with NextRequest though there are other solutions in this answer.
This question already has an answer here:
Creating Firebase Storage Security Rules Based on Firebase Database Conditions [duplicate]
(1 answer)
Closed 1 year ago.
I want to allow read permission on a file in Firebase Storage only if the value of a certain node in the Firebase Realtime Database is true. Is it possible to do so? If yes, then how?
That is not possible at this time. You would have to use Cloud functions or your own servers using the Admin SDK to do that.
The Admin SDK essentially has full access to your Firebase project's resources so you can check if the value you are looking for in the Admin SDK exists. If yes, proceed with getting the signed URLs (or the data you are looking for from storage) else return an error.
A simple cloud function for that would be something like:
exports.getStorageData = functions.https.onCall((data, context) => {
const {uid} = context.auth;
const {fileName} = data;
if (!uid) return {error: "User not authorized!"}
// Read for data in realtime database
const dbRef = admin.database().ref("/path/to/data");
if ((await dbRef.once("value")).val()) {
return {error: "Required Value is not true"}
}
//Get signed URL or any data from storage
const storageRef = admin.storage().bucket()
//.....
return {data: "..."}
});
You need to use such secure env such as server or functions as only client side validation is not secure. But is the true value in your database something like a role or anything? You can try adding those in Custom Claims. I'm not totally sure about your use case but if it something like allowing access to specific folders or something then you can add a claim the_folder_id: true. This is not the best solution if a user can have access to a lot of folders. In that case you can assign groups as mentioned in the documentation. But satisfies your needs then you can try the following security rules along with this.
// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
allow read: if resource.metadata.owner == request.auth.token.groupId;
allow write: if request.auth.token.groupId == groupId;
}
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".
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.