1
votes

Consider an app that allows users to store photos in Firebase storage, these photos have the user's ID set in a cutom metadata 'owner' field.

The following firebase storage security rule ensures only they can see their photos:

allow read: if resource.metadata.owner == request.auth.uid;

Now imagine the app allows a user to make their photos public if they choose, visible by anyone, even users not logged in (request.auth == null). This could be done with another custom metadata field and a slightly different rule:

allow read: if resource.metadata.owner == request.auth.uid || resource.metadata.public == "true";

This works in theory, but requires all photos to have their metadata altered everytime the user toggles public/private access on/off.

To achieve this the following cloud function could be used to update the metadata of all photos under a given 'folder':

import * as functions from 'firebase-functions';
import * as storage from '@google-cloud/storage';

// 'data' contains 'path' of all photos to update and 'public' which will be set to "true" or "false"
export const updatePublicMetadataField = functions.https.onCall( async (data, context) => {
    const s = new storage.Storage();
    const bucket = s.bucket("bucket name")

    const options = { prefix: data.path }
    const md = { metadata: {public: data.public}}
    const [files] = await bucket.getFiles(options)
    for (const file of files) {
        try {
            await file.setMetadata(md)
        } catch(error) {
            return { r: 'Error: ' + error};
        }
    }
    return { r: 'Files processed: ' + files.length};
});

The problem with this approach is that if the user has many hundreds of photos, the cloud function will simply timeout as the calls to setMetadata are not fast.

So is there an alternative approach to allow public access to many files (all within the same 'folder') to be toggled on/off?

Some ideas I had which don't seem to be currently supported by Firebase would be:

  1. Using a single 'security file' in the same folder, containing the required metadata, which could be read by a get() function similar to how Firestore security rules can read an arbitrary document.
  2. Access to Firestore from Firebase storage security rules to allow reading the user's desired value of 'public' from Firestore.
  3. Metadata on Firebase storage 'folders'.
  4. A single (fast) function to update metadata on all files in a 'folder'. E.g. bucket.setMetadata(prefix, metadata)
2

2 Answers

0
votes

As of today Firebase Storage rules don't offer the same capabilities as Firestore rules. It's not possible to query other documents with get() nor to access Firestore documents. The path you were taking with metadata fields is indeed a reasonable workaround although there are some performance trade-offs as you already noted.

To avoid Firebase function timeout an option might be to use a Google Compute Instance to do such update work either with the code you already have or with the gsutil command line tool since Firestore Storage is backed on Google Cloud Storage; This last option allows to make parallel metadata update calls as shown in this thread and explained in the documentation.

A second option might be to introduce a conditional statement into the code that retrieves the Storage photos to first check the user privacy settings from Firestore and proceed accordingly.

A third option could be to use Firestore instead of Firebase Storage, saving the images as bytes or utf-8 encoded string data type, allowing to implement the rules you wanted.

Anyway remember you can always open a feature request.


Edit: I share with you several links of other threads which may provide further insights and ideas post-1, post-2, post-3.

0
votes

You can use cloud functions to update only new files, and for older files use firebase admin ask on python, and update the documents from your local computer, instead of using cloud function. This way the cloud function won't receive timeout.