繁体   English   中英

如何为 Google Cloud Function Firestore 触发器使用可重用代码?

[英]how to use reusable code for Google Cloud Function Firestore trigger?

对不起,我是后端开发的新手。 我通常创建快速中间件来在 Node JS 中一遍又一遍地执行相同的操作

例如,如果我想检查请求是否具有作为餐厅所有者的有效授权,那么我创建一个像这样的中间件

const restaurantOwnerOnlyMiddleware = async (req, res, next) => {
    const {restaurantId} = req.params;
    const user = req.user;

    const restaurantDoc = await db.collection("restaurants").doc(restaurantId).get(); // db-read
    const restaurant = restaurantDoc.data();

    if (restaurant.owner !== req.user.uid) {
        res.status(403).send("Unauthorized");
        return;
    }
  
    req.restaurant = restaurant; 

    next();
};

然后在所需的路由中使用中间件

app.put("/:restaurantId/stars", [restaurantOwnerOnlyMiddleware], (req, res) => {
    

    // do something
  
    res.send();
});

现在例如我想在云 function 中使用 Firestore 触发器,我想在创建菜单后执行某些操作,但此操作仅适用于经过验证的餐厅所有者

exports.createMenu = functions.firestore
    .document('restaurant/{restaurantID}/menu/{menuId}')
    .onCreate((snap, context) => {


    // after a menu is created 
    // then if the menu creator is verified restaurant owner, then do something in here
   
     

});

所以检查经过验证的餐厅所有者将被执行多次,不仅在这个firestore触发器中,而且我也会在其他firestore触发器甚至存储触发器中使用它。 在 Node JS 中,我通常会创建一个中间件

但是如何在 Cloud Function 中进行这样的可重用授权检查?

如果我只制作一次并在多个地方使用它会更好。 怎么做?

我用的是 typescript,但是 javascript 没问题

使用 Firestore 安全规则

您应该使用Firebase 身份验证以及Firestore 安全规则来获得您想要的结果。

这是一个简单的 Firestore firestore security rule ,它允许Firebase 身份验证的用户根据以下内容从restaurant访问文档

  • 如果用户已通过身份验证,则允许READ
  • 如果restaurant.owner与经过身份验证的用户的uid相同,则允许WRITE
service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{document=**} {
        allow write: if request.auth != null && 
            request.auth.uid == resource.data.owner;
        allow read: if request.auth != null;
    }
  }
}

Firebase 云中的授权 function 触发器

由于触发器是基于在/restaurant/{restaurantID}/menu/中创建文档等条件运行的,因此您应该检查文档创建部分本身的授权

这样,如果创建了文档,则必须授权用户。 然后触发代码将运行。

However, if you are creating http callable cloud functions , then you can check user authentication there using the Firebase AdminSDK to authenticate Firebase user tokens (or any of your preferred authentication strategies) in the function.

笔记

详细查看Firebase 身份验证Firestore 安全规则,因为它们允许广泛的 scope 功能。

在继续之前的重要说明,请记住,仅仅因为文档restaurant/{restaurantID}/menu/{menuId}存在,并不意味着restaurant/{restaurantID}可以删除文档,而不删除它的 subcollections

根据您需要知道用户是所有者的原因,您还可以使用Firestore 安全规则Cloud Storage 安全规则来保护您的文档和文件。

对于创建菜单 function,您可能会执行以下操作:

exports.createMenu = functions.firestore
  .document('restaurant/{restaurantID}/menu/{menuId}')
  .onCreate(async (snap, context) => {
    const restaurantSnapshot = await admin.firestore().doc(`restaurant/${context.params.restaurantID}`).get();
    
    if (!restaurantSnapshot.exists) {
      console.error('Linked restaurant does not exist');
      return;
    }
    
    if (uid === restaurantSnapshot.get('owner')) {
      console.log('Current user is not owner');
      /* do something */
      return;
    }
    
    console.log('Current user is owner, and linked restaurant data exists');
    /* do something */
  });

我们可以将其提取到 function 中,变为:

// returns `true` when 'restaurant/{restaurantID}' exists AND it's owner is the same as the given user ID
async function isUserOwnerOfRestaurant(uid, restaurantID) {
  const restaurantSnapshot = await admin.firestore().doc(`restaurant/${restaurantID}`).get();
  return uid === restaurantSnapshot.get('owner');
}

exports.createMenu = functions.firestore
  .document('restaurant/{restaurantID}/menu/{menuId}')
  .onCreate(async (snap, context) => {
    if (await isUserOwnerOfRestaurant(context.auth.uid, context.params.restaurantID)) {
      console.log('Current user is not owner');
      return;
    }
    
    console.log('Current user is owner, and linked restaurant data MAY exist');
    /* do something */
  });

这里需要注意的是,当restaurant/${restaurantID}不存在时,使用isUserOwnerOfRestaurant将返回false 这意味着您可能正在为不存在的餐厅创建菜单。

如果餐厅数据实际存在很重要,我们可以继续进行以下操作:

// returns the restaurant's data if 'restaurant/{restaurantID}' exists AND it's owner is the same as the given user ID, otherwise throws an error
async function assertUserOwnerAndGetRestaurantData(uid, restaurantID) {
  const restaurantSnapshot = await admin.firestore().doc(`restaurant/${restaurantID}`).get();
  
  if (!restaurantSnapshot.exists) {
    throw new Error('restaurant-not-found');
  }
  
  if (uid !== restaurantSnapshot.get('owner')) {
    throw new Error('user-not-owner');
  }
  
  return restaurant.data();
}

exports.createMenu = functions.firestore
  .document('restaurant/{restaurantID}/menu/{menuId}')
  .onCreate(async (snap, context) => {
    const restaurantData = await assertUserOwnerAndGetRestaurantData(context.auth.uid, context.params.restaurantID);
    
    // if here, restaurant exists AND the current user is the owner AND
    //          restaurantData contains the data for the restaurant just in case it's useful
    
    console.log('Current user is owner, and linked restaurant data exists');
    /* do something */
  });

现在,如果您要将其用于带有文件元数据的 Cloud Storage 触发器,您可以使用:

exports.newFileUploaded = functions.storage.object().onFinalize(async (object) => {
  const filePathParts = object.name.split('/');
  const fileMetadata = object.metadata; // insert user's ID here (or better yet - their ID token), see https://firebase.google.com/docs/storage/web/upload-files#add_file_metadata
  
  if (filePathParts[0] == 'restaurant') {
    // new restaurant file
    const restuarantId = filePathParts[1]; // assuming restaurant/{restuarantId}/some/file/path.png
    const restuarantData = await assertUserOwnerAndGetRestaurantData(fileMetadata.uid, restuarantId);
    // if here, restaurant exists AND user ID in the metadata is the owner AND
    //          restaurantData contains the data for the restaurant just in case it's useful
    
    // do something with file, like generate thumbnails, update restaurant and menu info in Firestore, etc.
    return;
  }
  
  console.log(`No action needed for file at ${object.name}.`);
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM