Error: Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object

Viewed 3547

I am trying to follow an Image resizer tutorial in firebase. I tried rewriting the code in plain NodeJS syntax, and borrowed some ideas from other sources. Attached below is my final code in index.js

const functions = require('firebase-functions')
const { Storage } = require('@google-cloud/storage');
const projectId = "REDACTED INFO"
let gcs = new Storage ({
    projectId
});
const os = require('os');
const path = require('path');



const sharp = require('sharp');
const fs = require('fs-extra');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions


exports.onFileChange = functions.storage.object().onFinalize(async object => {
    const bucket = gcs.bucket(object.bucket);
    const filePath = object.name;


    const fileName = filePath.split('/').pop();
    const bucketDir = path.dirname(filePath);


    const workingDir = path.join(os.tmpdir(), 'thumbs');
    // const tmpFilePath = path.join(workingDir, 'source.png');
    const tmpFilePath = path.join(workingDir, fileName);
    //const tmpFilePath = path.join(os.tmpdir(), path.basename(filePath));


    if (fileName.includes('thumb@') || !object.contentType.includes('image')) {
        console.log('exiting function');
        return false;
    }


    // 1. Ensure thumbnail dir exists
    await fs.ensureDir(workingDir);


    // 2. Download Source File
    await bucket.file(filePath).download({
        destination: tmpFilePath
    });

    // 3. Resize the images and define an array of upload promises
    const sizes = [64, 128, 256];

    const uploadPromises = sizes.map(async size => {
        const thumbName = `thumb@${size}_${fileName}`;
        const thumbPath = path.join(workingDir, thumbName);

        // Resize source image
        await sharp(tmpFilePath)
          .resize(size, size)
          .toFile(thumbPath);

        // Upload to GCS
        return bucket.upload(thumbPath, {
            destination: path.join(bucketDir, thumbName)
        });
    });

    // 4. Run the upload operations
    await Promise.all(uploadPromises);

    // 5. Cleanup remove the tmp/thumbs from the filesystem
    return fs.remove(workingDir);
});

exports.onFileDelete = functions.storage.object().onDelete(event => {
    console.log(event);
    console.log('We deleted a file, exit...')
    return;
});

However, when I tried uploading an image, I keep getting these error and warnings in the firebase console logs.

Error: Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object.
    at new ApiError (/srv/node_modules/@google-cloud/common/build/src/util.js:59:15)
    at Util.parseHttpRespMessage (/srv/node_modules/@google-cloud/common/build/src/util.js:161:41)
    at Util.handleResp (/srv/node_modules/@google-cloud/common/build/src/util.js:135:76)
    at Duplexify.requestStream.on.on.res (/srv/node_modules/@google-cloud/storage/build/src/file.js:823:31)
    at emitOne (events.js:116:13)
    at Duplexify.emit (events.js:211:7)
    at emitOne (events.js:116:13)
    at DestroyableTransform.emit (events.js:211:7)
    at onResponse (/srv/node_modules/retry-request/index.js:200:19)
    at PassThrough.<anonymous> (/srv/node_modules/retry-request/index.js:152:11) 

and

MetadataLookupWarning: received unexpected error = URL is not defined code = UNKNOWN 

Does anyone know what steps am I missing out on? Do inform me if more information is needed. Thanks.

2 Answers
Try this:

import * as admin from "firebase-admin";
admin.initializeApp(functions.config().firebase);
const gcs = admin.storage();

Here are some functions I wrote to initialise both the client API / SDK and the admin API / SDK

I'll include the whole thing here but the important part for the purposes of this question is:

const serviceAccount = getJsonFromFile(`${rootDirname}/service-account-file.json`)
const adminConfig = appConfig.firebase.options
adminConfig.credential = admin.credential.cert(serviceAccount)
adminApp = admin.initializeApp(adminConfig)

Instructions for generating the service account JSON file are here

Here's the rest of the code to put this in context:

// noinspection ES6CheckImport
import { connectStorageEmulator, getStorage } from 'firebase/storage'
import { initializeApp } from 'firebase/app'
import admin from 'firebase-admin'
import { appConfig } from '../app-config.js'
import { axiosAsyncAwait } from './axios-utils.js'
import axios from 'axios'
import { rootDirname } from './root-dirname.js'
import { getJsonFromFile } from './get-json-from-file.js'

let app
let adminApp
let storage

const areEmulatorsRunning = async () => {
  const emulatorFunctionsPort = appConfig.firebase.emulatorPorts.functions
  // noinspection HttpUrlsUsage
  const pingUrl = `http://${appConfig.domain}:${emulatorFunctionsPort}/${appConfig.firebase.options.projectId}/us-central1/ping`
  const ping = await axiosAsyncAwait(axios.get(pingUrl))
  const emulatorsRunning = ping.success
  return emulatorsRunning
}

export const getFirebaseApp = async (emulate = true) => {
  if (app == null) {
    app = initializeApp(appConfig.firebase.options)
    storage = getStorage(app)
    const useEmulators = emulate && await areEmulatorsRunning()
    if (useEmulators) {
      console.log('using emulators (client API)')
      process.env.PUBSUB_PROJECT_ID = appConfig.firebase.options.projectId
      process.env.PUBSUB_EMULATOR_HOST = `${appConfig.domain}:${appConfig.firebase.emulatorPorts.pubsub}`
      connectStorageEmulator(storage, appConfig.domain, appConfig.firebase.emulatorPorts.storage)
    } else {
      console.log('using real (client API)')
      process.env.GOOGLE_APPLICATION_CREDENTIALS = `${rootDirname}/service-account-file.json` // pubsub authentication
    }
  }
  return { app, storage }
}

export const getFirebaseAdminApp = async (emulate = true) => {
  if (adminApp == null) {
    const serviceAccount = getJsonFromFile(`${rootDirname}/service-account-file.json`)
    const adminConfig = appConfig.firebase.options
    adminConfig.credential = admin.credential.cert(serviceAccount)
    adminApp = admin.initializeApp(adminConfig)
    const useEmulators = emulate && await areEmulatorsRunning()
    if (useEmulators) {
      console.log('using emulators (admin API)')
      process.env.PUBSUB_PROJECT_ID = appConfig.firebase.options.projectId
      process.env.PUBSUB_EMULATOR_HOST = `${appConfig.domain}:${appConfig.firebase.emulatorPorts.pubsub}`
      process.env.FIREBASE_STORAGE_EMULATOR_HOST = `${appConfig.domain}:${appConfig.firebase.emulatorPorts.storage}`
    } else {
      console.log('using real (admin API)')
    }
  }
  return { adminApp, storage: null }
}
Related