20

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

Lars-B
  • 284
  • 1
  • 2
  • 11
  • Your Firestore Rules are not allowing the read or write. Review your `firestore.rules` file. – Ronnie Royston Aug 14 '18 at 23:58
  • 4
    @ 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 Aug 15 '18 at 07:00
  • In my case using firebase serve --only functions I can read the firestore collection. However when deploying the firebase function to the cloud then I receive the permissions error. That leads me to believe everybody talking about firestore rules is sending everybody using admin.firestore() down blind alleyways. – Krafty Feb 19 '22 at 00:41

9 Answers9

24

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.

ADW
  • 4,177
  • 1
  • 14
  • 22
  • this is the correct answer, for some reason, default service account doesn't appear in IAM for me, I have added it back, not it worked :) – dcai Mar 05 '20 at 10:59
  • Took quite some time before I found this answer. Compute Engine account was the default account and although it did seem to have the same roles it did not work with Firestore. Changing the service account while creating the cluster did the trick. – Christoffer Dec 09 '21 at 13:51
14

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!

Matias Seguel
  • 788
  • 11
  • 17
11

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.

nVitius
  • 2,024
  • 15
  • 21
  • 1
    I was able to solve this problem in a similar manner with some differences. Details in my answer to this question. – ADW Oct 31 '19 at 15:03
  • Thank you so much. This solved my issue after wasting 4 hrs of debugging time, not sure why Google doesn't add this by default. I had to redploy the functions for the new IAM role to be effective. – Pritam Karmakar Jan 05 '23 at 06:25
6

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.

Lars-B
  • 284
  • 1
  • 2
  • 11
3

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'});
Seb
  • 200
  • 1
  • 5
  • 2
    OMG you legend. This took me so long to figure out. – CodeKiwi Sep 02 '20 at 21:32
  • This is it for me too, embarrassing... You can use an environment variable for your project id instead: https://firebase.google.com/docs/functions/config-env – Josh M. Dec 28 '20 at 19:31
0

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.

Dylan
  • 1
  • 2
0

Simplest solution that worked for me is running these commands:

firebase login //login through browser with project acc
firebase init firestore //When prompted select the firebase project

I ran this in the folder where I initialised the project.

Important : Select No whenever it asks if it should overwrite.

Note : This worked for me because, my local functions suddenly stopped working when I moved my project from one machine to the other. and reinitialising the project helped me, when none other did.

Ajay
  • 1
  • 1
0

In my case, my service account was not created properly, so I regenerated the service account and redeploy it.

Kyrielight
  • 119
  • 5
0

I was able to fix this cleanly with a new service account.

  1. IAM: Create new service account
  2. IAM: Assign roles 'Cloud Datastore User' and 'Firebase Rules Viewer' to new service account.
  3. Cloud Functions: Edit function details and set the new service account to it.
yood
  • 6,650
  • 3
  • 26
  • 24