Google Firebase 存储规则阻止从 Next.JS 端点发送文件上传

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

上下文: 我有一个在 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 不是未定义的。进一步增加我的困惑,当我上传具有宽松规则的文件时,我可以在文件元数据中看到这些字段肯定存在:

enter image description here

在 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' });
  }
}
javascript firebase google-cloud-firestore next.js file-upload
1个回答
0
投票

已解决

我面临的问题/错误归结为多个原因,但总的来说,我混合使用了 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 };
© www.soinside.com 2019 - 2024. All rights reserved.