The Fluffy T Rex
The Fluffy T Rex

Reputation: 600

How to get the uid of the authenticated Firebase user in a Cloud Functions storage trigger

Background: I'm using Firebase Cloud Functions, the new Firestore Database, and storage bucket with an Android client.

What I want to accomplish: When a user uploads a picture to a storage bucket, I want to use cloud functions to get the file path/link to the image location in the storage bucket and store this string as a new document under a new collection called "pictures" under the currently logged in user's document in Firestore.

That way, I can see the images each user has uploaded directly in Firestore and it makes it easier to pull a specific user's images down to the Android client.

What I've completed so far: 1. When a user logs in for the first time, it creates a user doc in the new Firestore Database. 2. A logged in user can upload an image to a storage bucket. 3. Using Firebase Cloud Functions, I managed to get the file path/link of the storage location as follows:

/**
 * When a user selects a profile picture on their device during onboarding,
 * the image is sent to Firebase Storage from a function running on their device. 
 * The cloud function below returns the file path of the newly uploaded image. 
 */
exports.getImageStorageLocationWhenUploaded = functions.storage.object().onFinalize((object) => {
  const filePath = object.name; // File path in the bucket.
  const contentType = object.contentType; // File content type.

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith('image/')) {
    console.log('This is not an image.');
    return null;
  }
console.log(filePath);
});

Question: How do I get the currently logged in user and store this user's uploaded image file path/link as a new doc under this logged in user's documents within the Firestore database using Cloud Functions?

Upvotes: 1

Views: 2039

Answers (3)

Sreedhara Chandra
Sreedhara Chandra

Reputation: 1

if (firebase.auth().currentUser !== null) 
        console.log("user id: " + firebase.auth().currentUser.uid);

Simple way to get userid of a user.

Upvotes: -1

M.Calugaru
M.Calugaru

Reputation: 442

I had a similar problem where I wanted to associate a user uploaded image with his/her uid. I found an elegant solution that does not necessarily requires inserting the uid in the path of the file or even adding it as metadata in the file upload. In fact, the uid is securely transmitted to the database via the standard idToken encoding. This example employs a modified version of the generate-thumbnail cloud function example (found here) which I believe the author of the question was using/alluding to. Here are the steps:

Client side:

  1. Create a trigger function that will run once the user uploaded the image - this functions will simply call the cloud function directly (via the httpsCallable method). The cloud function will receive the user uid (idToken encoded), along with any image metadata you may wish to send. The cloud function will then return a signed URL of the image.
const generateThumbnail = firebase.functions().httpsCallable('generateThumbnail');
const getImageUrl = (file) => {
  firebase.auth().currentUser.getIdToken(true)
    .then((idToken) => generateThumbnail({ 
      idToken, 
      imgName: file.name, 
      contentType: file.type 
    }))
    .then((data) => {   
      // Here you can save your image url to the app store, etc. 
      // An example of a store action: 
      // setImageUrl(data.data.url);
    })
    .catch((error) => { 
      console.log(error); 
  })
}
  1. Create an image upload function – this is a standard file upload handling function; you can make the uid part of storage file path location for the image (if you want to) but you can also trigger a firebase function once the image has been uploaded. This is possible using the 3rd parameter of the on method. Include the trigger function above as the 3rd argument in here.
// Function triggered on file import status change from the <input /> tag
const createThumbnail = (e) => {
  e.persist();
  let file = e.target.files[0];
  // If you are using a non default storage bucket use this
  // let storage = firebase.app().storage('gs://your_non_default_storage_bucket');
  // If you are using the default storage bucket use this
  let storage = firebase.storage();
  // You can add the uid in the image file path store location but this is optional
  let storageRef = storage.ref(`${uid}/thumbnail/${file.name}`);
  storageRef.put(file).on('state_changed', (snapshot) => {}, (error) => {
    console.log('Something went wrong! ', error); 
  }, getImageUrl(file));
}

Server side:

  1. Create a cloud function to convert the image into a resized thumbnail and generate a signed URL – this cloud function takes the image from storage, converts it into a thumbnail (basically reduces its dimensions but keeps initial aspect ratio) using ImageMagick (this is installed by default on all cloud function instances). It then generates a signed URL of the image location and returns it to the client side.
// Import your admin object with the initialized app credentials
const mkdirp = require('mkdirp');
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');

// Max height and width of the thumbnail in pixels.
const THUMB_MAX_HEIGHT = 25;
const THUMB_MAX_WIDTH = 125;
// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';

async function generateThumbnail(data) {
  
  // Get the user uid from IdToken
  const { idToken, imgName, contentType } = data;
  const decodedIdToken = await admin.auth().verifyIdToken(idToken);
  const uid = decodedIdToken.uid;
  
  // File and directory paths.
  const filePath = `${uid}/thumbnail/${imgName}`;
  const fileDir = path.dirname(filePath);
  const fileName = path.basename(filePath);
  const thumbFilePath = path.normalize(path.join(fileDir, `${THUMB_PREFIX}${fileName}`));
  const tempLocalFile = path.join(os.tmpdir(), filePath);
  const tempLocalDir = path.dirname(tempLocalFile);
  const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith('image/')) {
    return console.log('This is not an image.');
  }

  // Exit if the image is already a thumbnail.
  if (fileName.startsWith(THUMB_PREFIX)) {
    return console.log('Already a Thumbnail.');
  }

  // Cloud Storage files.
  const bucket = initDb.storage().bucket('your_bucket_if_non_default');
  const originalFile = bucket.file(filePath);
  
  // Create the temp directory where the storage file will be downloaded.
  // But first check to see if it does not already exists
  if (!fs.existsSync(tempLocalDir)) await mkdirp(tempLocalDir);

  // Download original image file from bucket.
  await originalFile.download({ destination: tempLocalFile });
  console.log('The file has been downloaded to', tempLocalFile);

  // Delete the original image file as it is not needed any more
  await originalFile.delete();
  console.log('Delete the original file as it is not needed any more');
  
  // Generate a thumbnail using ImageMagick.
  await spawn('convert', [ tempLocalFile, '-thumbnail', 
    `${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}>`, tempLocalThumbFile], 
    { capture: ['stdout', 'stderr'] }
  );
  console.log('Thumbnail created at', tempLocalThumbFile);
  
  // Uploading the Thumbnail.
  const url = await uploadLocalFileToStorage(tempLocalThumbFile, thumbFilePath, 
  contentType);
  console.log('Thumbnail uploaded to Storage at', thumbFilePath);
  
  // Once the image has been uploaded delete the local files to free up disk space.
  fs.unlinkSync(tempLocalFile);
  fs.unlinkSync(tempLocalThumbFile);
  
  // Delete the uid folder from temp/pdf folder
  fs.rmdirSync(tempLocalDir);

  await admin.database().ref(`users/${uid}/uploaded_images`).update({ logoUrl: url[0] });
  return { url: url[0] };
}

// Upload local file to storage
exports.uploadLocalFileToStorage = async (tempFilePath, storageFilePath, 
  contentType, customBucket = false) => {
  let bucket = initDb.storage().bucket();
  if (customBucket) bucket = initDb.storage().bucket(customBucket);
  const file = bucket.file(storageFilePath);
  try {
    // Check if file already exists; if it does delete it
    const exists = await file.exists();
    if (exists[0]) await file.delete();
  
    // Upload local file to the bucket
    await bucket.upload(tempFilePath, {
      destination: storageFilePath,
      metadata: { cacheControl: 'public, max-age=31536000', contentType }
    });
    const currentDate = new Date();
    const timeStamp = currentDate.getTime();
    const newDate = new Date(timeStamp + 600000);
    const result = await file.getSignedUrl({
      action: 'read',
      expires: newDate
    });
    return result;
  } catch (e) {
    throw new Error("uploadLocalFileToStorage failed: " + e);
  }
};

Upvotes: 0

Doug Stevenson
Doug Stevenson

Reputation: 317467

Currently, with Cloud Storage triggers, you don't have access to authenticated user information. To work around that, you'll have to do something like embed the uid in the path of the file, or add the uid as metadata in the file upload.

Upvotes: 6

Related Questions