aiueoH
aiueoH

Reputation: 783

How to control upload with md5 in Firebase storage?

I want to build the upload system that client can only upload the file once.

  1. Client sends the file's md5 to server.
  2. Server returns the upload path to client.
  3. Client upload file to storage.
  4. Storage rules check the file's md5 is client sent before or not.

How to implement this in firebase?

Upvotes: 1

Views: 2162

Answers (2)

Mike McDonald
Mike McDonald

Reputation: 15963

I interpret this question two ways:

  • Don't allow a file to be overwritten
  • Don't allow the same file to be written twice (minimize storage space)

You can do both entirely in Storage security rules:

// Don't allow overwrites
service firebase.storage {
  match /b/{bucket}/o {
    match /files/{fileName} {
      // Allow an initial upload, or a metadata change
      allow write: if resource == null
                   || request.resource.md5Hash == resource.md5Hash;
    }
  }
}

// Hash files so you only have one file
service firebase.storage {
  match /b/{bucket}/o {
    match /files/{fileHash} {
      // Allow initial upload only, ensure that the hashes match
      allow write: if resource == null
                   && request.resource.md5Hash == fileHash;
    }
  }
}

Upvotes: 3

Chris
Chris

Reputation: 8020

This is the way I would do it:

I would use Firebase Functions, Storage and Database.

There is several ways of doing this, and mine might not be the easiet but it should get you going.

Get the MD5 hash on the client.

Write the MD5 hash to your database to something like /requests/{md5Hash}

Your function has an onWrite() event listener for that endpoint.

The function looks for that md5Hash in another endpoint that is completely locked down (no read or write access to the outside world) in an endpoint like /files/{md5Hash}.

Cloud Function:

(below is written in 'free hand' so there might be some errors, but you get the idea)

module.exports = functions.database.ref('requests/{userId}/{md5Hash}')
.onWrite(event => {
  //Only respond to new writes
  if (!event.data.val() || event.data.previous.val()) {
    return null
  }
  const {userId, md5Hash} = event.params;

  ref.child('files').child(userId).once('value')
  .then(snapshot => {
      return snapshot.hasChild(md5Hash)
  })
  .then(exists => {
    //If the md5Hash doesn't already exist
    var obj = {};
    if (!exists) {
      obj['status'] = 'permitted'
      //This will be the path for Storage
      obj['path'] = `files/${userId}/${md5Hash}`
    } else {
      obj['status'] = 'denied'
    }
    return event.data.ref.update(status)
  })
  .catch(error => {
    console.log(error);
  })

})

While this is going on your could make your client listen to your /request/{md5Hash} endpoint. In this endpoint you could have a status key that will represent the status of the operation. If the cloud function detects that the md5 hash already exists, it will write /requests/{md5Hash}/status = denied, if it doesn't it will write /requests/{md5Hash}/status = permitted.\

You could then create a path for your file in your cloud function that will comprise of different parameters, e.g. const path = ${userId}/${files}/${md5Hash}

and then write that path to /requests/{md5Hash}/path = yourPath.

Then upload your object to Firebase Storage with the path given by functions.

Upvotes: 0

Related Questions