I'm trying to set firebase rules to my database but i'm having problem with data and newData
According to firebase:
newData A RuleDataSnapshot corresponding to the data that will result
if the write is allowed.
For .write and .validate rules, the newData variable gives you a
RuleDataSnapshot corresponding to the data that will result if the
write is allowed (it is a "merging" of the existing data plus the new
data being written).
So I have the following rule:
"users_details":{
"$uid":{
".write":"$uid == auth.uid && newData.exists()",
".read":"$uid == auth.uid",
".validate":"newData.child('auth').hasChild('crsfToken')",
}
User has a field auth which has a field crsfToken... so everytime someone writes a user i want to ensure the "edited" user will have auth/crsfToken
Ok.
Let's say a have an actual valid user abc.
Then i want to set another field on abc, that has no relation to any rule...
lets say: abc.field.subField = 1
so what I am doing is:
FirebaseDatabase.getInstance().getReference(Firebase.NODE_USERS_DETAILS).child(user.getUserid()).child("field").child("subField").setValue(1);
this isn't changing auth nor crsfToken but i'm getting Permision Denied
When I do a simulation on console, i see that isn't writing because the validate rule is denied
why? is there any way to debug firebase rules and see what is newData on a simulation?
Rules are resolving ok :) Because rules will be checked for all data manipulations on user_details/UID/*. So if you update user_detail/UID/field/subField, your rules will be checked and you will get access denied as you are. You said that you want to check for newData.auth.crsfToken and you are doing that indeed, but now you want to update field/subField without checking that? In which case you don't want to check crsfToken existence?
Related
i want go give some user Special permission to write to a folder like (Sp_Adim) if they have a custom UID . to give permission all custom UID saved in a folder in real time data base how can i check this
"Newdata.child('S_uid').val === root.child(\"$folder\")"
i tried its not working even the folder are inside there Cust_uid/sdsd get denied
root.child('Cust_uid').child(\"$custom_id\").exists() === 'sdsd'
What am I doing wrong?
It looks like you're putting the wildcard capture variables in quotes, which is not correct.
If you have a $custom_id variable in your rules tree, you can use that in a specific rule with:
root.child('Cust_uid').child(custom_id).exists()
If you want to check the value of a node under that root, be sure to call .val to the end of path too. So:
newdata.child('S_uid').val === root.child($folder).val()
Finally, note that case is important, so it's newData and not NewData.
I have Firebase data structure like this:
As you can see in the image, the value of OT1, OT1_B, etc. is Integer. Now, I want such a validation that no one can save value of other data-type for this key.
How can set I rule for this kind of restriction?
There is a rule available to validate your data in FireBase RealTime Database. Use .validate rule to check if your data is a string or other type and you can also use regular expression to check the matches.
Refer the official documentation to check how to Validate Data under section
Validating Data.
If you are taking user input for that fields then simply adding ,
android:inputType="numberDecimal"
will do a work for you
I have some Edittext that allows the user to enter data:
AppName
Date
Title
Quantity
User
I started a new Firebase project and it is currently a blank slate.
I am setting up my Firebase instance like this:
private DatabaseReference mDatabase;
mDatabase = FirebaseDatabase.getInstance().getReferenceFromUrl("https://myapp.firebase.io");
How I want it is everytime a new adding occurs, I would like to add it like this (AppName is the common ground for all entry):
AppName
Date
Title
Quantity
User
Date
Title
Quantity
User
...
...
AppName
Date
Title
Quantity
User
Date
Title
Quantity
User
...
...
How can I do the following:
The first time it is added, the AppName will be the parent node, and
anytime after Firebase should check to see if the parent node exist
and is the same and add it to that, otherwise create a new parent node
and add the children node to the new parent node.
I currently have my auth set up like this:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
I had to change to true for my app to write to Firebase.
How can I:
Not register the user but only allow the app to able to write to
Firebase.
EDIT:
I am doing the following:
mDatabase.child("AppName").setValue(etName.getText().toString());
mDatabase.child("AppName").child("Date").setValue(etDate.getText().toString());
mDatabase.child("AppName").child("Title").setValue(etQuantity.getText().toString());
mDatabase.child("AppName").child("Quantity").setValue(User.getText().toString());
mDatabase.child("AppName").child("User").setValue(etUser.getText().toString());
Instead of adding, it is overwriting the previous entry. How can I append.
If I understood you, you need that not registered users could edit your database on Firebase and how to know If you added an ID.
First of all, as Ishan Fernando said, If you add data with the same id, this data will be overwrited, and using the push method, Firebase will create an unic ID to add you data
Read this: https://firebase.google.com/docs/database/android/read-and-write?hl=en
This Gist, thanks to the user realgt, could help you to know if the Appname exists or not: https://gist.github.com/anantn/4323949#gistcomment-998628
This will get all your data and you could check if you need add it or not
And, if you don't want to register users, but you need them to write the database, I think that you need to register them as anonymous users:
https://firebase.google.com/docs/auth/android/anonymous-auth
Firebase needs registered users (using social networks, email o anonymously) to modify it, so try the anonymous method. The problem is when the user get out of your app, this user will keep registered on your database. So, I recommend you, (Please, correct me if I'm wrong) to erase the user when onStop starts and create it again when onReady starts.
I hope it helps.
I found this documentation that explains about how to link anonymous user with a new registered user, but I couldn't understand how it works.
From the explanation also found here, I got a big picture like below (please correct me if I'm wrong):
User login with anonymous: got userUid (just for example) ANONYM-USER-UID
User than add data to shopping cart like below:
data
-- shoppingCart
-- ANONYM-USER-UID
-- <push-id>
-- itemUid: <item-uid>
-- count: 2
-- <push-id>
-- itemUid: <other-item-uid>
-- count: 1
-- OTHER-USER-UID
-- .......
with rule: Only appropriate UserId can access shopping cart
"rules": {
"shoppingCart" {
"$userUid": {
".read": "auth.uid == $userUid"
}
}
}
Before checkout, user "forced" to register/or login, the AuthCredential then retrieved then linkWithCredential called. User than use a new userUID for example REGISTERED-USER-UID
The question is, whenever the client code query shoppingCart/REGISTERED-USER-UID will it retrieve the item list of shoppingCart/ANONYM-USER-UID? Will the Rule allow it?
What if a more complex rule is applied, for example, the rule becomes
-- Only appropriate UserId can access shopping cart, but userUid must not in blackList child.
"rules": {
"shoppingCart" {
"$userUid": {
".read": "auth.uid == $userUid && root.child('blackList').child($userUid).val() == false"
}
}
}
With a logical restriction like that, will it successfully return the list?
I think you are slightly confused regarding the flow of Anonymous-User login and then linking it with new Auth Credentials. Here is what I have experienced.
When a user logs in as anonymous then system assigns a unique ID. Now when the user decides to sign up with some new auth credentials then all that happens is that previous UID generated at time of anonymous login gets assigned or linked to those new credentials.
So in reality no new UID is created and you are good to go.
Do let me know if this info was helpful.
I want to make a system which allows for username login. This process requires the following:
User must register with an email/password
The user can set a unique username
The user can sign in with either email or username
The user can recover their password via email or username
The functionality must work on a persistence enabled database
This question has been answered previously, but it disabled the functionality for the user to use password recovery. They also didn't address case-sensitivity, one person could register as "scooby" and another as "Scooby".
DISCLAIMER: This code is now over two years old. While this doesn't mean it's deprecated, I would strongly recommend investigating alternative methods before assuming this is the best approach. I personally wouldn't want the standard login process for Firebase to be dictated by my initial approach to a problem while Firebase wasn't as heavily adopted as it is now.
After multiple iterations of development I've come up with the following design to address this. I will post my code snippets in Swift, but they will typically be translatable directly into Android with ease.
Create a Firebase registration process for email/password.
This is required as the backbone of the user's sign-in experience. This can be implemented completely from the stock Firebase API documentation provided here
Prompt the user to enter their username
The username entry should be completed at registration, I'd recommend an additional field in the registration flow. I also recommend checking if the user has a username whenever they log in. If they don't, then display a SetUsername interface that prompts them to set a username before progressing further into the UI. A user might not have a username for a few reasons; it could be revoked for being rude or reserved, or they might have signed up prior to the username being required at registration.
Make sure that if you're using a persistence-enabled Firebase build that you use Firebase Transactions. The transactions are necessary, otherwise your app can make assumptions about the data in the username table, even though a username might have been set for a user only seconds earlier.
I would also advise enforcing the username to be nearly alphanumeric (I allow for some harmless punctuation). In Swift I can achieve this with the following code:
static var invalidCharacters:NSCharacterSet {
let chars = NSMutableCharacterSet.alphanumericCharacterSet()
// I add _ - and . to the valid characters.
chars.addCharactersInString("_-.")
return chars.invertedSet
}
if username.rangeOfCharacterFromSet(invalidCharacters) != nil {
// The username is valid
}
Saving the user data
The next important step is knowing how to save the user's data in a way that we can access it in the future. Below is a screenshot of the way I store my user data:
A few things to note:
The usernames are stored twice, once in usernames and again in details/[uid]/username. I recommend this as it allows you to be case sensitive with usernames (see the next point) and it also allows you to know the exact database reference to check a username (usernames/scooby) rather than having to query or check through the children of details to find a username that matches (which would only become more complicated when you have to factor in case-sensitivity)
the usernames reference is stored in lowercase. When I check the values in this reference, or when I save to this reference, I ensure that I only save data in lowercase. This means if anyone wants to check if the username 'scoobY' exists, it will fail because in lowercase it's the same username as the existing user "Scooby".
The details/[uid]/username field contains capitals. This allows for the username to display in the case of preference for the user, rather than enforcing a lowercase or Capitalised word, the user can specify their name as "NASA Fan" and not be converted over to "Nasa Fan", while also preventing anyone else from registering the username "NASA FAN" (or any other case iterations)
The emails are being stored in the user details. This might seem peculiar because you can retrieve the current user's email via Firebase.auth().currentUser.email?. The reason this is necessary is because we need references to the emails prior to logging in as the user.
Logging in with email or username
For this to work seamlessly, you need to incorporate a few checks at login.
Since I've disallowed the # character in usernames, I can assume that a login request containing an # is an email request. These requests get processed as normal, using Firebase's FIRAuth.auth().signInWithEmail(email, password, completion) method.
For all other requests, we will assume it's a username request. Note: The cast to lowercase.
let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/usernames/\(username.lowercaseString)")
When you perform this retrieval, you should consider if you have persistence-enabled, and if there's a possibility that a username could be revoked. If a username could be revoked and you have persistence-enabled, you will want to ensure you retrieve the username value within a Transaction block, to make sure you don't get a cached value back.
When this retrieval succeeds, you get the value from username[username], which is the user's uid. With this value, you can now perform a retrieval on the user's email value:
let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/details/[uid]/email")
Once this request succeeds, you can then perform the standard Firebase email login with the email string you just retrieved.
The exact same retrieval methods can be used to retrieve an email from a username to allow for password recovery.
A few points to be wary of for advanced functionality:
- If you allow the user to update their email using FIRUserProfileChangeRequest, make sure you update it both on the auth AND the details[uid]email field, otherwise you will break the username login functionality
- You can significantly reduce the code required to handle all the different failure cases in the retrieval methods by using success and failure blocks. Here's an example of my get email method:
static func getEmail(username:String, success:(email:String) -> Void, failure:(error:String!) -> Void) {
let usernameRef = FIRDatabase.database().reference().child("users/usernames/\(username.lowercaseString)")
usernameRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let userId = snapshot.value as? String {
let emailRef = FIRDatabase.database().reference().child("users/details/\(userId)/email")
emailRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let email = snapshot.value as? String {
success(email: email)
} else {
failure(error: "No email found for username '\(username)'.")
}
}) { (error) in
failure(error: "Email could not be found.")
}
} else {
failure(error: "No account found with username '\(username)'.")
}
}) { (error) in
failure(error: "Username could not be found.")
}
}
This success/failure block implementation allows the code I call in my ViewControllers to be much cleaner. Å login calls the following method:
if fieldText.containsString("#") {
loginWithEmail(fieldText)
} else {
// Attempt to get email for username.
LoginHelper.getEmail(fieldText, success: { (email) in
self.loginWithEmail(email)
}, failure: { error in
HUD.flash(.Error, delay: 0.5)
})
}