11
votes

I am trying to access Firestore using Firebase Admin on Node.js v8.4.0 in a Firebase Cloud Function running locally using firebase functions:shell on Windows 10.

firebase -v 4.2.1

  "dependencies": {
    "firebase-admin": "^6.0.0",
    "firebase-functions": "^2.0.5"
  }

After attempting to use firebase admin from my apps code, I attempted to run the quick start example in https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase-firestore/functions/index.js.

This is the actual code run:

'use strict';

// [START all]
// [START import]
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');

admin.initializeApp({})
// [END import]

// [START addMessage]
// Take the text parameter passed to this HTTP endpoint and insert it into the
// Realtime Database under the path /messages/:documentId/original
// [START addMessageTrigger]
exports.addMessage = functions.https.onRequest((req, res) => {
// [END addMessageTrigger]
  // Grab the text parameter.
  const original = req.query.text;
  // [START adminSdkAdd]
  // Push the new message into the Realtime Database using the Firebase Admin SDK.
  return admin.firestore().collection('messages').add({original: original}).then((writeResult) => {
    // Send back a message that we've succesfully written the message
    return res.json({result: `Message with ID: ${writeResult.id} added.`});
  });
  // [END adminSdkAdd]
});
// [END addMessage]

// [START makeUppercase]
// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
// [START makeUppercaseTrigger]
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
    .onCreate((snap, context) => {
// [END makeUppercaseTrigger]
      // [START makeUppercaseBody]
      // Grab the current value of what was written to the Realtime Database.
      const original = snap.data().original;
      console.log('Uppercasing', context.params.documentId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an 'uppercase' sibling in the Realtime Database returns a Promise.
      return snap.ref.set({uppercase}, {merge: true});
      // [END makeUppercaseBody]
    });
// [END makeUppercase]
// [END all]

However, I still get the permission denied error.

This is the output I get:

firebase > makeUppercase({original:'alphabets'},{params:{documentId:'mydoc'}})
'Successfully invoked function.'
firebase > info: User function triggered, starting execution
info: Uppercasing mydoc alphabets
info: Function crashed
info: { Error: 7 PERMISSION_DENIED: Missing or insufficient permissions.
    at Object.exports.createStatusError (C:\projects\myproject\functions\node_modules\grpc\src\common.js:87:15)
    at Object.onReceiveStatus (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:1188:28)
    at InterceptingListener._callNext (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:564:42)
    at InterceptingListener.onReceiveStatus (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:614:8)
    at callback (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:841:24)
  code: 7,
  metadata: Metadata { _internal_repr: {} },
  details: 'Missing or insufficient permissions.' }

My security rules are completely open but that did not resolve the error.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

I also thought that this might be an authentication issue so I have tried the following to initialize the app:

1

admin.initializeApp()

2

admin.initializeApp(functions.config().firebase);

3

var serviceAccount = require('path/to/serviceAccountKey.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});

The last one with my own credentials file and project configuration. All of these attempts still give me the missing permissions error.

Update: I deployed these functions to the cloud and they seem to be working perfectly but when running locally, I'm still getting a Error:7 Permission Denied.

Update 2: Set application default credentials using gcloud auth application-default login as per suggestion by @Doug Stevenson. Ensured environment variable GOOGLE_APPLICATION_CREDENTIALS is not set. Attempted the code in 1,2 and 3 above as well as 4 below with no success. Encountered the same error.

4

admin.initializeApp({
    credential: admin.credential.applicationDefault(), 
    databaseURL: "https://myapp-redacted.firebaseio.com"
  });
2
What happens if you try to deploy and invoke the function normally (not using the emulator)? - Doug Stevenson
The actual deploy works as expected. The error occurs only during emulation. - smartexpert
Could you edit the question to show the exact code that doesn't work the way you expect? (don't just link to it - show your actual code) - Doug Stevenson
Updated the question with actual code run. Ensured 'messages' collection exists. - smartexpert
Do you happen to have gcloud installed or work with other cloud services? - Doug Stevenson

2 Answers

6
votes

I hope that by now you've already resolved your issue. The same thing just happened to me and I'm sharing what it was in my case with the hope that it will help others.

We manage several firebase projects - productions, dev, staging etc.

we init the admin sdk with this:

let serviceAccount = require("../serviceAccountKey.json");
const databaseUrl = functions.config().environment.databaseurl
const storageBucket = functions.config().environment.storagebucket
const isDev = "true" === functions.config().environment.dev

// if dev environment
if (isDev) {
  serviceAccount = require("../serviceAccountKey-dev.json");
}

admin.initializeApp({
    projectId: functions.config().environment.projectid,
    credential:    admin.credential.cert(serviceAccount),
    databaseURL:   databaseUrl,
    storageBucket: storageBucket
});

You know how you need to do

firebase functions:config:get > .runtimeconfig.json

for the emulation to work. Well, my runtimeconfig was containing the wrong configuration. I was loading my serviceAccountKey-dev, but I was trying to access a different project. The second I fixed my runtimeconfig - it worked for me.

3
votes

Ultimately I just had to run gcloud auth application-default login to make sure I was logged in with the correct Google account.