我没有任何软件经验,我在 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."
);
}
}
);
您使用的工具混淆了 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
调用的位置,而不是将其一直放在文件底部。