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

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.

Related

Trouble finding out if this counts as a read/many reads/will I get charged loads on database costs?

I am currently developing an iOS app with a google cloud firestore as a backend and I am using a few listeners to find out if data is updated and then pushing it to my device accordingly. I wrote this function that listens for a value if true or not and according to so will update an animation in my app. The trouble is I don't know if I wrote it properly and don't want to incur unnecessary reads from my database if I don't have to.
func dingAnimation() {
let identifier = tempDic![kBOUNDIDENTIFIER] as! String
if identifier != "" {
dingListener = reference(.attention).document(identifier).addSnapshotListener({ (snapshot, error) in
if error != nil {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
return
}
guard let snapshot = snapshot else { return }
let data = snapshot.data() as! NSDictionary
for dat in data {
let currentId = FUser.currentId() as! String
let string = dat.key as! String
if string == currentId {
} else {
let value = dat.value as! Bool
self.shouldAnimate = value
self.animateImage()
}
}
})
}
}
This might help you.
From Firestore DOCS - Understand Cloud Firestore billing
https://firebase.google.com/docs/firestore/pricing
Listening to query results
Cloud Firestore allows you to listen to the results of a query and get realtime updates when the query results change.
When you listen to the results of a query, you are charged for a read each time a document in the result set is added or updated. You are also charged for a read when a document is removed from the result set because the document has changed. (In contrast, when a document is deleted, you are not charged for a read.)
Also, if the listener is disconnected for more than 30 minutes (for example, if the user goes offline), you will be charged for reads as if you had issued a brand-new query.

Annoying issue regarding indexOn in Firebase rules

I have read practically every StackOverflow answer and none of them worked for my scenario since this is a frequent issue. My Xcode console is giving a very common warning when querying for data in Firebase. That warning is Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "username" at /users to your security rules for better performance
What I have tried was to first read the Firebase documentation understanding exactly what I am doing along with other answers as stated such as Why does this Firebase ".indexOn" not work?. Below, I have provided my security rules doing exactly as the message prompt; adding indexOn at /users but to no success. I have also provided my database users node and one function below.
{
"rules": {
".read": true,
".write": true,
"users":{
".indexOn": "username"
}
}
}
My Firebase database at /users in JSON format
"users":{
"5LYUynelLTcL8Bg9WNWGXV34YIq2" {
"email": "user1#gmail.com"
"username": "user1"
}
"9srk307kzxOW7j6dNmMaac9eYPu2" {
"email": "user2#gmail.com"
"username": "user2"
}
My function that I use in Swift
let ref = Database.database().reference().child("users").queryOrdered(byChild: "username").queryEqual(toValue: passedInFriendString)
ref.observeSingleEvent(of: .value) { (snapshot) in
print(snapshot)
}
I'm not sure where else to turn to. Any insight on if this is even the correct format to query for what I want would be great!
I solved it!
The thing I did not realize was that the above code in my question DOES print the snapshot successfully after adding the appropriate index definition on /users. My issue was realizing you need to loop through the snapshot if you want to further access data of the username snapshot I was querying for accordingly. That would look something like this
let ref = Database.database().reference().child("users").queryOrdered(byChild: "username").queryEqual(toValue: passedInFriendString)
ref.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
guard let snap = child as? DataSnapshot else {return}
if snap.hasChild("email") {
print("It has an email")
}
}
}
Thanks to Frank above for guiding me and confirming that I was on the right track. I'm happy to have learned something new and super efficient whenever I need to grab data.

Firebase Swift - Slow response - Rules index [g]

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)

Firebase audience based on user properties

I need to test some features of my app with just a previously selected group of users.
I created an Audience where user_id exactly matches 123456. 123456 being my own ID.
In Remote Config I created a Condition that matches users in the Audience above.
Then I created a parameter in Remote Config called feature_available and for the condition, I set a return value of true. The Default value is false.
In my app I set up Firebase:
FIRApp.configure()
let remoteConfig = FIRRemoteConfig.remoteConfig()
if let remoteConfigSettings = FIRRemoteConfigSettings(developerModeEnabled: false) {
remoteConfig.configSettings = remoteConfigSettings
}
remoteConfig.setDefaultsFromPlistFileName("FirebaseRemoteConfigDefaults")
And set the user ID:
FIRAnalytics.setUserID("123456")
Then I fetch from Firebase:
var expirationDuration: Double
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from the server.
expirationDuration = remoteConfig.configSettings.isDeveloperModeEnabled ? 0 : 3600
remoteConfig.fetch(withExpirationDuration: TimeInterval(expirationDuration)) { (status, error) -> Void in
if status == .success {
remoteConfig.activateFetched()
} else {
assertionFailure("Firebase config not fetched. Error \(error!.localizedDescription)")
}
}
The last thing I do is to get the value from Firebase and check if I have the feature enable:
let featureIsAvailable = remoteConfig["feature_available"].boolValue
if featureIsAvailable { ... }
The problem is that every single time the value returns from Firebase it is false and I can't manage to get it to return the correct value that matches that Audience I created.
I also tried to do it setting a user property instead of using setUserID() and had the same result.
Any suggestions?
I've run into similar issues before, sometimes it can take a while for the fetch to finish. The check if the feature is available needs to be done when the config is successfully fixed. Something like this hopefully works for you as well:
var featureIsAvailable : Bool?
override func viewDidLoad() {
super.viewDidLoad()
configureRemoteConfig()
fetchConfig()
}
func configureRemoteConfig() {
remoteConfig = FIRRemoteConfig.remoteConfig()
// Create Remote Config Setting to enable developer mode.
// Fetching configs from the server is normally limited to 5 requests per hour.
// Enabling developer mode allows many more requests to be made per hour, so developers
// can test different config values during development.
let remoteConfigSettings = FIRRemoteConfigSettings(developerModeEnabled: true)
remoteConfig.configSettings = remoteConfigSettings!
remoteConfig.setDefaultsFromPlistFileName("RemoteConfigDefaults")
}
func fetchConfig() {
var expirationDuration: Double = 3600
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if (self.remoteConfig.configSettings.isDeveloperModeEnabled) {
expirationDuration = 0
}
// cacheExpirationSeconds is set to cacheExpiration here, indicating that any previously
// fetched and cached config would be considered expired because it would have been fetched
// more than cacheExpiration seconds ago. Thus the next fetch would go to the server unless
// throttling is in progress. The default expiration duration is 43200 (12 hours).
remoteConfig.fetch(withExpirationDuration: expirationDuration) { (status, error) in
if (status == .success) {
print("Config fetched!")
self.remoteConfig.activateFetched()
let featureIsAvailable = self.remoteConfig["feature_available"]
if (featureIsAvailable.source != .static) {
self.featureIsAvailable = featureIsAvailable.boolValue
print("should the feature be available?", featureIsAvailable!)
}
} else {
print("Config not fetched")
print("Error \(error)")
}
self.checkIfFeatureIsAvailable()
}
}
func checkIfFeatureIsAvailable() {
if featureIsAvailable == false {
// Don't show new feature
} else {
// Show new feature
}
}
I sent an email to the Firebase team requesting support and they told me that the issue was a bug in Xcode 8(.0 and .1) using Swift.
Updating to the latest version released today, 8.2, fixed the issue.

Firebase 3 can't modify other user created data

In this application, each user has a counter for "votes" but it only works when the same user votes, when another user tries to do a FIRTransaction in that reference it returns nil. The rules are set to any user.
When I need that user2 updates the user1 "votes" value it doesn't reads that key value to perform the transaction block, so it returns nil.
votesCountRef = ref.child("user1").child("votes")
votesCountRef.runTransactionBlock( { (currentData) -> FIRTransactionResult in
let value = currentData.value! as! Int
currentData.value = value + 1
return FIRTransactionResult.successWithValue(currentData)
})
result:
Received votes actual value: Optional()
Could not cast value of type 'NSNull' (0x110684600) to 'NSNumber' (0x10fce92a0).
But when original user runs this it works successfully.
Json tree for user1:
{
"userUID" : "user1",
"votes" : 2
}
Database rules:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Thanks to #Frank I could manage it like this:
ownerReceivedVotesCountRef.runTransactionBlock( { (currentData) -> FIRTransactionResult in
let value = currentData.value as? Int ?? 0
currentData.value! = (value as Int) + 1
return FIRTransactionResult.successWithValue(currentData)
})
A transaction handler is initially run with the client's best guess fo rthe current value. Quite often that will be nil, so your code has to handle that.
From the Firebase documentation:
Note: Because runTransactionBlock:andCompletionBlock: is called multiple times, it must be able to handle nil data. Even if there is existing data in your remote database, it may not be locally cached when the transaction function is run, resulting in nil for the initial value.
Something like this should work for your case:
let value = currentData.value as? Int ?? 0