Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
64
rated 0 times [  71] [ 7]  / answers: 1 / hits: 6103  / 10 Years ago, tue, march 25, 2014, 12:00:00

Let's say my Firebase collection looks like:



{
max:5
things:{}
}


How would I use the value of max in my security rules to limit the number of things?



{
rules: {
things: {
.validate: newData.val().length <= max
}
}
}

More From » firebase

 Answers
2

Using existing properties is done using root or parent and is pretty straightforward.



{
rules: {
things: {
// assuming value is being stored as an integer
.validate: newData.val() <= root.child('max')
}
}
}


However, determining the number of records and enforcing this is a bit more complex than simply writing a security rule:




  • since there is no .length on an object, we need to store how many records exist

  • we need to update that number in a secure/real-time way

  • we need to know the number of the record we are adding relative to that counter



A Naive Approach



One poor-man's approach, assuming the limit is something small (e.g. 5 records), would be to simply enumerate them in the security rules:



{
rules: {
things: {
.write: newData.hasChildren(), // is an object
thing1: { .validate: true },
thing2: { .validate: true },
thing3: { .validate: true },
thing4: { .validate: true },
thing5: { .validate: true },
$other: { .validate: false
}
}
}


A Real Example



A data structure like this works:



/max/<number>
/things_counter/<number>
/things/$record_id/{...data...}


Thus, each time a record is added, the counter must be incremented.



var fb = new Firebase(URL);
fb.child('thing_counter').transaction(function(curr) {
// security rules will fail this if it exceeds max
// we could also compare to max here and return undefined to cancel the trxn
return (curr||0)+1;
}, function(err, success, snap) {
// if the counter updates successfully, then write the record
if( err ) { throw err; }
else if( success ) {
var ref = fb.child('things').push({hello: 'world'}, function(err) {
if( err ) { throw err; }
console.log('created '+ref.name());
});
}
});


And each time a record is removed, the counter must be decremented.



var recordId = 'thing123';
var fb = new Firebase(URL);
fb.child('thing_counter').transaction(function(curr) {
if( curr === 0 ) { return undefined; } // cancel if no records exist
return (curr||0)-1;
}, function(err, success, snap) {
// if the counter updates successfully, then write the record
if( err ) { throw err; }
else if( success ) {
var ref = fb.child('things/'+recordId).remove(function(err) {
if( err ) { throw err; }
console.log('removed '+recordId);
});
}
});


Now on to the security rules:



{
rules: {
max: { .write: false },

thing_counter: {
.write: newData.exists(), // no deletes
.validate: newData.isNumber() && newData.val() >= 0 && newData.val() <= root.child('max').val()
},

things: {
.write: root.child('thing_counter').val() < root.child('max').val()
}
}
}


Note that this doesn't force a user to write to thing_counter before updating a record, so while suitable for limiting the number of records, it's not suitable for enforcing game rules or preventing cheats.



Other Resources and Thoughts



If you want game level security, check out this fiddle, which details how to create records with incremental ids, including security rules needed to enforce a counter. You could combine that with the rules above to enforce a max on the incremental ids and ensure the counter is updated before the record is written.



Also, make sure you're not over-thinking this and there is a legitimate use case for limiting the number of records, rather than just to satisfy a healthy dose of worry. This is a lot of complexity to simply enforce a poor man's quota on your data structures.


[#46556] Tuesday, March 25, 2014, 10 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
billosvaldor

Total Points: 601
Total Questions: 113
Total Answers: 113

Location: Iceland
Member since Sat, Sep 17, 2022
2 Years ago
;