5

I'm having problem getting download url in firebase function after saving image to cloud storage. Below is my firebase http function in typescript to save base64 string to jpeg in firebase cloud storage. when i log result, it always empty. I follow this link to get download url "Get Public URL from file uploaded with firebase-admin". But it give me following error:"SigningError: Identity and Access Management (IAM) API has not been used in project 81673989436 before or it is disabled." And couldn't find simple example to follow. My plan is to update my firestore table, once uploaded to cloud storage.

export const publishImage = functions.https.onRequest((req, res) => {
// Convert the base64 string back to an image to upload into the Google 
// Cloud Storage bucket
const base64EncodedImageString=req.body.image.replace(/^data:image\/jpeg;base64,/, "");
const mimeType = 'image/jpeg';
const randomFileName = crypto.randomBytes(16).toString('hex') + '.jpeg';    
const imageBuffer = new Buffer(base64EncodedImageString, 'base64');

const bucket = admin.storage().bucket();

// Upload the image to the bucket
const file = bucket.file('images/' + randomFileName);
file.save(imageBuffer, {
    metadata: { contentType: mimeType },
}).then(result => {
    console.log(result);
    file.makePublic();
    file.getSignedUrl({
        action: 'read',
        expires: '03-09-2491'
    }).then(urls => {
        console.log(urls[0]);
    })
});
return res.status(200).send("NOT WORKING");    
})

The error is as follow in firebase console. Even with error, I can save image in storage.

SigningError: Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/vmsystem-4aa54/serviceAccounts/vmsystem-4aa54@appspot.gserviceaccount.com. at /user_code/node_modules/@google-cloud/storage/src/file.js:1715:16 at Request._callback (/user_code/node_modules/@google-cloud/storage/node_modules/google-auto-auth/index.js:356:9) at Request.self.callback (/user_code/node_modules/@google-cloud/storage/node_modules/request/request.js:186:22) at emitTwo (events.js:106:13) at Request.emit (events.js:191:7) at Request. (/user_code/node_modules/@google-cloud/storage/node_modules/request/request.js:1163:10) at emitOne (events.js:96:13) at Request.emit (events.js:188:7) at IncomingMessage. (/user_code/node_modules/@google-cloud/storage/node_modules/request/request.js:1085:12) at IncomingMessage.g (events.js:292:16)

How can i get download url?

I am getting confused. Now, I am stuck at updating function after changes. Below is my initialization for admin.

 import * as admin from 'firebase-admin';
 const serviceAccount = require('./../service_account.json');

 try {   
 // admin.initializeApp(functions.config().firebase); // initial
 admin.initializeApp({
     credential: admin.credential.cert(serviceAccount),
     databaseURL: "https://vmsystem-4aa54.firebaseio.com"
 });
 } catch(e) {}
 import * as simpletest from './simpletest';
 export const testsomething = simpletest.helloWorld;

first

second

third

fourth

phonemyatt
  • 1,287
  • 17
  • 35
  • In the title you say Firebase Cloud Storage and in the text you say Google Cloud Storage. These are two different APIs. Moreover, since you are performing an asynchronous action in the cloud function, you should return a Promise. – apaatsio May 02 '18 at 10:35
  • sorry about that. i already change it to firebase. i will return the promise when i can get basic functionality working. – phonemyatt May 02 '18 at 10:44
  • 1
    @apaatsio HTTPS-triggered functions don't need to return a promise. – Frank van Puffelen May 02 '18 at 13:56
  • It doesn't look like you initialized the cloud storage API with a service account as described in the other issue you linked it. If you want to use getSignedUrl, you need to init with a dedicated service account. – Doug Stevenson May 02 '18 at 15:49

2 Answers2

7

You're not showing all the code here (for example, how you initialized the Firebase Admin SDK to create admin). So I'm going to assume that you used the project default credentials to initialize it like this:

import * as admin from 'firebase-admin'
admin.initializeApp()

This isn't sufficient to to be able to use getSignedUrl when reaching into the admin SDK to use Cloud Storage APIs. If you want to use getSignedUrl, you'll need to provide the credentials for a dedicated service account that you create in the console.

Assuming that you put those credentials in a file called yourServiceAccount.json in your functions folder, you should initialize the admin SDK with those credentials like this:

import * as serviceAccount from 'yourServiceAccount.json';
const adminConfig = JSON.parse(process.env.FIREBASE_CONFIG)
adminConfig.credential = admin.credential.cert(<any>serviceAccount)
admin.initializeApp(adminConfig);

Note that this is just taking the default project config from FIREBASE_CONFIG and adding the service account to them.

In order to get the import of a JSON file, you will also need to have a file (called typings.d.ts):

declare module "*.json" {
  const value: any;
  export default value;
}
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • I added service account as mentioned. but it's giving me another error – phonemyatt May 03 '18 at 11:37
  • I get this error: `Error: Error parsing triggers: Cannot find module './serviceAccountKey.json'` – Andrey Solera Sep 09 '19 at 01:58
  • @Doug Stevenson Does the URL returned by getSignedUrl() expires in maximum 7 days? I am using getSignedUrl to generate url from admin sdk but it works fine for some days and throws SignatureDoesNotMatch error after 7 days. – Nainal Dec 06 '19 at 08:43
  • Doug, would you recommend this over adding the "Service Account Token Creator" role to the default App Engine service account? Adding the required roles seems much easier. – nicoqh Jun 05 '20 at 11:41
0

Thanks @Doug Stevenson. I believe I found an answer.

  1. I solve deployment errors by recreating new project.

  2. I change my initialization to code below to solve unhandled rejection. Go to Service Acccounts under Project Settings in your firebase project and generate new private key under firebase admin sdk.

    const serviceAccount = require('./path/to/serviceaccount.json');
    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),
      databaseURL: '<ur database url>', // check it under service accounts
      storageBucket: '<ur storage bucket url>' //check it in script snippet require to add to your website.
    });
    
phonemyatt
  • 1,287
  • 17
  • 35