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.
Related
i want to write firebase realtime database rule where certain user can only write cetain key
ex: user with UID underlined in red can write value of key franchise_active only
user with UID underlined in green can write value of key vendor_active only
both users can read
Sounds possible. Something like this should work:
{
"rules": {
"application_status": {
"$uid1": {
"$uid2": {
"franchise_active": {
".write": "auth.uid === $uid1"
},
"vendor_active": {
".write": "auth.uid === $uid2"
}
}
}
}
}
}
I'm struggling with understanding how I can allow users to create new records in the list, but only allow creators to update their own posts.
E.g. the following structure:
post {
post1: {
author: "user1"
text: "Some text"
}
post2: {
author: "user2"
text: "Some text 2"
}
}
Here, I want both users to be able to create new posts. But also protect, say, post2 from being edited by user1. Thus, only user1 can edit post1 and only user2 can edit post2.
You'd want to do something like this:
{"rules": {
"post": {
"$id": {
".write": "auth !== null && (!data.exists() || data.child('author').val() === auth.uid)"
}
}
}}
Here you're only allowing write if the user is logged in and a) the node attempting to be written is empty or b) the node attempting to be written was authored by the current user.
So I have my Database structured like this, the owner gets set when the group is created and the owner the should have the permission to add other Users to allowed so they can access and edit the data too.
-Groups
|-Groupname
|- Owner: string
|- Allowed: List<string>
|- Data: all the data
So my attempt were these rules but they dont work when I use the playground feature with a saved uid under owner or allowed:
"Groups" : {
"$group": {
".read": "auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').val() === auth.uid)",
".write": "auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').val() === auth.uid)"
}
}
And would a User still be able to create a new group when these rules would work?
Pictures of the Database and Errors:
First, in the Realtime Database, avoid using arrays and use a map instead.
Change this:
"Allowed": {
"0": "8ZiQGBPFkiZOLgLJBgDeLw9ie9D3",
"1": "KEuhrxnAWXS0dnotjhjFAYUOcm42",
"2": "48yULftKSxgyS84ZJC4hs4ug4Ei2"
}
to this:
"Allowed": {
"8ZiQGBPFkiZOLgLJBgDeLw9ie9D3": true,
"KEuhrxnAWXS0dnotjhjFAYUOcm42": true,
"48yULftKSxgyS84ZJC4hs4ug4Ei2": true
}
Read that linked blog post for more info, but in short, it makes adding/removing users really simple:
const groupRef = firebase.database.ref(`Groups/${groupId}`);
// add a user
groupRef.child("E04HLbIjGDRUQxsRReHSKifaXIr2").set(true);
// remove a user
groupRef.child("KEuhrxnAWXS0dnotjhjFAYUOcm42").remove();
You can also change true to whatever you want. Here are some examples:
false = participant, true = moderator
false = read-only, true = can edit
Role names: "member", "admin", "moderator", etc.
Privilege levels: 0 (member), 500 (moderator), 1000 (owner), etc. (make sure to space these out, you don't want to have to add in a level between 0 and 1 and have to edit your entire database).
The most important point though, is that Realtime Database security rules don't know about arrays. data.val() won't return an array, it will just return a sentinel value that says "non-null object is here!". This means a map is necessary for security rules.
This reference document covers the structure and variables you can use in your Realtime Database Security Rules.
With your proposed rules, you attempt to allow any user in the group to be able to write to the group's data - but you don't manage what they can and can't write to. Any malicious member of a group could add/delete anyone else, make themselves the owner, or even delete the group entirely.
{
"rules": {
"Groups" : {
"$group": {
// If this group doesn't exist, allow the read.
// If the group does exist, only the owner & it's members
// can read this group's entire data tree.
".read": "!data.exists() || (auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').child(auth.uid).val() === true))",
"Owner": {
// Only the current owner can write data to this key if it exists.
// If the owner is not yet set, they can only claim it for themselves.
".write": "auth != null && (data.val() === auth.uid || (!data.exists() && newData.val() === auth.uid))",
// Force this value to be a string
".validate": "newData.isString()"
},
"Allowed": {
// Only the owner can edit the entire member list
// For a new group, the owner is also granted write access
// for it's creation
".write": "auth != null && (data.parent().child('Owner').val() === auth.uid || (!data.exists() && newData.parent().child('Owner').val() === auth.uid))",
"$member": {
// Allows the user to remove themselves from the group
".write": "auth != null && auth.uid === $member && !newData.exists()",
// Force this value to be a boolean
".validate": "newData.isBoolean()"
}
},
"Data": {
// The owner and members can edit anything under "Data"
// Currently this includes deleting everything under it!
// For a new group, the owner is also granted write access
// for it's creation
// TODO: tighten structure of "Data" like above
".write": "auth != null && (data.parent().child('Owner').val() === auth.uid || data.parent().child('Allowed').child(auth.uid).val() === true || (!data.exists() && newData.parent().child('Owner').val() === auth.uid))"
}
}
}
}
}
I have a Sign Up Flow using Firebase. When I check if an email already exists in the database, like so:
refUsers.queryOrdered(byChild: "email").queryEqual(toValue: emailText).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("Unique email")
// Move to Password View.
let passwordViewController = self.storyboard?.instantiateViewController(withIdentifier: "PasswordViewController") as! PasswordViewController
self.navigationController?.present(passwordViewController, animated: true, completion: nil)
// Pass the emailText to the last View of the flow.
self.singleton.sharedInstance.emailText = emailText!
}
else {
print("Duplicate email")
}
})
The problem is, I don't have the permission to view /users in the database cause my rule is:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
I know I can find if an email is a duplicate using Auth.auth().createUser but it's not just email that I'm checking in the sign up flow. I use the same method for unique username, as well. How can I achieve this?
As you can see this is not the best way to do it. You should not manually check if email already exists - Firebase can do that for you when user signs up and why would you not want to use that?
What you need is a different approach. I can think of two ways right now:
First:
You can add a new rule to Firebase, eg:
{
"rules": {
"usernames": {
".read": true,
".write": "auth != null"
},
"emails": {
".read": true,
".write": "auth != null"
}
}
}
What you do here is create a new node named usernames which every user can access and read.
Here you should hold a copy of all usernames that registered users have and when registering check if users username is already inside this node.
Second way:
You could modify your signup flow a bit and let users register without a username. After account is created you let them set a username. With a nice flow it would all look as the same registration form.
UPDATE
With rules above users should be able to read from emails and usernames without being registered. This way you can fetch data and compare if email or username is already in use.
Just make sure that when user registers you insert his email and username into those two nodes.
Though #ZassX answered helped me, I've learned what a good approach for this would be, for those who are confused like me.
The best approach is to keep users data safe in /users with "auth != null" rule. Only show the user's meta data to everyone that includes just the email and password of each user. For example:
Database
{
“metaData”: {
uid: {
“email”: …,
“password”: …
}
},
“users”: {
uid: {
“email”: …,
“password”: …
// other information
}
}
}
Security
{
"rules": {
“metaData”: {
“.read”: true,
“.write”: “auth !== null”
},
“users”: {
“.read”: “auth !== null”,
“.write”: “auth !== null”
}
}
}
The information in metaData can now be matched without a user being authenticated first. Of course, this can get more complex as you add more security but for easy understanding, this is it.
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
}
}];