Fakhar Ahmad Rasul
Fakhar Ahmad Rasul

Reputation: 1691

Unable to upload image to firebase storage with firebase functions

here is my code:-

exports.uploadImage = (req, res) => {

    const BusBoy = require('busboy');
    const path = require('path');
    const os = require('os');
    const fs = require('fs');

    const busboy = new BusBoy({ headers: req.headers });

    let imageFileName;
    let imageToBeUploaded = {};

    busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {

        const imageExtension = filename.split('.')[filename.split('.').length - 1];
        imageFileName = `${Math.round(Math.random() * 100000000000)}.${imageExtension}`;
        const filepath = path.join(os.tmpdir(), imageFileName);
        imageToBeUploaded = { filepath, mimetype };
        file.pipe(fs.createWriteStream(filepath));

    });

    busboy.on('finish', () => {

        console.log('Busboy on started');    

        //code breaks here    
        admin.storage().bucket().upload(imageToBeUploaded.filepath, {
            resumable: false,
            metadata: {
                metadata: {
                    contentType: imageToBeUploaded.mimetype
                }
            }
        })
        .then(() => {
            const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`;
            console.log('logging image url' + imageUrl);
            return db.doc(`/users/${req.user.handle}`).update({ imageUrl })
        })
        .then(() => {
            return res.json({ message: 'Image uploaded successfully' });
        })
        .catch(err => {
            console.error(err);
            return res.status(500).json({ error: err.code });
        })
    });
    busboy.end(req.rawBody);
}

I have mentioned where my code is breaking in a comment and the error I am getting is Error: Cannot parse response as JSON: Not Found message: 'Cannot parse response as JSON: Not Found'

The error message says cannot parse response as JSON. Does that mean the response from firebase is not JSON? I have a token in the header of the request and an image in the body as form-data. I really have not clue what wrong, please help

Upvotes: 2

Views: 2231

Answers (4)

Tiago Medici
Tiago Medici

Reputation: 2194

check the storage configuration under Firebase console >> Storage >> (tab) rules, this is the default config, change the flag to true:

rules_version = '2';
 
service firebase.storage {
  match /b/{bucket}/o {
  match /{allPaths=**} {
      allow read, write: if false;
   }
  }
}

Upvotes: 0

Rakesh Pandey
Rakesh Pandey

Reputation: 37

In my case it was wrong bucket Id configured - after correcting that i was able to upload file

Upvotes: 0

samthecodingman
samthecodingman

Reputation: 26171

I unfortunately can't identify the JSON parsing error, so I've instead rewritten the code to be more streamlined as @robsiemb eluded to.

Your uploadImage function appears to be configured as some middleware, so I have done the same below. This code will stream the uploaded data straight to Cloud Storage under a unique file name as generated from Reference.push().key to prevent conflicts.

In the code below,

  • The uploaded file will be stored at a location similar to: userData/someUserId/images/-JhLeOlGIEjaIOFHR0xd.png
  • The image's raw URL is not stored in the database because unless the file object or containing bucket is made public it will require a signed URL which can only last up to 7 days (see below).
  • More than one file can be accepted and uploaded. If this is undesired, configure the limits for the BusBoy instance.
  • Basic error handling for non-POST requests and missing file entries was added.
// import Firebase libraries & initialize
const admin = require('firebase-admin');
admin.initializeApp(); // initializes from environment variables

// import required modules
const BusBoy = require('busboy');

exports.uploadImage = (req, res) => {
    if (req.method !== 'POST') {
      res.sendStatus(405); // 405 METHOD_NOT_ALLOWED
      return;
    }

    let busboy = new BusBoy({headers: req.headers}); // add {limits: {files: 1}} to limit to only a single file upload
    let bucket = admin.storage().bucket();
    let db = admin.firestore();

    let storageFilepath;
    let storageFile;

    // Note: Currently only the last file is saved to `/users/${req.user.handle}`
    busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
        let fileext = filename.match(/\.[0-9a-z]+$/i)[0];

        storageFilepath = `userData/${req.user.handle}/images/` + getUniqueName() + fileext;
        storageFile = bucket.file(storageFilepath);

        file.pipe(storageFile.createWriteStream({ gzip: true }));
    })
    .on('finish', () => {
      if (!storageFile) {
        res.status(400).json({error: 'expected file'}); // 400 BAD_REQUEST
        return;
      }

      db.doc(`/users/${req.user.handle}`).update({ imagePath: storageFilepath })
        .then(() => {
            res.status(201).json({ message: 'Image uploaded successfully' }); // 201 CREATED
        })
        .catch((err) => {
          console.error(err);
          res.status(500).json({ error: err.code }); // 500 INTERNAL_SERVER_ERROR
        });
    })
    .on('error', (err) => {
      console.error(err);
      res.status(500).json({ error: err.code });
    });

    req.pipe(busboy);
});

function getUniqueName() {
  // push() without arguments returns a ThennableReference, which we'll abuse for it's key generation
  return admin.database().ref().push().key;
}

If you did want the uploaded image to be publicly accessible, you could use the following .on('finish', ...) handler that adds in the File.makePublic() function:

.on('finish', () => {
  if (!storageFile) {
    res.status(400).json({error: 'expected file'}); // 400 BAD_REQUEST
    return;
  }

  storageFile.makePublic()
    .then(() => {
      return db.doc(`/users/${req.user.handle}`).update({
          imagePath: storageFilepath,
          imageUrl: `https://storage.googleapis.com/${config.storageBucket}/${storageFilepath}`
        });
    })
    .then(() => {
        res.status(201).json({ message: 'Image uploaded successfully' }); // 201 CREATED
    })
    .catch((err) => {
      console.error(err);
      res.status(500).json({ error: err.code }); // 500 INTERNAL_SERVER_ERROR
    });
})

Upvotes: 1

LeCoda
LeCoda

Reputation: 1016

Found a solution to the issue!

Essentially - you need to set up your Google Application Credentials. Go into firebase and look into your settings. You need to set up the environment variable GOOGLE_APPLICATION_CREDENTIALS so that firebase has your credentials when you access these files.

https://firebase.google.com/docs/admin/setup?authuser=1 for more information.

After you've done that, check the security settings in firebase, in every area you're dealing with. This should solve the problem (it's definitely a security issue and not your code).

This was the tutorial in question as well for those looking on . https://www.youtube.com/watch?v=m_u6P5k0vP0&t=7661s .

Upvotes: 0

Related Questions