javascriptfirebasegoogle-cloud-firestorefirebase-security

Missing or insufficient permissions when testing existence of doc before creating with js modular web sdk


Using the Web modular API (firebase js sdk 11.0.1 as well as previous version), when I call setDoc after testing existence, I get:

Missing or insufficient permissions

export async function setYouTubePublication(publication: YouTubePublication) : Promise<void> {
    console.log("setYouTubePublication", publication);  
    const db = getFirestore();
    const ref = doc(db, "youtube_publications", publication.youtubeID);
    const snap = await getDoc(ref);
    if (snap.exists()) {
        throw new Error(`YouTube publication with ID ${publication.youtubeID} already exists`);
    }
    return setDoc( ref, publication );
}
rules_version = '2';

service cloud.firestore {
    match /databases/{database}/documents {
        match /youtube_publications/{document} {
            allow read: if request.auth != null && resource.data.owner == request.auth.token.dcid;
            allow create: if request.auth != null;
        }
    }
}

If I remove the existence test, setDoc works as it should:

export async function setYouTubePublication(publication: YouTubePublication) : Promise<void> {
    console.log("setYouTubePublication", publication);  
    const db = getFirestore();
    const ref = doc(db, "youtube_publications", publication.youtubeID);
    return setDoc( ref, publication );
}

Anyone have any idea what I'm doing wrong or if there is a workaround? Seems like there is a bug.


Solution

  • The problem is in your read rule:

    allow read: if request.auth != null && resource.data.owner == request.auth.token.dcid;
    

    If the document doesn't exist then the rules engine cannot determine resource.data.owner, so it raises an error. And any error while evaluating the rules means that the operation is denied.

    If you want the operation to work, explicitly check whether the resource exists:

    allow read: if request.auth != null &&
      (!exists(/databases/$(database)/documents/youtube_publications/$(document))
       || resource.data.owner == request.auth.token.dcid);