Firebase Prevent deletion of parent node if child node exist in firebase - firebase-realtime-database

Rules to prevent deletion of parent node if Childs exist. I have a structure of firebase as below
Table :
{
$albumId : //Prevent deletion of this node if its child exist.
{
$childID : date;
}
}

To prevent a node from being deleted if it has data in it:
".write": "data.exists() && newData.exists()"

Related

Firebase realtime DB - Create rule that limits size of child object

I've got a Realtime Database where users can write data to a certain key. However, I want to avoid a potentially malicious user from inserting a huge amount of data, without creating any new middleware.
Here are my rules right now:
{
"rules": {
/*
* Only let people read a specific name
* Only let people write "$user": <public info json> or "$user": null
*/
"$user": {
".read": "true",
".write": "auth.uid === $user"
".validate": "newData.val().length <= 123456 || newData.val() == null"
}
}
}
But this incorrectly rejects a write if the data at databaseurl/$user is a child object rather than a string, even if the JSON string representation of the child is below the length size.
How can I restrict the length of a child object, if the child is more than a simple string?
Thanks!

How to update firebase-query after user login

Using polymer and firebase I have some data that is only accessible by logged users. But, when the user login in the application the data isn't automatically updated.
An easily workaround is force the page reload with location.reload(). But, I'm trying to discover how to update these values without having to reload the entire application. Any ideas?
Expected outcome:
When user logins all fibase-document and firebase-query updates the synced data.
Actual outcome:
These elements doesn't update and the user has to reload the page
Example:
Element: app-auth.html
<firebase-app
id="firebaseApp"
auth-domain="app.firebaseapp.com"
database-url="https://app.firebaseio.com"
storage-bucket="app.appspot.com"
api-key="api"
messaging-sender-id="id"
></firebase-app>
<firebase-auth id="auth"
signed-in="{{isUserLogged}}"
user="{{user}}"
on-error="_onAuthError"
log
></firebase-auth>
login(credentials) {
if (credentials.provider === 'email') {
this.$.auth.signInWithEmailAndPassword(credentials.params.email, credentials.params.password)
.catch(e => {
if (e.code === 'auth/user-not-found') {
this.$.auth.createUserWithEmailAndPassword(credentials.params.email, credentials.params.password)
.then(this._successfulLogin());
}
})
.then(this._successfulLogin());
this.password = null;
} else {
this.$.auth.signInWithPopup(credentials.provider);
}
}
app-contents.html:
<firebase-query
data="{{contents}}"
path="/contents"
></firebase-query>
<div class="card">
<h1>Contents:</h1>
<template is="dom-repeat" items="[[contents]]" as="content">
<app-content-preview="[[content]]"></app-content-preview>
</template>
</div>
</template>
The contents element shows questionnaires the user has to answer in order to complete the goal of the app. But, the questionnaires can only be accessed by logged users. I would like to, when user logins in the app-auth, process his identity and update the firebase-query that shows the questionnaires he has to answer
Actually should read the data at query (due to your firebase datababase rule. (if you allow read for all, but write on logged in ) something like in database rules;
"rules": {
".read": true,
".write": "auth != null"
}
But in case let says read need also auth !=null than, lets change your code like:
<firebase-query
data="{{contents}}"
path="{{pathContents}}"
></firebase-query>
and define an observer like
static get observers() {
return ['_isUserLogged(user)']
_isUserLogged(user) {
if (user) {
this.set('pathContents', 'contents');
}
}
this observer will check user is exist or not. And if exist than the path will be defined as contents than query element will fire again automatically.
Hope this will work :)
Another js options is;
_isUserLogged(user) {
if (user) {
var db = firebase.database();
db.ref('contents').on('value', snapshot=>{
if (snapshot.exists()) { //To check that you have a data
this.set('contents', snapshot.val() );
}
})
}
}
Please not: if you read database with above code, than you do not need firebase-query element. So, delete it.

Controlling data-update on Firebase

I am using Firebase for a small iOS project and I wonder if the following is possible.
Under a given node "myNode_123" I store data records; each record having the shape below:
[fieldStr: "ABC_xyz", fieldNum: 678]
A record is in fact a subnode.
Now my question is about updating the data, for already existing records.
Someone should be allowed to update a data record only if the new value for fieldNum is higher than the one already there.
And in any case the value for fieldStr should stay as it is.
I have already written some rules to make sure fieldNum is fullfilling my request.
But I still have the issue of fieldStr having the risk of being changed.
Here are my rules:
{
"rules": {
".read": true,
//".write": true
"myNode_123": {
"$Record": {
// Ensure that we are either creating a new record not yet existing,
// or that we are only updating for a higher fieldNum.
".write": "(!root.child('myNode_123/'+$Record).exists()) ||
(root.child('myNode_123/'+$Record).exists() && (newData.child('fieldNum').val() > data.child('fieldNum').val()))"
}
}
}
}
The control of fieldNum is working as I wish. But it is still possible to change fieldStr, which I do not want.
Any advice from a Firebase expert?
Add this under $Record.
{
"rules": {
".read": true,
//".write": true
"myNode_123": {
"$Record": {
// Ensure that we are either creating a new record not yet existing,
// or that we are only updating for a higher fieldNum.
".write": "!data.exists() || newData.child('fieldNum').val() > data.child('fieldNum').val()",
"fieldStr": {
".validate": "!data.exists() || data.val() == newData.val()"
}
}
}
}
}
The !data.exists() will make sure only new data can be written to this location. And data.val() == newData.val() will add an exception to allow writes when the new data is the same as the old data, just in case you want to write the entire object to the Database and include the fieldStr.

swift showing posts from all firebase users

I need to create a UISearchController using firebase as the backend. I currently have two users in firebase. One user has made one post and the other has made four posts. I will like to be able to search for the title of all of the books in my database (which is five). However, as of now, I can only search for the books the current signed in user uploaded. Below is a screenshot of what my database looks like and what my code currently looks like.
databaseRef.child("posts").child(userID!).observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
let snapshot = snapshot.value as? NSDictionary
snapshot?.setValue(key, forKey: "uid")
if(key == self.loggedUser?.uid)
{
print("same as logged in user")
}
else
{
self.usersArray.append(snapshot)
self.followUsersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
}
}) { (error) in
print(error.localizedDescription)
}
}
You will need a different parent node called Books :-
Right now your JSON is something like this :-
posts : {
userID1 : {
BOOK_11_ID : {.....}
};
userID2 : {
BOOK_21_ID : {.....},
BOOK_22_ID : {.....},
BOOK_23_ID : {.....},
BOOK_24_ID : {.....},
}
}
You gotta modify your database JSON structure to :-
posts : {
Users_books :{
userID1 : {
BOOK_11 : true // Value 'true' means that this user
// has subscribed or bought this book or whatever
};
userID2 : {
BOOK_21 : true,
BOOK_22 : true,
BOOK_23 : true,
BOOK_24 : true,
}
},
Books:{
BOOK_11_ID : {/Book details/};
BOOK_21_ID : {/Book details/};
BOOK_22_ID : {/Book details/};
BOOK_23_ID : {/Book details/};
BOOK_24_ID : {/Book details/};
}
}
Modify your security rules for section 'Book' to make is visible to all the authenticated users.
{
"rules" :{
"Users_books" : {
".write" : "auth != null && auth.uid === $uid", // Will allow only the user to make any change within its node and no-one else
".read": "auth != null && auth.uid === $uid"
},
"Books" : {
".write" : "auth != null", // Will allow all the users that are authenticated to your app to access every books detail.
".read" : "auth != null"
}
}
}
Now what you gotta do is :-
1) If its the user that is creating the book ; Then store the book details in the parent node 'Books' under the books uid and set the value of book_ID to true in the users node.
2) If the database is all backend i.e you input it; Whenever the user subscribe's or buys a books just fetch the uid of that book and set it to true in the section of that user.
PS : Always fan out your database ; its much easier to search from it .Although i am pretty sure you are gonna wanna store some personal details of the user in the near future so for that just make another node . Name it 'Users', security rules will be same as 'Users_books' and under 'Users' parent node append details of every which user with their uid as their key.

FireBase - maintain/guarantee data consistency

I'm trying to understand what is the right approach for this following scenario :
Multiplayer game,each game structured only with two players. Each game/match will be completely randomized
Lets assume 5 users "logs" the same time into my app, each one of them "searching" for a match. Each user hold a property named opponent which equal the the opponent uniqueID(initial value equal "". so far so good.
assuming user 1 matched with user 3. user 1 will update his own oppoent value to user 3 uniqueID and will do the same to user 3
Problem
1) What if at the same moment, user 2 tried to to the same to user 3?
2) What if at the same moment, user 3 tried to do so to user 4?
Main Point
Is it possible to "lock" a user values? or freeze them once they changed? Am i going in the wrong approach?
I was thinking using Security Rules and Validation in order to create consistency but i just may picked the wrong tech(FireBase). Any thoughts?
EDIT
Security rules i have tried, which still for some reason enable a third device change "already changed opponent" value.
{
"rules": {
".read": true,
".write": true,
"Users" :
{
"$uid" : {
"opponent" :
{
".write" : "data.val() == 'empty' || data.val() == null",
".validate": "data.val() == null || data.val() == 'empty' || newData.parent().parent().child(newData.val())
.child('opponent').val() == $uid"
}
,".indexOn": "state"
}
}
}
}
You can validate many things with Firebase security rules.
For example, you can say that an opponent can only be written if there currently is no opponent for the user:
"users": {
"$uid": {
"opponent: {
".write": "!data.exists()"
}
}
}
With this and the following operations:
ref.child('users').child(auth.uid).child('opponent').set('uid:1234');
ref.child('users').child(auth.uid).child('opponent').set('uid:2345');
The second set() operation will fail, because the opponent property already has a value at that point.
You can expand that to also validate that the opponents must refer to each other:
"users": {
"$uid": {
"opponent: {
".write": "!data.exists()"
".validate": "newData.parent().parent().child(newData.val())
.child('opponent').val() == $uid"
}
}
}
From the opponent that is being written, we go up two levels back to users: newData.parent().parent().
Then we go down into the opponent's node: child(newData.val()).
And we then validate that the opponent's opponent property matches our uid: child('opponent').val() == $uid.
Now both of the write operations from above will fail, because they're only setting the opponent one at a time. To fix this, you'll need to perform a so-called multi-location update:
var updates = {};
updates['users/'+auth.uid+'/opponent'] = 'uid:1234';
updates['users/uid:1234/opponent'] = auth.uid;
ref.update(updates);
We're now sending a single update() command to the Firebase server that writes the uids to both opponents. This will satisfy the security rule.
A few notes:
these are just some examples to get you started. While they should work, you'll need to write your own rules that meet your security needs.
these rules just handle writing of opponents. You'll probably also want to testing what happens when the game is over and you need to clear the opponents.
You might also look at the transaction operation.
Firebase transactions make sure that the current set of data you are acting on is really what is in the database, guaranteeing that you are updating data that is in the right condition. The docs indicate that this is the recommended way to avoid race conditions such as you describe.
Something like this (in IOS, and warning - not tested):
NSString* user1Key = #"-JRHTHaIs-jNPLXOQivY";
NSString* user2Key = #"-NFHUaIs-kNPLJDHuvY";
Firebase *user1Ref = [[Firebase alloc] initWithUrl: #"https://docs-examples.firebaseio.com.users/-JRHTHaIs-jNPLXOQivY/opponent"];
Firebase *user2Ref = [[Firebase alloc] initWithUrl: #"https://docs-examples.firebaseio.com.users/-NFHUaIs-kNPLJDHuvY/opponent"];
//See if the proposed opponent does not yet have a match
[user2Ref runTransactionBlock:^FTransactionResult *(FMutableData *opponent) {
if (opponent.value == [NSNull null]) {
//They have no match - update with our key and signal success
[opponent setValue:user1Key];
return [FTransactionResult successWithValue: opponent];
} else {
return [FTransactionResult abort]; //They already have an opponent - fail
//Notify the user that the match didn't happen
}
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
if (!error && committed) {
//The transaction above was committed with no error
//Update our record with the other player - we're matched!
[user1ref setValue:user2Key];
//Do whatever notification you want
} else {
//Notify that the matchup failed
}
}];

Resources