Search code examples
firebasegoogle-cloud-platformfirebase-authenticationgoogle-cloud-functionsgoogle-cloud-storage

How to update user object by uid with async await using Cloud Functions?


I have implemented firebase auth in my app. I need to update the photo URL using a Cloud Function. So when the user uploads a photo to storage, I need to update the URL in the user object with the URL of the image which is uploaded at images/uid.png

I need to find the user by uid and then update the user object with the new URL but I cannot do that. This what I have tried:

exports.updateUrl = functions.auth.getUser(uid)(async (user) => {
  const newPhotoUrl = storage.bucket().file(`images/${uid}.jpg`);
  await admin.auth().updateUser(user.uid, {photoUrl: newPhotoUrl});

  return true;
});

I've found this answer but it contains only broken links :(

How to solve this?


Solution

  • When the user uploads a photo to storage

    This means that you need to use a Cloud Function that is triggered by a Cloud Storage event and not a CF triggered by the Authentication Service event (BTW functions.auth.getUser() does not exist, see the doc).

    The following should do the trick (untested).

    const functions = require("firebase-functions");
    
    const admin = require("firebase-admin");
    admin.initializeApp()
    
    exports.updateUrl = functions.storage.object().onFinalize(async (object) => {
    
        try {
    
            const newPhotoStorageFile = admin.storage().bucket(object.bucket).file(object.name);
            const signedURLconfig = { action: 'read', expires: '01-01-2030' };
            const newPhotoURLArray = await newPhotoStorageFile.getSignedUrl(signedURLconfig);
            const newPhotoURL = newPhotoURLArray[0];
            
            const newPhotoStoragePath = object.name;
            const userId = newPhotoStoragePath.split("/")[1].split(".")[0]; // Double check this simple extraction code is valid for all your cases.
    
            await admin.auth().updateUser(userId, { photoUrl: newPhotoURL });
    
            return true;
    
        } catch (error) {
            console.log(error);
            return null;
        }
    
    });
    

    At this stage if you run this Cloud Function you'll get the following error: Error: Permission 'iam.serviceAccounts.signBlob' denied on resource (or it may not exist).

    Assigning, via the Google Cloud console, this IAM permission to the App Engine default service account, PROJECT_ID @appspot.gserviceaccount.com would solve the problem.

    HOWEVER, you would face another problem: The SignedURL has a short life duration due the problem described in this SO answer. So you need to use the Google Cloud Storage npm module and initialize it with a service account json file.

    const {Storage} = require('@google-cloud/storage');
    const storage = new Storage({keyFilename: "key.json"});
    
    const newPhotoStorageFile = storage.bucket(object.bucket).file(object.name);