Cloud Functions, deleting Firestore SubCollections, is AdminToken necessary?

Viewed 359

I am trying to build callable cloud functions, when users delete a post, it also try to delete the comments, which is a sub-collection of the post. so I saw the example and implemented just like a documentation example

const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');

admin.initializeApp({
  serviceAccountId: 'xxxxxx-xxxxx@appspot.gserviceaccount.com'
  }
);

exports.mintAdminToken = functions.https.onCall(async (data: any, context: any) => {
  const uid = data.uid;

  const token = await admin
    .auth()
    .createCustomToken(uid, { admin: true });

  return { token };
});

exports.recursiveDelete = functions
  .runWith({
    timeoutSeconds: 540,
    memory: '2GB'
  })
  .https.onCall(async (data: any, context: any) => {
    // Only allow admin users to execute this function.
    if (!(context.auth && context.auth.token && context.auth.token.admin)) {
      throw new functions.https.HttpsError(
        'permission-denied',
        'Must be an administrative user to initiate delete.'
      );
    }

    const path = data.path;
    console.log(
      `User ${context.auth.uid} has requested to delete path ${path}`
    );

    await firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true,
        token: functions.config().fb.token
      });

    return {
      path: path 
    };
  });

and I succeeded in receiving the custom token to the client. but what I have to do now? after getting token I called the "recursiveDelete" function from client but it occurs error PERMISSION_DENIED

  1. Should the user who received the token be initialized with a new custom admin token? (if I misunderstand let me know)
  2. Is the admin token really necessary when deleting a sub collection like this? It's difficult to use, so I ask.
1 Answers

I don't think that you really need a custom token for this use case and I suggest that you use firebase firestore rules rather than implementing your own role based authentication.

Steps to follow:

1- create a collection that you may call "users" and include in it a field of the role that this user may have such as "ADMIN". every document id in this collection can be the auth uid of users that firebase auth generates. you can get this uid from your frontend by using the currentUser prop and it's all explained here

2- protect your database with firestore rules as such:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // only admins can remove posts
    match /posts/{postID} {
      allow read, write: if isAdmin();
    }

    // only admins can remove comments
    match /comments/{commentID} {
      allow read, write: if isAdmin();
    }

    // this function will check if the caller has an admin role and allow or disallow the task upon that
    function isAdmin() {
      return get(/databases/$(database)/documents/
          users/$(request.auth.uid)).data.role == "ADMIN";
   }

  }
}

3- after you succefully deletes a post document you can create a function with onDelete trigger that get invoked and deletes the comments subcollection recursivley and to do that you should include this bit of code:

const client = require('firebase-tools');

exports.recursiveDelete = functions.firestore
    .document('posts/{postID}')
    .onDelete((snap, context) => {
     .....
     await client.firestore
      .delete(collectionPath, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true
      });
} 
Related