上下文: 我有一个在 Next.JS 中构建的 Web 应用程序,使用 Firebase Storage 进行文件存储。当我删除 Firebase 规则/将 Firebase 规则设置为公开时,我可以成功上传文件。我需要实施更严格的规则来保护数据库。
我没有使用 Firebase 身份验证,而是使用 NextAuth。该用例需要根据 Firestore 中的用户和组织检查元数据 (firestore.get() / firestore.exists())。
我尝试了一些基线规则,并且以下规则有效,我能够成功上传文件:
// Allow anyone to read/write - PUBLIC
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
// Allow anyone to read/write if there is a request
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request != null;
}
}
}
问题:尝试实施任何其他规则时,上传失败并显示:
状态 403 - Firebase 存储:用户无权访问
问题似乎源于 request.resource 始终未定义(当我控制台登录我的代码时可以看到这一点)。这非常令人困惑,因为当规则被删除/公开时,文件上传成功,这告诉我 request.resource 不是未定义的。进一步增加我的困惑,当我上传具有宽松规则的文件时,我可以在文件元数据中看到这些字段肯定存在:
在 Firebase 规则游乐场中测试以下规则时,它按预期工作,但是当发布规则并触发端点时,它失败并出现上述错误:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.resource.metadata != null;
}
}
}
后端代码:
imports...
export const config = {
api: {
bodyParser: false, // Disable Next.js's default body parser
},
};
export interface ParsedForm {
fields: formidable.Fields;
files: formidable.Files;
}
// Helper function to initialize formidable and parse form data
const parseForm = (req: IncomingMessage): Promise<ParsedForm> =>
new Promise((resolve, reject) => {
const form = formidable({ multiples: true, keepExtensions: true });
form.parse(req, (err, fields, files) => {
if (err) reject(err);
resolve({ fields, files });
});
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession(req, res, authOptions(req, res));
if (!session || !session.user) {
res.status(401).json({ success: false, message: 'Unauthorized' });
return;
}
const user = session.user as User;
const { method } = req;
if (method !== 'POST') {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${method} Not Allowed`);
return;
}
try {
const { fields, files } = await parseForm(req);
// Ensure `file` is an array and access the first element
const fileArray = files.file;
if (!fileArray || !Array.isArray(fileArray) || fileArray.length === 0) {
res.status(400).json({ success: false, message: 'No file provided' });
return;
}
const metaData = {
contentType: fileArray[0].mimetype || 'application/octet-stream',
customMetadata: {
userId: user.id,
organisationId: user.organisationID,
},
};
const file = fileArray[0];
const currentDateTimeSinceEpoch = secondsSinceEpoch();
const storageRef = ref(storage, `uploads/${user.organisationID}/${currentDateTimeSinceEpoch}_${file.originalFilename}`);
// Read the file as a buffer
const fileBuffer = await fs.readFile(file.filepath);
// Upload the file to Firebase Storage
const uploadResult = await uploadBytesResumable(storageRef, fileBuffer, metaData);
console.log("🚀 ~ handler ~ uploadResult:", uploadResult)
// Get the download URL
const downloadURL = await getDownloadURL(storageRef);
// Optionally, remove the file from the local filesystem after uploading
await fs.unlink(file.filepath);
res.status(200).json({ success: true, url: downloadURL });
} catch (error) {
console.error('Failed to upload file:', error);
res.status(500).json({ success: false, message: 'Failed to upload file' });
}
}
已解决
我面临的问题/错误归结为多个原因,但总的来说,我混合使用了 v9 和 v10 Firebase,导致了一些奇怪的行为。
使用 v10 Firebase 时,如果您以 v10 方式初始化应用程序,请确保使用正确的导入和函数。
下面解决了我的问题,现在我的请求结构符合 Firebase 的预期,我可以正确使用规则了:
import { getStorage, ref, uploadBytesResumable } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, `uploads/${user.organisationID}/${file.originalFilename}`);
以下是我之前的做法并导致错误:
import { storage } from '@/config/firebase';
import { ref, uploadBytesResumable } from "firebase/storage";
const storageRef = ref(storage, `uploads/${user.organisationID}/${currentDateTimeSinceEpoch}_${file.originalFilename}`);
// Config/init file
import { getApp, getApps, initializeApp } from 'firebase/app';
import { getAnalytics } from 'firebase/analytics';
export const firebaseConfig = {
...
};
// Initialize Firebase only if it hasn't been initialized yet
const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
const analytics = typeof window !== 'undefined' ? getAnalytics(app) : null;
const storage = admin.storage();
export { app, analytics, storage };