- 
                Notifications
    
You must be signed in to change notification settings  - Fork 407
 
Closed
Labels
Description
System
- Operating System version: MacOS 10.12.5
 - Firebase SDK version: 3.9.0
 - Library version: 5.0.0
 - Firebase Product: auth
 
Problem
In firebase functions, when using storage hook, Google cloud storage has to be used to generate signed URL for any resource. The signed URL generated by the Google cloud storage cannot be used for photoURL in auth object. When setting that URL, firebase admin throws not a valid URL. On checking the source code of validateURL @ https://github.com/firebase/firebase-admin-node/blob/master/src/utils/validator.ts#L165, the path regex doesn't accept % as a valid character in URL.
Steps to reproduce:
- Create a firebase function with storage hook
 - Download the file if it's an image
 - Do some operations on image (I am resizing it using ImageMagick from firebase function examples)
 - Upload the new image to storage
 - Create signed URL using Google cloud storage library
 - Assign that image URL to user profile picture using updateUser
 - It throws an error that this is not a valid URL
 
Relevant Code:
const functions = require('firebase-functions');
const mkdirp = require('mkdirp-promise');
const gcs = require('@google-cloud/storage')({keyFilename: 'service-account.json'});
const spawn = require('child-process-promise').spawn;
const LOCAL_TMP_FOLDER = '/tmp/';
const admin = require('firebase-admin');
// initialize admin SDK with env variables
admin.initializeApp(functions.config().firebase);
// Max height and width of the thumbnail in pixels.
const THUMB_MAX_HEIGHT = 200;
const THUMB_MAX_WIDTH = 200;
// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';
/**
 * When an image is uploaded in the Storage bucket We generate a thumbnail automatically using
 * ImageMagick.
 */
exports.generateThumbnail = functions.storage.object().onChange(event => {
  const filePath = event.data.name;
  const filePathSplit = filePath.split('/');
  const fileName = filePathSplit.pop();
  const fileDir = filePathSplit.join('/') + (filePathSplit.length > 0 ? '/' : '');
  const thumbFilePath = `${fileDir}${THUMB_PREFIX}${fileName}`;
  const tempLocalDir = `${LOCAL_TMP_FOLDER}${fileDir}`;
  const tempLocalFile = `${tempLocalDir}${fileName}`;
  const tempLocalThumbFile = `${LOCAL_TMP_FOLDER}${thumbFilePath}`;
  // Exit if this is triggered on a file that is not an image.
  if (!event.data.contentType.startsWith('image/')) {
    console.log('This is not an image.');
    return;
  }
  // Exit if the image is already a thumbnail.
  if (fileName.startsWith(THUMB_PREFIX)) {
    console.log('Already a Thumbnail.');
    return;
  }
  // Exit if this is a move or deletion event.
  if (event.data.resourceState === 'not_exists') {
    console.log('This is a deletion event.');
    return;
  }
  // Create the temp directory where the storage file will be downloaded.
  return mkdirp(tempLocalDir).then(() => {
    // Download file from bucket.
    const bucket = gcs.bucket(event.data.bucket);
    return bucket.file(filePath).download({
      destination: tempLocalFile
    }).then(() => {
      console.log('The file has been downloaded to', tempLocalFile);
      // Generate a thumbnail using ImageMagick.
      return spawn('convert', [tempLocalFile, '-thumbnail', `${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}>`, tempLocalThumbFile]).then(() => {
        console.log('Thumbnail created at', tempLocalThumbFile);
        // Uploading the Thumbnail.
        return bucket.upload(tempLocalThumbFile, {
          destination: thumbFilePath
        }).then((files) => {
          console.log('Thumbnail uploaded to Storage at', thumbFilePath);
          return files[0].getSignedUrl({
            action: 'read',
            expires: '03-09-2491'
          }).then(signedUrls => {
            const photoURL = signedUrls[0];
            const uid = 'XXXXXXXXXXXXX';
            return admin.auth().updateUser(uid, {
              photoURL: outputURL,
            })
            .then(response => {
              console.log('success');
            });            
          });
        });
      });
    });
  });
});