每当我运行 Firebase 函数时,如何解决此 401 错误?

问题描述 投票:0回答:1

我没有任何软件经验,我在 11 月底才开始使用 AI 为自己构建一些东西。我正在使用一个反应网络应用程序,并尝试在生产中部署。我正在尝试运行我的 firebase 函数,但收到此错误:

加载资源失败:服务器响应状态401() LGChat.js:78 完整错误对象:FirebaseError:必须在经过身份验证时调用该函数。

它告诉我我需要经过身份验证才能执行此操作。但是,我已完全通过身份验证。我已登录,Firebase 存储正在提取与该帐户关联的文档。所以我知道其他 Firebase 服务正在运行。我删除了该帐户并重新创建了它,所以我知道身份验证没有被破坏。根据我的理解,可调用函数的工作方式是 firebase 使令牌在其末端工作。所以我添加到代码中的任何内容都不会影响发送的令牌。

每次我尝试使用该函数时,都会发送令牌,然后 firebase 会拒绝它。我无法想象令牌是不正确的。它是唯一一个正在生成并从其自身授权中拉出自身的 Firebase。

llms 提出的主要是环境问题。我已经检查了他们建议的每一项。我还确认我已完全登录 google cloud sdk。

有谁有任何经验可以为我指明解决这个问题的新方向吗?这似乎是一个愚蠢的问题哈哈。

错误消息是从我的functions/index.js 和名为LGChat 的文件中调用的。请让我知道我还可以发送哪些内容来提供帮助。我对此很陌生,所以请随意假设我不知道你在说什么。另外,如果我错误地构建了这篇 stackoverflow 帖子,抱歉。

我已经仔细检查了所有潜在的环境问题。

我确认我已登录谷歌云。

我已阅读可调用文档以确认所有设置均正确。

我已经确认了我的 gcloud 秘密等,一切都正确。

我已确认其他 Firebase 服务正在运行。

我已授予所有用户调用者访问权限。

我在每次重新部署尝试时都会清除缓存。

我错过了什么?

提前感谢您的帮助。

错误消息截图

LG聊天文件:


  const processDocument = async () => {
    if (!selectedFile || isProcessing || isProcessed || !currentUser) return;

    setIsProcessing(true);
    setError(null);
    setProcessingStatus('Starting processing...');

    try {
      // Use the already initialized functions instance from above
      const processPdfDocument = httpsCallable(functions, 'processPdfDocument', {
        timeout: 540000
      });

      console.log('Processing document with auth:', {
        uid: currentUser?.uid,
        email: currentUser?.email
      });

      console.log('Document details being sent:', {
        fileId: selectedFile.id,
        fileName: selectedFile.name,
        fileUrl: selectedFile.url,
        userId: currentUser.uid
      });

      setProcessingStatus('Processing PDF...');
      console.log('Processing PDF...');
      const result = await processPdfDocument({
        fileId: selectedFile.id,
        fileName: selectedFile.name,
        fileUrl: selectedFile.url,
        userId: currentUser.uid
      });
      console.log('Processing PDF result:', result);

      if (result.data?.success) {
        setIsProcessed(true);
        setProcessingStatus(
          `Successfully processed ${result.data.pagesProcessed} pages`
        );
      } else {
        throw new Error(
          'Processing failed: ' + (result.data?.error || 'Unknown error')
        );
      }
    } catch (error) {
      console.error('Error processing document:', error);
      setError(error.message || 'Failed to process document.');
      setIsProcessed(false);
      setProcessingStatus('Processing failed');
    }
  };

函数/index.js:

// Process PDF
exports.processPdfDocument = onCall(
  {
    memory: "2GiB",
    timeoutSeconds: 540,
    cors: true,
    enforceAppCheck: false,
    secrets: [
      "OPENAI_API_KEY",
      "PINECONE_API_KEY",
      "PINECONE_INDEX_NAME",
      "PINECONE_HOST"
    ]
  }, 
  async (request, context) => {
    // Log the full context and request for debugging
    console.log("[processPdfDocument] Request data:", {
      auth: request.auth,
      rawRequest: request.rawRequest,
      data: request.data
    });

    console.log("[processPdfDocument] Context:", {
      auth: context.auth,
      rawRequest: context.rawRequest
    });

    // Check auth
    if (!request.auth) {
      console.error("[processPdfDocument] No auth context");
      throw new HttpsError(
        "unauthenticated",
        "The function must be called while authenticated."
      );
    }

    // Verify UID
    if (!request.auth.uid) {
      console.error("[processPdfDocument] No UID in auth context");
      throw new HttpsError(
        "unauthenticated",
        "Invalid authentication. Please sign in again."
      );
    }

    try {
      // Verify the token
      const decodedToken = await admin.auth().verifyIdToken(context.auth.token);
      console.log("[processPdfDocument] Token verified:", decodedToken.uid);

      console.log("[processPdfDocument] Auth context:", {
        hasAuth: Boolean(context.auth),
        uid: context.auth ? context.auth.uid : null,
        token: context.auth ? Boolean(context.auth.token) : false,
        app: Boolean(context.app)
      });

      if (!request.auth || !request.auth.uid) {
        console.error("[processPdfDocument] Authentication error: No valid auth context");
        throw new HttpsError(
          "unauthenticated",
          "The function must be called while authenticated."
        );
      }

      try {
        console.log("[processPdfDocument] Request data:", {
          ...request.data,
          auth: {uid: context.auth.uid}
        });

        const {fileId, fileName, path} = request.data;
        const uid = context.auth.uid;

        // Validate parameters with detailed logging
        const missingParams = [];
        if (!fileId) missingParams.push("fileId");
        if (!fileName) missingParams.push("fileName");
        if (!path) missingParams.push("path");

        if (missingParams.length > 0) {
          const errorMsg = `Missing required parameters: ${missingParams.join(", ")}`;
          console.error("[processPdfDocument] Parameter validation failed:", {
            received: {fileId, fileName, path},
            missing: missingParams
          });
          throw new HttpsError("invalid-argument", errorMsg);
        }

        // Validate config with error handling
        let config;
        try {
          config = getConfig();
          console.log("[processPdfDocument] Configuration validated successfully");
        } catch (configError) {
          console.error("[processPdfDocument] Configuration error:", configError);
          throw new HttpsError(
            "failed-precondition",
            `Configuration error: ${configError.message}`
          );
        }

        // Initialize storage and get file
        const storage = getStorage();
        const bucket = storage.bucket();
        const tempFilePath = `/tmp/${fileId}-${Date.now()}.pdf`;

        // Download file with detailed error handling
        try {
          console.log("[processPdfDocument] Attempting to download file:", {path, tempFilePath});
          await bucket.file(path).download({destination: tempFilePath});
          console.log("[processPdfDocument] File downloaded successfully");
        } catch (downloadError) {
          console.error("[processPdfDocument] Download error:", {
            error: downloadError,
            path,
            tempFilePath
          });
          throw new HttpsError(
            "internal",
            `Failed to download file: ${downloadError.message}`
          );
        }

        // Process PDF with error handling
        let pdfContent;
        try {
          const dataBuffer = await fs.readFile(tempFilePath);
          console.log("[processPdfDocument] File read successfully, size:", dataBuffer.length);
          
          pdfContent = await pdf(dataBuffer, {
            pageNumbers: true,
            normalizeWhitespace: true,
            disableCombineTextItems: false
          });
          console.log("[processPdfDocument] PDF parsed successfully, pages:", pdfContent.numpages);
        } catch (pdfError) {
          console.error("[processPdfDocument] PDF processing error:", pdfError);
          throw new HttpsError(
            "internal",
            `Failed to process PDF: ${pdfError.message}`
          );
        }

        // Create text chunks
        const splitter = new RecursiveCharacterTextSplitter({
          chunkSize: 1000,
          chunkOverlap: 200
        });

        let allDocs = [];
        try {
          const docs = await splitter.createDocuments(
            [pdfContent.text],
            [{
              pageNumber: 1,
              fileId,
              fileName,
              userId: uid
            }]
          );
          allDocs = docs;
          console.log("[processPdfDocument] Created chunks:", allDocs.length);
        } catch (splitError) {
          console.error("[processPdfDocument] Text splitting error:", splitError);
          throw new HttpsError(
            "internal",
            `Failed to split text: ${splitError.message}`
          );
        }

        // Initialize Pinecone with error handling
        let pineconeIndex;
        try {
          const pineconeOptions = {
            apiKey: config.pineconeApiKey
          };
          if (config.pineconeHost) {
            pineconeOptions.controllerHostUrl = config.pineconeHost;
          }
          const pinecone = new Pinecone(pineconeOptions);
          pineconeIndex = pinecone.index(config.pineconeIndexName);
          console.log("[processPdfDocument] Pinecone initialized successfully");
        } catch (pineconeError) {
          console.error("[processPdfDocument] Pinecone initialization error:", pineconeError);
          throw new HttpsError(
            "internal",
            `Failed to initialize Pinecone: ${pineconeError.message}`
          );
        }

        // Create and store embeddings
        try {
          const embeddings = new OpenAIEmbeddings({
            openAIApiKey: config.openaiApiKey,
            batchSize: 100
          });

          await PineconeStore.fromDocuments(allDocs, embeddings, {
            pineconeIndex,
            namespace: uid,
            maxConcurrency: 5
          });
          console.log("[processPdfDocument] Documents stored in Pinecone successfully");
        } catch (embeddingError) {
          console.error("[processPdfDocument] Embedding/storage error:", embeddingError);
          throw new HttpsError(
            "internal",
            `Failed to create/store embeddings: ${embeddingError.message}`
          );
        }

        // Cleanup temp files
        try {
          await fs.unlink(tempFilePath);
          console.log("[processPdfDocument] Cleaned up temporary file");
        } catch (cleanupError) {
          console.warn("[processPdfDocument] Cleanup warning:", cleanupError);
          // Don't throw on cleanup errors
        }

        return {
          success: true,
          chunksProcessed: allDocs.length,
          pagesProcessed: pdfContent.numpages || 1
        };

      } catch (error) {
        console.error("[processPdfDocument] Top-level error:", {
          message: error.message,
          code: error.code,
          stack: error.stack
        });
        
        if (error instanceof HttpsError) {
          throw error;
        }
        
        throw new HttpsError(
          "internal",
          `Processing failed: ${error.message}`
        );
      }
    } catch (error) {
      console.error("[processPdfDocument] Token verification failed:", error);
      throw new HttpsError(
        "unauthenticated",
        "Invalid authentication token. Please sign in again."
      );
    }
  }
);
javascript firebase function authentication google-cloud-functions
1个回答
0
投票

您使用的工具混淆了 Firebase Functions SDK 的 v1 和 v2 版本。

onCall(options, handler)API
firebase-functions/v2
的签名(当前默认)是:

export declare function onCall<T = any, Return = any | Promise<any>>(opts: CallableOptions, handler: (request: CallableRequest<T>, response?: CallableProxyResponse) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;

特别值得注意的是,

handler
参数的类型为:

(request: CallableRequest<T>, response?: CallableProxyResponse) => Return

onCall(options, handler) API
(旧版)中
firebase-functions/v1
的签名是:

export declare function onCall(handler: (data: any, context: CallableContext) => any | Promise<any>): HttpsFunction & Runnable<any>;

特别值得注意的是,

handler
参数的类型为:

(data: any, context: CallableContext) => any | Promise<any>

在您的代码中,您当前正在使用:

async (request, context) => {
  // ...
}

因为第二个参数(错误标记为

context
)是
CallableProxyResponse
对象(不是
CallableContext
对象),所以它上面没有
auth
rawRequest
属性。

这意味着

context.auth
context.rawRequest
都返回
undefined
。由于
undefined
值在 JSON 中隐藏,因此您应该注意到您的 Cloud Functions 日志有一条如下所示的消息:

INFO: [processPdfDocument] Request data: { auth: { uid: "<userid>", token: "..." }, rawRequest: { ... }, data: { fileId: "...", fileName: "...", fileUrl: "...", userId: "<userid>" }}

INFO: [processPdfDocument] Context: {}

最终,这意味着您的错误是由以下行引发的:

const decodedToken = await admin.auth().verifyIdToken(context.auth.token);

这应该会产生类似于以下内容的 Cloud Functions 错误消息:

ERROR: [processPdfDocument] Token verification failed: Uncaught TypeError: Cannot read properties of undefined (reading 'token')

这是由

functions/index.js
文件底部的这些行生成的:

} catch (error) {
  console.error("[processPdfDocument] Token verification failed:", error); // <-- logged on server
  throw new HttpsError(
    "unauthenticated",
    "Invalid authentication token. Please sign in again." // <-- logged on client
  );
}

此“身份验证令牌无效。请重新登录。”错误消息与您在客户端上看到的内容相符。


要解决此问题,请从处理函数中删除令人困惑的

context
参数(您不太可能需要
CallableProxyResponse
对象)并在整个代码中删除所有对
context
的使用。

async (request) => {
  // ...
}

然后修复您的

verifyIdToken
线以使用:

// Verify the token
const decodedToken = await admin.auth().verifyIdToken(request.auth.token);  // context --> request
console.log("[processPdfDocument] Token verified:", decodedToken.uid);

您还应该考虑将导致此错误的 catch 块移至更靠近

verifyIdToken
调用的位置,而不是将其一直放在文件底部。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.