Update: As of 2022-09-28, Firebase introduced cross-service Security Rules, so the answer below is outdated. See @Dharmaraj's answer below for an example.
You can retroactively validate and delete the file after it's been uploaded using a cloud function trigger.
Warning: this technique is not bullet proof, as the invalid file will be stored in Cloud Storage temporarily or potentially forever if the Cloud Function trigger fails. My preference is to prevent the upload in the first place, but if the logic to determine permission resides in Firestore and can't be stuffed in custom claims, then this is currently the only way if you're uploading files using Firebase's Client SDKs. If you're building a mission critical system, you should upload the file to a Cloud Function and let the Cloud Function store the file in Cloud Storage instead.
When uploading a file, add some metadata indicating who's doing the upload:
const storageRef = ref(
storage,
`files/${fileName}`,
);
const uploadTask = uploadBytesResumable(storageRef, file, {
customMetadata: {
uploaderId: userId,
},
});
Set storage rule to ensure that the user identity metadata can be trusted:
match /files/{fileName} {
allow create: if request.auth != null &&
request.resource.metadata.uploaderId == request.auth.uid
}
Create a cloud function trigger that retroactively validates and deletes:
export const onFinalize = functions
.storage.object()
.onFinalize(async object => {
// We can trust object.metadata.uploaderId, so check Firestore if user is able to upload file
if (!(await canUploadFile(object.metadata.uploaderId, object.name))) {
await storage.bucket(object.bucket).file(object.name).delete();
throw new Error(
`Permission error: ${object.metadata.uploaderId} not allowed to upload ${object.name}`,
);
}
// Continue
});