如何在 Cloud Functions 存储触发器中获取经过身份验证的 Firebase 用户的 uid

Posted

技术标签:

【中文标题】如何在 Cloud Functions 存储触发器中获取经过身份验证的 Firebase 用户的 uid【英文标题】:How to get the uid of the authenticated Firebase user in a Cloud Functions storage trigger 【发布时间】:2018-12-13 03:58:54 【问题描述】:

背景:我正在使用 Firebase Cloud Functions、新的 Firestore 数据库和带有 android 客户端的存储桶。

我想要完成的事情: 当用户将图片上传到存储桶时,我想使用云功能获取存储桶中图像位置的文件路径/链接,并将此字符串作为新文档存储在名为“图片”的新集合下当前登录用户在 Firestore 中的文档。

这样,我可以在 Firestore 中看到每个用户直接上传的图像,并且可以更轻松地将特定用户的图像拉到 Android 客户端。

到目前为止我已完成的工作: 1. 用户首次登录时,会在新的 Firestore 数据库中创建一个用户文档。 2. 登录用户可以上传图片到存储桶。 3.使用Firebase Cloud Functions,我设法获得了存储位置的文件路径/链接如下:

/**
 * When a user selects a profile picture on their device during onboarding,
 * the image is sent to Firebase Storage from a function running on their device. 
 * The cloud function below returns the file path of the newly uploaded image. 
 */
exports.getImageStorageLocationWhenUploaded = functions.storage.object().onFinalize((object) => 
  const filePath = object.name; // File path in the bucket.
  const contentType = object.contentType; // File content type.

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith('image/')) 
    console.log('This is not an image.');
    return null;
  
console.log(filePath);
);

问题:如何使用 Cloud Functions 获取当前登录用户并将该用户上传的图像文件路径/链接作为新文档存储在 Firestore 数据库中的该登录用户文档下?

【问题讨论】:

【参考方案1】:

目前,通过 Cloud Storage 触发器,您无法访问经过身份验证的用户信息。要解决这个问题,您必须执行一些操作,例如将 uid 嵌入文件的路径中,或者将 uid 作为元数据添加到文件上传中。

【讨论】:

感谢道格的解释!为了将上传的图像与上传这些图像的用户连接起来,创建与此 uid 命名相同的新存储桶文件夹是否更好?还是我应该像您建议的那样将其添加为元数据? 我会将 uid 放在路径中,以便文件也可以使用 Cloud Storage 安全规则受到保护,以便仅对该用户进行读写。【参考方案2】:

我遇到了类似的问题,我想将用户上传的图片与他/她的 uid 相关联。我找到了一个优雅的解决方案,它不一定需要在文件路径中插入 uid,甚至不需要将其作为元数据添加到文件上传中。事实上,uid 是通过标准的 idToken 编码安全地传输到数据库的。此示例使用了生成缩略图云功能示例的修改版本(找到here),我相信该问题的作者正在使用/暗示。步骤如下:

客户端:

    创建一个触发器函数,一旦用户上传图像就会运行 - 该函数将直接调用云函数(通过 httpsCallable 方法)。云函数将接收用户 uid(idToken 编码)以及您可能希望发送的任何图像元数据。然后,云函数将返回图像的签名 URL。
const generateThumbnail = firebase.functions().httpsCallable('generateThumbnail');
const getImageUrl = (file) => 
  firebase.auth().currentUser.getIdToken(true)
    .then((idToken) => generateThumbnail( 
      idToken, 
      imgName: file.name, 
      contentType: file.type 
    ))
    .then((data) =>    
      // Here you can save your image url to the app store, etc. 
      // An example of a store action: 
      // setImageUrl(data.data.url);
    )
    .catch((error) =>  
      console.log(error); 
  )

    创建图片上传函数——这是一个标准的文件上传处理函数;您可以将 uid 部分设置为图像的存储文件路径位置(如果需要),但也可以在图像上传后触发 firebase 功能。这可以使用on 方法的第三个参数来实现。将上面的触发函数作为第三个参数包含在此处。
// Function triggered on file import status change from the <input /> tag
const createThumbnail = (e) => 
  e.persist();
  let file = e.target.files[0];
  // If you are using a non default storage bucket use this
  // let storage = firebase.app().storage('gs://your_non_default_storage_bucket');
  // If you are using the default storage bucket use this
  let storage = firebase.storage();
  // You can add the uid in the image file path store location but this is optional
  let storageRef = storage.ref(`$uid/thumbnail/$file.name`);
  storageRef.put(file).on('state_changed', (snapshot) => , (error) => 
    console.log('Something went wrong! ', error); 
  , getImageUrl(file));

服务器端:

    创建云函数将图像转换为调整大小的缩略图并生成签名 URL - 此云函数从存储中获取图像,将其转换为缩略图(基本上减小了其尺寸但保持初始纵横比)使用 ImageMagick(默认情况下安装在所有云函数实例上)。然后它会生成图像位置的签名 URL 并将其返回给客户端。
// Import your admin object with the initialized app credentials
const mkdirp = require('mkdirp');
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');

// Max height and width of the thumbnail in pixels.
const THUMB_MAX_HEIGHT = 25;
const THUMB_MAX_WIDTH = 125;
// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';

async function generateThumbnail(data) 
  
  // Get the user uid from IdToken
  const  idToken, imgName, contentType  = data;
  const decodedIdToken = await admin.auth().verifyIdToken(idToken);
  const uid = decodedIdToken.uid;
  
  // File and directory paths.
  const filePath = `$uid/thumbnail/$imgName`;
  const fileDir = path.dirname(filePath);
  const fileName = path.basename(filePath);
  const thumbFilePath = path.normalize(path.join(fileDir, `$THUMB_PREFIX$fileName`));
  const tempLocalFile = path.join(os.tmpdir(), filePath);
  const tempLocalDir = path.dirname(tempLocalFile);
  const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith('image/')) 
    return console.log('This is not an image.');
  

  // Exit if the image is already a thumbnail.
  if (fileName.startsWith(THUMB_PREFIX)) 
    return console.log('Already a Thumbnail.');
  

  // Cloud Storage files.
  const bucket = initDb.storage().bucket('your_bucket_if_non_default');
  const originalFile = bucket.file(filePath);
  
  // Create the temp directory where the storage file will be downloaded.
  // But first check to see if it does not already exists
  if (!fs.existsSync(tempLocalDir)) await mkdirp(tempLocalDir);

  // Download original image file from bucket.
  await originalFile.download( destination: tempLocalFile );
  console.log('The file has been downloaded to', tempLocalFile);

  // Delete the original image file as it is not needed any more
  await originalFile.delete();
  console.log('Delete the original file as it is not needed any more');
  
  // Generate a thumbnail using ImageMagick.
  await spawn('convert', [ tempLocalFile, '-thumbnail', 
    `$THUMB_MAX_WIDTHx$THUMB_MAX_HEIGHT>`, tempLocalThumbFile], 
     capture: ['stdout', 'stderr'] 
  );
  console.log('Thumbnail created at', tempLocalThumbFile);
  
  // Uploading the Thumbnail.
  const url = await uploadLocalFileToStorage(tempLocalThumbFile, thumbFilePath, 
  contentType);
  console.log('Thumbnail uploaded to Storage at', thumbFilePath);
  
  // Once the image has been uploaded delete the local files to free up disk space.
  fs.unlinkSync(tempLocalFile);
  fs.unlinkSync(tempLocalThumbFile);
  
  // Delete the uid folder from temp/pdf folder
  fs.rmdirSync(tempLocalDir);

  await admin.database().ref(`users/$uid/uploaded_images`).update( logoUrl: url[0] );
  return  url: url[0] ;


// Upload local file to storage
exports.uploadLocalFileToStorage = async (tempFilePath, storageFilePath, 
  contentType, customBucket = false) => 
  let bucket = initDb.storage().bucket();
  if (customBucket) bucket = initDb.storage().bucket(customBucket);
  const file = bucket.file(storageFilePath);
  try 
    // Check if file already exists; if it does delete it
    const exists = await file.exists();
    if (exists[0]) await file.delete();
  
    // Upload local file to the bucket
    await bucket.upload(tempFilePath, 
      destination: storageFilePath,
      metadata:  cacheControl: 'public, max-age=31536000', contentType 
    );
    const currentDate = new Date();
    const timeStamp = currentDate.getTime();
    const newDate = new Date(timeStamp + 600000);
    const result = await file.getSignedUrl(
      action: 'read',
      expires: newDate
    );
    return result;
   catch (e) 
    throw new Error("uploadLocalFileToStorage failed: " + e);
  
;

【讨论】:

【参考方案3】:
if (firebase.auth().currentUser !== null) 
        console.log("user id: " + firebase.auth().currentUser.uid);

获取用户用户 ID 的简单方法。

【讨论】:

以上是关于如何在 Cloud Functions 存储触发器中获取经过身份验证的 Firebase 用户的 uid的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Cloud Functions 连接 Google Cloud SQL?

如何在 Cloud Functions 中访问云存储

如何使用 Cloud Build 在 GCP 上部署功能?

从 Cloud Functions for Firebase 获取当前用户 ID(PubSub 触发器)

Cloud PubSub 重复消息触发的 Cloud Functions

如何从 Google Cloud Functions (nodeJS) 发送 HTTP 请求