0

I am doing a batch write like so:

const batch = this.afs.firestore.batch();

const studentID = this.afs.createId();
const classID = this.afs.createId();

const studentRef = this.afs.doc(`students/${studentID}`).ref;
batch.set(studentRef, {
  name: 'tom',
  classes: firebase.firestore.FieldValue.arrayUnion(classID)
});

const classRef = this.afs.doc(`classes/${classID}`).ref;
batch.set(classRef, {
  name: 'calculus',
  students: firebase.firestore.FieldValue.arrayUnion(studentID)
});

await batch.commit();

And I want to ensure that either:

  1. students/studentID/classes array can only be changed if classes/classID/students array also changed with the correct IDs
  2. this is not enforced if that field is not updated
  3. one field cannot be deleted unless the other field is deleted

So, I am thinking this:

match /students/{studentID} {
  allow read;
  allow write: if noChange('classes') || 
    matchWrite(studentID, 'classes', 'students');
}
match /classes/{classID} {
  allow read;
  allow write: if noChange('students') ||
    matchWrite(classID, 'students', 'classes');
}

function noChange(field) {
  return !(field in request.writeFields);
}
function getVal(field) {
  return resource.data[field].removeAll(request.resource.data[field])[0];
}
function matchWrite(VAL1, VAL2, VAL3) {
  return VAL1 in
  getAfter(/databases/$(database)/documents/$(VAL2)/$(getVal(VAL2))).data[VAL3];
}  

delete - delete for references...

  // allow delete: if noChange('classes') || matchDelete('classes', 'students');
  // allow delete: if noChange('students') || matchDelete('students', 'classes');
// function matchDelete(VAL1, VAL2) {
  // students/STUDENTID/classes array CLASSID (being removed)
  // must eq classes/CLASSID/students array STUDENTID (being removed)
  // and the other way around
// }

I am getting boggled down on the last part. Since the batches are atomic, I would think I can use getAfter() somehow.

How would I ensure atomic rules or nothing?

J

Jonathan
  • 3,893
  • 5
  • 46
  • 77
  • Yup, that would require a `getAfter` indeed, similar to what I do in the last part of my answer here: https://stackoverflow.com/questions/56487578/how-do-i-implement-a-write-rate-limit-in-cloud-firestore-security-rules – Frank van Puffelen Aug 22 '21 at 22:34
  • I am still confused on where to start on this with `getAFter` - there are 2 different writes at the same time etc... not sure at all how to translate my pseudocode – Jonathan Aug 24 '21 at 23:56
  • Since you do a batched write, the results of both writes are present in the `getAfter` and `request.resource` data in your rules. I'm really not sure where you are stuck, so can you maybe show how you tried to access the "after" data already? – Frank van Puffelen Aug 25 '21 at 03:37
  • @FrankvanPuffelen - I updated the code above, I think dealing with the arrays is the problem. Any ideas here? – Jonathan Aug 28 '21 at 01:38

0 Answers0