brandbei37
brandbei37

Reputation: 501

Validate Firebase realtime URL's are from Firebase storage

So I have a function in my website which allows users to upload a profile photo. The photo is stored in a folder in my Firebase storage under their UID and then once successfully uploaded, the downloadURL is stored under their UID in my Firebase realtime database like so:

database.ref('users/'+firebase.auth().currentUser.uid).update({
profilePhoto: downloadURL
});

Now I have some rules already in place so that only the auth UID can write data to themselves.

{
"rules": { 
 "users": {
  ".read": "true",
  "$user": {
   ".read": "true",
   ".write": "auth.uid == $user",
    "profilePhoto": {
     ".validate": "newData.isString()
    },  
  }
 }
}
}

Work great but how can I make it so only URL's coming directly from my Firebase storage are written?

For example, a user with knowledge could simply run the script above and change the data to something outside of the storage or just some random text which breaks the profile image when I fetch it.

One thing I thought of doing is adding the Firebase storage 'base path' as a validation like so:

"profilePhoto": {
 ".validate": "newData.isString()
 && newData.val().contains('mystorage-12345.appspot.com/') 
}

But is there a better way of doing it? Will the 'base path' ever change down the line?

Upvotes: 1

Views: 139

Answers (1)

Brian Burton
Brian Burton

Reputation: 3842

There are no combinations of rules that you could use to prevent a user from linking to a malicious file or entering gibberish. Since you're open to better methods, here's my standard pattern for handling uploads like this.

First, decide on a URL structure for your profile photos where the photo will be stored in your bucket with an easily guessable format. My preference is: /u/<uid>/profile_<width>x<height>.jpg

Second, provide a signed URL for your user to upload their image to. This might be in a separate bucket that, when a new object appears, triggers a function resize the photo and then moves the resized photos to /u/<uid>/profile_<width>x<height>.jpg with the relevant widths and heights.

Finally, there's no need to store the URL of the profile photo because any user's profile can be displayed like this:

<img src="https://<bucketUrl>/u/<uid>/profile_512x512.jpg" onerror="this.src=' + "/img/default-profile.jpg" + '" />

What's happening here is it first attempts to load the user's profile photo using the guessable /u/<uid>/profile_<width>x<height>.jpg pattern and if it fails to load, for any reason, display a default photo in its place.

Advantages

  1. Lower costs due to fewer database reads, writes and storage.
  2. Simple, fast and reliable.
  3. Built-in failover in case the bucket can't be reached for any reason.

Upvotes: 1

Related Questions