Firebase Swift - Slow response - Rules index [g] - swift

My app is sending and retrieving large amount of data from Firebase every second, performing multiple functions.
I'm trying to understand the Firebase rules. Currently I have the default rules set up.
{
"rules": {
".read": "auth != null",
".write": "auth != null",
".indexOn": ["g"]
}
}
However, in my debugger it is saying...
[FirebaseDatabase] Using an unspecified index. Consider adding ".indexOn": "g" at /gameUserCoordinates to your security rules for better performance
Should I be creating something extra in my FireBase rules for gameUserCoordinates?
It is set up FirebaseRoot -> gameUserCoordinates -> UID -> GeoFireCoordinates
My user has to sign in and be authenticated to use the app.
Thanks, any help would be much appreciated.
UPDATE
var dbRef: FIRDatabaseReference {return FIRDatabase.database().reference()}
var gameUserCoordinatesRef: FIRDatabaseReference {return dbRef.child("gameUserCoordinates")}
func geoFireUploadUserCoordinates(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
let userKey = FIRAuth.auth()?.currentUser?.uid
let geoFireUser = GeoFire(firebaseRef: gameUserCoordinatesRef)
geoFireUser?.setLocation(CLLocation(latitude: latitude, longitude: longitude), forKey: userKey)

Related

Firebase Realtime Database rules for specific node of structure

Before creating a new user I want to check if creating username property already exists in Firebase Database.
Checking function is:
let databaseRef = Database.database().reference()
databaseRef.child("users").queryOrdered(byChild: "username").queryEqual(toValue: loginRegisterTextField.text).observeSingleEvent(of: .value, with: { (snapshot: DataSnapshot) in
if snapshot.exists() {
print("Login exists")
} else {
print("Login does not exist")
}
})
JSON is:
Rules are for node users:
{
"rules": {
"users" : {
".read": "auth != null",
"$uid" : {
".write": "auth != null && auth.uid == $uid",
}
},
Is it possible to write a rules to check existing of username without a new uid?
There is no way to check for a specific value across a JSON branch in security rules. This has been covered quite a few times before, so I recommend checking some of these search results.
But you can make your query on /users more secure, by only allowing that specific query, and not allowing people to read all of /users. To secure the query you could some something like:
{
"rules": {
"users" : {
".read": "auth != null &&
query.orderByChild == 'username' &&
query.equalTo !== null",
...
This is the first time I've used query.equalTo !== null, so there may be some small mistakes in that part, but the flow should be clear.

Firebase rules failed: permission_denied

In my app I have vc1 that pushes on vc2. Inside vc2 I pull some data from Firebase.
The problem is when I set my rules to the below values, when vc2 gets pushed on I keep getting failed: permission_denied
{
"rules": {
"sneakers": {
".read": "auth != null || auth.uid === null",
".write": "auth.uid != null"
},
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
If I go back to vc1, change the values to the below, then I get no problems and I get access
{
"rules": {
".read": "auth.uid != null",
".write": "auth.uid != null"
}
}
But here's the thing, once I set them back to the first values, go back to vc1 then push on vc2 I can then still get access to the database. If I delete the app and relaunch it the process repeats itself.
I want to use the first values because I want to keep the data at the user's node safe.
Why won't do I keep getting permission_denied with my first values, make a change to the rules, then after I change them back I no longer get permission denied? Where am I going wrong in my rules
vc2:
let root = Database.database().reference()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
root?.observeSingleEvent(of: .value, with: {
(snapshot) in
// sneakers node might not exist
if snapshot.hasChild("sneakers/nike)"){
....
}
)}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
root?.removeAllObservers()
}
The database layout:
root
|
#-sneakers // this node might not exist
| |
| nike
|
#-users
| |
| uid
|
#-jackets...
You're trying to read from the root of your database (root?.observeSingleEvent(...), but you only grant access to individual users and sneakers nodes. Since you're trying to read more than you have access to, Firebase rejects your observer.
It's hard to say why sometimes you don't get this error, but most likely it's a caching issue. Either your new rules haven't been applied yet (which should be rare), or you're reading from the cache of your iOS device (where the data was stored when you still did have access).
Either way, you should make sure you only read data you have access to. So:
root?.child("sneakers/nike")?.observeSingleEvent(of: .value, with: {
(snapshot) in
// sneakers node might not exist
if snapshot.exists() {
....
}
)}

Unable to Compare database timestamp to firebase 'now' security rule

I am having an issue comparing the time interval that I have saved in my firebase db to the 'now' firebase security rule. The rule I am writing is intended to prevent users from reading if a post is after a certain time.
This is how I am saving the timestamp in my database:
"time": NSDate().timeIntervalSince1970
This is the security rule I am writing that should prevent a read if after a certain time:
"follower-feed": {
// ".read": "auth !== null",
".write": "auth !== null",
"$userID": {
"$postID": {
".read": "auth !== null && data.child('time').val() >= ((now / 1000) - 86400)",}}},
And this is the database schema I am using to store posts:
-follower-feed: {
user_uid_1: {
post_uid_1: {
"time": 1515435031.16646
post_uid_2: {
"time": 1515435091.22323
I would like to note that I am already accounting for the fact that 'now' is in milliseconds and dividing it by 1,000 should set my two numbers to the same time value of seconds. I have stifled all about the firebase documentation but nothing is helping me solve this. When I run the simulator test to determine if the requested read will pass, it says that it will pass. However, in my app no data is being read.
This is the code that attempts to read the data from firebase:
var followingPosts = [Post]()
func loadUserFeed(_ update: #escaping () -> Void) {
userFeedHandle = CURRENT_USER_FEED_REF.observe(DataEventType.value, with: {(snapshot) in
self.followingPosts.removeAll()
for child in snapshot.children.allObjects as! [DataSnapshot] {
let post = Post(postID: child.key, postData: child.value as! Dictionary<String, Any>)
self.followingPosts.append(post)
self.followingPosts.sort(by: { Double(truncating: $0.time) > Double(truncating: $1.time)})
update()
}
if snapshot.childrenCount == 0 {
update()
}
})
}
It appears that CURRENT_USER_FEED_REF is the location containing a given user's posts, i.e. follower-feed/$userID, and that you were expecting that the security rule for post age would act a filter, allowing the user's recent posts to be returned by the query and old posts to be excluded. But security rules are not filters. For any location, you'll either be able to read all of the data (including its children), or none of it. You have no rule allowing read at follower-feed/$userID, so a query at that location will fail.
See this answer from a Firebase team-member for an idea on how to implement what you want, or search for "firebase rules are not filters" to see other related questions and answers.

Unique usernames in Firebase

I have been trying to implement Chris’ answer here: Can I make Firebase use a username login process? for the Facebook login but I can’t seem to get my head around it.
So far I’ve tried to set conditions on the textField but as Firebase observer works asynchronously, the conditions to check if the username exists in the database won’t work.
let usernameString = usernameTextField.text
let uid = FIRAuth.auth()?.currentUser?.uid
ref.runTransactionBlock({ (currentData: FIRMutableData) -> FIRTransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = FIRAuth.auth()?.currentUser?.uid {
let usernamesDictionary = post["usernames"] as! NSDictionary
for (key, _) in usernamesDictionary {
if key as? String == usernameString {
print("username not available: \(key)")
}
else if usernameString == "" {
print("Uh oh! Looks like you haven't set a username yet.")
}
else if key as? String != usernameString {
print("username available: \(key)")
print("All set to go!")
let setValue: NSDictionary = [usernameString!: uid]
post["usernames"] = setValue
currentData.value = post
}
}
return FIRTransactionResult.successWithValue(currentData)
}
return FIRTransactionResult.successWithValue(currentData)
}
Then I tried creating /usernames/ node in the database and set up rules as:
{
"rules": {
"usernames": {
".read": "auth != null",
".write": "newData.val() === auth.uid && !data.exists()"
}
}
}
Now that won’t let me set any username to the database. I get confused in creating rules but my whole point is that I need a sign up flow with the username data that’s unique for each user in the database.
While trying every answer I found in related posts, what worked for me the easy way i.e. without making Firebase rules play a part in it or creating a separate usernames node in the database was to not put an if/else condition inside the Firebase observer but instead to use the exists() method of FIRDataSnapshot.
Now here’s the trick, while I did try only the exists() method with a simple observer but that did not help me. What I did was first query usernames in order, then match the username with queryEqualToValue to filter the query:
refUsers.queryOrderedByChild("username").queryEqualToValue(usernameString).observeSingleEventOfType(.Value , withBlock: {
snapshot in
if !snapshot.exists() {
if usernameString == "" {
self.signupErrorAlert("Uh oh!", message: "Looks like you haven't set a username yet.")
}
else {
// Update database with a unique username.
}
}
else {
self.signupErrorAlert("Uh oh!", message: "\(usernameString!) is not available. Try another username.")
}
}) { error in
print(error.localizedDescription)
}
}
This is the first time out of most of the answers here that worked for me. But for now, I don’t know if this would scale. Post your experiences and best practices. They’ll be appreciated.

Swift, Firebase. Check if a users, childs value exists

In my game you, first have to login, then you have to choose a team name, which gets stored in my firebase database, under the players UID, and when the player has entered his team name, I want to check if it is already taken, or the player is good to go.
let rootRef = FIRDatabase.database().reference()
rootRef.queryOrderedByChild("teamName").queryEqualToValue("Bob fc").observeEventType(.Value, withBlock: { snapshot in
if (snapshot.value is NSNull) {
print("Name is not in use")
} else {
print("Name is in use")
}
})
My data tree:
{
"users" : {
"pbXvXYOKmJQqwSQZ9IlBykG7x1P2" : {
"teamName" : "Bob fc"
}
}
}
My database rules:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
The problem is that it doesn't print anything, what am I doing wrong here?
You are querying your root ref. You should query the /users node instead
let rootRef = FIRDatabase.database().reference()
let usersRef = rootRef.childByAppendingPath("users")
usersRef.queryOrderedBy....
You can shorten that up but I used the verbose model for clarity.
As a side note, with Firebase 3.x, the default is to only allow authenticated users to read and write. This is accomplished through Rules in the Realtime Database section.
If you want to test your code without authenticating, change your Rules to this
{
"rules": {
".read": true,
".write": true
}
}
PLEASE NOTE: This opens up your data to ANYONE that wants to read it, but if you are just learning or testing an app it makes it a bit more convenient.