13
votes

I'm trying to use a Firebase Cloud Function to update a document within my Firestore database, when one of my documents has been updated using a trigger. The trigger is called and working fine, but when I'm using the firebase admin instance to get the other document which I want to update, I'm getting the following error.

Error: 7 PERMISSION_DENIED: Missing or insufficient permissions.
    at Object.exports.createStatusError (/user_code/node_modules/firebase-admin/node_modules/grpc/src/common.js:87:15)
    at ClientReadableStream._emitStatusIfDone (/user_code/node_modules/firebase-admin/node_modules/grpc/src/client.js:235:26)
    at ClientReadableStream._receiveStatus (/user_code/node_modules/firebase-admin/node_modules/grpc/src/client.js:213:8)
    at Object.onReceiveStatus (/user_code/node_modules/firebase-admin/node_modules/grpc/src/client_interceptors.js:1256:15)
    at InterceptingListener._callNext (/user_code/node_modules/firebase-admin/node_modules/grpc/src/client_interceptors.js:564:42)
    at InterceptingListener.onReceiveStatus (/user_code/node_modules/firebase-admin/node_modules/grpc/src/client_interceptors.js:614:8)
    at /user_code/node_modules/firebase-admin/node_modules/grpc/src/client_interceptors.js:1019:24

function code:

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";

admin.initializeApp();
const settings = { timestampsInSnapshots: true };
admin.firestore().settings(settings);

export const onDocUpdate = functions.firestore
  .document("documents/{documentId}")
  .onUpdate((snapshot, context) => {
    console.log("onDocUpdate called ", context.params.documentId);
    const document = snapshot.after.data();
    console.log("Document: ", document);
    if (document.screw) {
      console.log("Document screw exists. ", document.screw);
      const docRef = admin
        .firestore()
        .collection("screws")
        .doc(document.screw);
      return docRef
        .get()
        .then(doc => {
          if (doc.exists) {
            console.log("Screw for document exists.");
          } else {
            console.error(
              "Screw for document not found! ",
              document.screw
            );
          }
        })
        .catch(error => {
          // Here I get the permission error :(
          console.error(
            "Screw for document doc load error!! ",
            error
          );
        });
    } else {
      console.error("Document is not bound to a screw! ", document.id);
    }
    return null;
  });

package.json

{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "@google-cloud/firestore": "^0.16.0",
    "firebase-admin": "^6.0.0",
    "firebase-functions": "^2.0.4",
    "protobufjs": "^6.8.8"
  },
  "devDependencies": {
    "tslint": "~5.8.0",
    "typescript": "~2.8.3"
  },
  "private": true
}

I assume that it has something to do with the permission of the admin instance, but no idea what the error could be, I've just followed the steps from the docs and the firebase videos on youtube.

My account is still on a Free Plan and I'm getting a notice in the logs the that I should configure the billing account, but if understand the documentation correct I should be able to access services within the Google Cloud Platform and so reading other nodes within the same database should not be a problem.

I've already found two similar issues here on stackoverflow, but did not find a solution there. Maybe someone else also faced this issue in the meantime and was able to solve it?

PERMISSION_DENIED Firestore CloudFunction TypeScript and Firebase error writing to Firestore via a Function: "7 PERMISSION_DENIED: Missing or insufficient permissions"

Update 1: Had another issue with the new timestampsInSnapshots setting. This has been fixed and the code above updated. The main issue permission denied is still present.

Update 2: Regarding the answer by @RonRoyston below. This is a Cloud Function and its using the Admin SDK from firebase-admin package to read the node. Hence it should not be effected by the firestore security rules. There's already a comment on one of the linked questions by @DougStevenson mentioning this. Based on the Admin SDK documentation it should be enough to initialize it by calling admin.initializeApp(), but unfortunately in my case it isn't. I've read no where that there is any need to apply any special IAM settings within the service accounts or security rules when using Cloud Functions, and so I didn't touch any of these settings.

Cheers, Lars

6
Your Firestore Rules are not allowing the read or write. Review your firestore.rules file.Ronnie Royston
@ RonRoyston Thanks for your reply, but this is a Cloud Function using firebase-admin, it should not be effected by the firestore security rules. See my complete answer in the question (Update 2). It also contains a link by @DougStevenson mentioning this.Lars-B

6 Answers

12
votes

I had the same issue. And like nvitius, I solved it by changing permissions.

When creating a function, the default service account appears to be <project-name>@appspot.gserviceaccount.com.

You can check this by clicking on the Environment variables, networking, timeouts and more link:

To show Service Account

And then you can verify or change the account to the `App Engine default service account':

App Engine default service account

After this, I went to the IAM to verify the permission this service account had.

But the IAM did not have this service account listed/added.

So I add it >> Add >> New members. Start typing the ID of the project and the service account should pop-up in the drop-down.

And then I gave it the following permissions:

  • Project >> Editor (It may have this already)
  • Datastore >> Cloud Datastore Owner
  • Storage >> Storage Admin

Hope this helps.

11
votes

My solution was setting up the serviceAccount, check the following code snippet:

var admin = require("firebase-admin");

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

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://your-database-url.firebaseio.com"
});

You can generate the serviceAccountKey on: Firebase dashboard -> Project Settings -> Service Accounts tab

Hope it helps!

8
votes

I had the same exact problem with Cloud Functions. My issue was not solved by deleting/redeploying the function. It turns out that, when my project was provisioned, the IAM roles for the default service account were not added. I had to add an Editor role in the IAM Admin panel for <project-name>@appspot.gserviceaccount.com.

4
votes

I've finally got it working. I didn't change any firestore security rules nor any IAM stuff. I deleted the function which was running on us-central1. Created the same Cloud Function project again, copied over my existing code, but this time I deployed it to europe-west1 and it worked out of the box.

I assume that something might failed during the first initial deployment to us-central1 and after that my project stuck with the error even if I had deleted and redeployed the function several times. Not sure what happened exactly, because no obvious error has been displayed. Maybe someone of the firebase team who knows the internal workflows can tell us if something like this can happen and if yes, how to deal with it.

For now the above steps solved my issue.

2
votes

I've met the same error and have not found the solution anywhere so I post it here if it can help someone...

We use 2 firebase projects (DEV, PROD) and deploy the same functions on both. The PERMISSION_DENIED appears also when you specify a wrong projectId in :

admin.initializeApp({projectId: 'your_project_id'});
0
votes

One solution that worked for me:

  • I was switching between firebase projects where I wanted to emulate a Cloud Function and see the results in production in Firestore (I already had the Cloud Function creating data in production Firestore in a project (ex. "project-dev")
  • Permission denied error kept happening when creating new document in Firestore via the cloud function even though I was using the downloaded the serviceAccount credentials for the new project ("project-sandbox")
let serviceAccount = require('../credentials-sb.json');
admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://<project>-sandbox.firebaseio.com"
});
  • I noticed I had not yet run the firebase use command for my new project, nor was it added to .firebaserc. I was "using" a project other than the one that the credentials file would have needed (ex: Firebase CLI set to use "project-dev", I had not yet run the firebase use command for my new "project-sandbox")
  • After I ran firebase use project-sandbox, I ran npm run shell and executed my function from the emulator and everything worked. The emulator worked as it should for "project-sandbox" as it had for "project-dev".

I can only assume this is due to setting a project-id environment variable which is either sensed from the environment when running admin.initializeApp() or that I could have set manually like this (see comment from @Seb above):

let serviceAccount = require('../credentials-sb.json');
admin.initializeApp({
    project-id: '<project>-sandbox'
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://inkling-sandbox.firebaseio.com"
});

I hope this helps! Please let me know if it did.