【问题标题】:Getting the user id from a Firestore Trigger in Cloud Functions for Firebase?从 Cloud Functions for Firebase 中的 Firestore 触发器获取用户 ID?
【发布时间】:2021-10-06 21:22:14
【问题描述】:

在下面的示例中,有没有办法获取写信给“offers/{offerId}”的用户的用户 ID (uid)?我尝试按照here 的描述进行操作,但它在 Firestore 中不起作用。

exports.onNewOffer = functions.firestore
  .document('offers/{offerId}')
  .onCreate(event => {
    ...
});

【问题讨论】:

    标签: node.js firebase google-cloud-platform google-cloud-firestore google-cloud-functions


    【解决方案1】:

    您可以通过 Callable 函数添加您的数据,您可以从中访问当前用户 ID:

    exports.addNewOffer = functions.https.onCall(async (data, context) => {
      const uid = context.auth.uid
      const writeResult = await admin.firestore().collection('offers').add({ ... })
      ...
      return { id: writeResult.id, ... }
    })
    

    【讨论】:

      【解决方案2】:

      documentation 明确指出 context.auth 参数仅在实时数据库中可用。

      此字段仅为实时数据库触发器和 可调用函数。对于未经身份验证的用户,此字段为空。 对于不提供用户的 Firebase 管理员用户和事件类型 信息,该字段不存在。

      我个人意识到我的数据路径中已经有了 userId。

      export const onCreate = functions.firestore.document('docs/{userId}/docs/{docId}')
          .onCreate((snapshot, context) => {
              const userId = context.params.userId;
      

      【讨论】:

        【解决方案3】:

        我为此苦苦挣扎了一段时间,最后联系了 Firebase 支持:

        event.auth.uid 在 firestore 数据库触发器的事件对象中未定义。 (它适用于实时数据库触发器)

        当我console.log(event) 时,我在输出中找不到任何auth。

        官方支持回答:

        抱歉,尚未在 Firestore SDK 中添加身份验证。我们将其列在下一个功能中。

        请留意我们的发行说明以获取任何进一步的更新。

        我希望这可以为某人节省几个小时。

        更新:

        问题已关闭,该功能将永远不会实现:

        大家好 - 另一个更新。不幸的是,由于技术限制,将不会实现对 Firestore 触发器的 context.auth 的原生支持。但是,有一个不同的解决方案有望满足您的用例,但我不能分享细节。在这个论坛上,我们通常只开放可以在函数 SDK 本身内部解决的问题——我一直开放这个,因为它看起来很重要,我想提供一些关于跟踪这项工作的内部错误的更新。既然已经做出决定,我将关闭它。再次感谢大家的耐心等待,很抱歉没有更好的消息。请使用here 中引用的解决方法。

        【讨论】:

        • 嗨,我的云函数端点看起来不同,并且有 export const getUsersInThisChatRoom = functions.https.onRequest(async function(req,res) 没有 req.auth.uid 也没有事件。让uid = admin.auth().getUser(req.auth.uid); 我缺少什么?
        • 我看到你有一个 if 语句来避免循环。但是如果客户端手动添加 createdAt 字段呢?我认为这张支票不应该在那里。如果我错了,请纠正我。但我相信您想在服务器上添加时间戳并避免客户端添加它
        • 身份验证对象永远不会为 Firestore 实现 :( github.com/firebase/firebase-functions/issues/…
        【解决方案4】:

        这应该可以解决问题:

        exports.newMessage = functions.firestore
                .document('groups/messages/{messageId}')
                .onCreate((snap, context) => {
                    var fromId = snap.data().fromId;
        

        【讨论】:

        • 您的函数应该始终指向文档级别,而不是指向集合级别
        【解决方案5】:

        我如何解决这个问题的总结/一个可行的解决方案:

        在客户端

        将登录/当前用户的 uid(例如 creatorId)添加到他们正在创建的实体中。通过将firebase.auth().onAuthStateChanged() User 对象存储在您的应用状态中来访问此 uid。

        在 Firebase Firestore/数据库中

        向create 添加安全规则,以验证客户端提供的creatorId 值是否与经过身份验证的用户的uid 相同;现在您知道客户端没有欺骗 creatorId 并且可以在其他地方信任此值。

        例如

        match /entity/{entityId} {
          allow create: if madeBySelf();
        }
        
        function madeBySelf() {
          return request.auth.uid == request.resource.data.creatorId;
        }
        

        在 Firebase 函数中

        将onCreate 触发器添加到您创建的实体类型,以使用客户端提供且现已验证的creatorId 来查找创建用户的个人资料信息,并将此信息关联/附加到新实体文档。

        这可以通过以下方式完成:

        1. 在创建新帐户时创建users 集合和单独的user 文档,并使用应用程序有用的字段(例如displayName)填充新的user 文档。这是必需的,因为 Firebase 身份验证系统公开的字段不足以供消费者应用程序使用(例如,displayName 和 avatarURL 未公开),因此您不能仅仅依靠以这种方式查找创建用户的信息。

          例如(使用 ES6)

        import * as functions from 'firebase-functions'
        import * as admin from 'firebase-admin'
        
        const APP = admin.initializeApp()
        
        export const createUserRecord = functions.auth.user()
          .onCreate(async (userRecord, context) => {
            const userDoc = {
              id: userRecord.uid,
              displayName: userRecord.displayName || "No Name",
              avatarURL: userRecord.photoURL || '',
            }
            return APP.firestore().collection('users').doc(userRecord.uid).set(userDoc)
          })
        
        1. 现在您已经拥有经过验证的creatorId 值和有用的user 对象,将onCreate 触发器添加到您的实体类型(或您创建的所有实体)以查找创建用户的信息并将其附加到创建的对象。
        export const addCreatorToDatabaseEntry = functions.firestore
          .document('<your entity type here>/{entityId}')
          .onCreate(async (snapshot, context) => {
            const userDoc = await APP.firestore().collection('users').doc(snapshot.data().creatorId).get()
            return snapshot.ref.set({ creator: userDoc.data() }, { merge: true })
        })
        

        这显然会导致整个系统中出现大量重复的用户信息数据——你可以做一些清理工作(在上面的实现中,'creatorId` 在创建的实体上重复)——但现在它是超级的易于显示谁在整个应用中创建了什么,并且似乎是“Firebase 方式”。

        希望这会有所帮助。我发现 Firebase 在某些方面非常令人惊叹,并且让一些通常容易的事情(比如这样)变得比他们“应该”更难;总的来说,虽然我是一个主要的粉丝。

        【讨论】:

          【解决方案6】:

          snap._fieldsProto.uid.stringValue 是怎么回事

          示例:

          exports.hello = functions.firestore.document('hello/{worldId}').onCreate((snap, context) => {
             console.log(snap._fieldsProto.uid.stringValue)
          });
          

          【讨论】:

          • 我得到了同样的错误。这行不通! TypeError: Cannot read properties of undefined (reading 'uid')
          【解决方案7】:

          正如上面已经建议的,解决方法是添加一个包含数据本身的 user_id 字段,然后在服务器上读取它。

          这种方法的缺点是存在安全漏洞。由于我们不会在服务器上验证用户 ID,因此任何其他用户都可以通过将其 ID 与数据一起发送来冒充其他用户。

          对于安全关键应用程序,解决方案是使用安全规则来验证正确的 user_id 已与数据一起发送

          allow write: if resource.data.user_id == request.auth.uid;
          

          【讨论】:

          • 正确的规则应该检查 request.resource 而不是资源(更改前的值)allow write: if request.resource.data.user_id == request.auth.uid;
          【解决方案8】:

          在将其添加到 firestore 函数之前,一种解决方法是在创建文档时将 user_id 添加为字段,然后在之后删除。然后,您可以在 onCreate 函数中获取它,然后在将其用于您需要的用途后,在仍在该函数中时,只需从该文档中删除该字段即可。

          【讨论】:

          • 当然,如果您想跟踪更新,您可以拥有一个updatedBy 字段,该字段始终由客户端在更新文档时设置。但是,如果您想跟踪文档删除,这无济于事。您必须想出更难看的解决方法,也许您有一个单独的集合,您可以将已删除的文档移入其中,或者从不实际删除文档,而是将 deleted 属性设置为 true。
          【解决方案9】:

          您可以通过在用户上调用getIdToken()获取当前登录用户tokenId:https://firebase.google.com/docs/reference/functions/functions.auth.UserInfo

          【讨论】:

          猜你喜欢
          • 2021-08-05
          • 2018-05-21
          相关资源
          最近更新 更多