How to prevent user from reading after password change? - android

In my firebase realtime database, only authenticated user can read data. Suppose a user(suppose xyz) is logged in in app and I reset his password. After resetting/changing password, xyz is still able to read/write in database until I reauthenticate and logged him out manually(through code). The problem is, if someone modify app source code and remove manually log-out part, how do I prevent that user from reading/writing to database?

You can do this by invoking a cloud function with the new password, I would advise encoding the password and decoding it but this is out of the scope of this question.
// declare function
exports.updatePass= functions.https.onCall((data, context) => {
return admin
.auth()
.updateUser(context.auth.uid, { password: 'newPassword' })
.then(async (userRecord) => {
const utcRevocationTimeSecs = await new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
return admin.database().ref('metadata/' + uid)
.set({ revokeTime: utcRevocationTimeSecs })})
.then(() => admin.auth().revokeRefreshTokens(context.auth.uid))
.then(() => return {message: "success"};
.catch((error) => {
return error;
});
});
This will write the changes to the realtime database of which you can listen to changes, and use within your Security Rules, This can easily swap this out for firestore if needed.
{
"rules": {
"metadata": {
"$user_id": {
// this could be false as it is only accessed from backend or rules.
".read": "$user_id === auth.uid",
".write": "false",
}
}
}
}
Source:
https://firebase.google.com/docs/auth/admin/manage-users#update_a_user
https://firebase.google.com/docs/auth/admin/manage-sessions

Firebase Authentication is based on tokens, the shorted lived (ID token) of which is valid for an hour. Changing the password on the account does not invalidate existing ID token(s), so if you want the user to lose access immediately, you'll have to do that in another way.
A common way is to keep a banlist/blocklist in the database, for the UIDs that are supposed to be blocked even if they have a valid ID token. This scenario is described more fully in the documentation on detecting ID token revocation.
With this approach though, you don't have a clear trigger on when to unblock/reenable the user's access, as there is no callback when their password changes. The best I can think of is to periodically run a Cloud Function to check if the user's password hash has changed, but I'm hoping somebody comes up with a better option for this step.

Related

Realtime Database from insecure to secure rules without interrupting services

Today, I received an email which states,
[Firebase] Your Realtime Database 'CC-default-rtdb' has insecure rules, any user can read your entire database, any user can write to your entire database
My server runs every day to store values in the Realtime Database. When I started this Firebase project, I used the basic read and write rules.
{
"rules": {
".read": true,
".write": true
}
}
If I change the rules now, does it affect my external Node.JS server from storing values into the Realtime DB?
I even access this DB from a mobile app, so will it affect the mobile app from reading data if the rules are changed?
How can I secure these Realtime DB rules (from public to private) without interrupting the access for the external server and mobile app? Importantly, my external server access must not get disrupted.
If you are accessing your database through authentication(login) you can set your nodes to auth!=null so that any unauthorized user cannot access them.
You need to follow the following steps:
Set read to true for all nodes you need to make publicly available
Set read/write to auth!=null for all nodes you want to make available to any authorized user
Set custom validation rules for any node that needs special access (e.g.: A user can only write to his/her data)
As per the screenshot, if anyone gets a hold of your project id, they can modify and steal your entire database.
You need to set rules in a way that properly matches your use case. As far as I understand:
Allow the external server account access to the whole DB
Allow the mobile app conditional access (by user perhaps?)
Example
Take this database for instance:
{
"top_level": {
"users": [
{
"username": "X",
"userId": "0"
},
{
"username": "Y",
"userId": "1"
}
],
"public_data": {
"news": [
{
"title": "X",
"body": "Y"
}
]
}
}
}
I want to set:
Node "news" to be publicly available to read (no auth)
Node "users" to be only available to other logged-in users to read(auth)
Children of node "users" to only be writable for the user whose id matches the value userId in the node
The rule, in this case, would be:
{
"rules": {
".read": false,
".write": false,
"top_level": {
"users": {
".read": "auth!=null",
"$user_id": {
".write": "auth!=null && data.child('userId').val()===auth.uid"
}
},
"news" : {
".read":true
}
}
}
}
Note, rules set as true in the top-level override inner rules.
Comment if you need clarification.

Firestore security rules to allow access only to specific queries and not the whole collection

Given the following simplified Firebase Firestore database structure:
users
user1
email: "test1#test.com"
user2
email: "test2#test.com"
I want to be able to query if a user with a specific email exists in the database WITHOUT giving access to the whole users collection
Is it possible to achieve this using the database rules without modifying the database structure?
If it's not possible, what would be the best workaround for this?
I see two possible solutions, but it seems to me that they add too much complexity:
Expose that specific query via an API endpoint (maybe using Firebase Functions)
Modify the DB structure as suggested in this thread: Firestore security rules based on request query value
Which approach do you think is better?
To meet the requirement, "query if a user with a specific email exists in the database WITHOUT giving access to the whole users collection," you'll need to rethink your database architecture. Firestore won't allow you to make queries on data that you don't also have read access to.
I would recommend creating a separate collection that just contains email addresses in use like this:
{
"emails": {
"jane#example.com": { userId: "abc123" },
"sally#example.com": { userId: "xyz987" },
"joe#example.com": { userId: "lmn456" }
}
}
This collection should be updated everytime a user is added or an email address is changed.
Then set up your Firestore rules like this:
service cloud.firestore {
match /databases/{database}/documents {
match /emails/{email} {
// Allow world-readable access if the email is guessed
allow get: if true;
// Prevent anyone from getting a list of emails
allow list: if false;
}
}
}
With all of that you can then securely allow for anonymous queries to check if an email exists without opening your kimono, so to speak.
List All Emails
firebase.firestore().collection('emails').get()
.then((results) => console.error("Email listing succeeded!"))
.catch((error) => console.log("Permission denied, your emails are safe."));
Result: "Permission denied, your emails are safe."
Check if joe#example.com exists
firebase.firestore().collection('emails').doc('joe#example.com').get()
.then((node) => console.log({id: node.id, ...node.data()}))
.catch((error) => console.error(error));
Result: {"id": "joe#example.com": userId: "lmn456"}
Check if sam#example.com exists
firebase.firestore().collection('emails').doc('sam#example.com').get()
.then((node) => console.log("sam#example.com exists!"))
.catch((error) => console.log("sam#example.com not found!"));
Result: sam#example.com not found!

Firebase realtime database validation username and email

Recently found out the database rules can be set to validate whether the database already have the same data but currently it just accepted same data to insert but how to validate and prevent same input of username and email?
{
"rules": {
".read": true,
".write": true,
"username":{
".validate": "!root.child('username').child(newData.val()).exists()"
},
"email":{
".validate": "!root.child('email').child(newData.val()).exists()"
}
}
}
The root child is created by email authentication uid and the rest will be under the same nodes.
How to prevent user enter same username and email?
Your rules validate if a single property /username exists and has the same value as the new data you're writing. Your use-case seems different: you want to ensure a unique user name across all users.
You cannot ensure a unique value across many multiple nodes in Firebase's security rules. You instead will need to store the user names as keys in a separate collection, i.e.
usernames
"rexyou0831": "ik3sf...."
The above data structure indicates that user ik3sf... claimed name rexyou0831. With such a structure in place, user names are guaranteed to be unique since keys must by definition be unique in a collection. You can ensure that users can only write new names or delete their own name with:
{
"rules": {
"usernames": {
"$name": {
".write": "!data.exists() || newData.val() === data.val()"
}
}
}
}
To enforce uniqueness of the email addresses too, you will need to create a similar collection for those.
For more explanation read one of the previous questions covering this same topic:
Firebase android : make username unique
Enforcing unique usernames with Firebase simplelogin
Firebase Database Rules for Unique Usernames
How do you prevent duplicate user properties in Firebase?

Firebase check if a value exists without reading a snapshot or trying a write (for unique usernames)?

I've been stuck on this problem for many hours now, so any help is appreciated. Trying to make an Android app with Firebase for user authentication (simple-login email and password) along with unique usernames. I have my sign-up screen fields laid out on a single screen e.g.:
"Enter Username"
"Enter Email Address"
"Enter Password"
I'm very confused as how how to query the database to check if the username exists or not without reading a database snapshot and without attempting to write the username to the database (because this is happening while the user is in state auth == null, so while on the sign-up page before the user has created his account I want to inform the user whether his username is taken or not).
I mean, this feels like it should be very simple, just a simple query to Firebase with a string and just getting Firebase to return True or False, but after hours of googling I could find nothing.
The reason I don't want to use a snapshot to do this is because I do not want to expose all my user's names and their UIDs to the public by setting "read" to true (I followed this guide so my security rules are set up just like this, along with my database structure Firebase android : make username unique).
Here are my rules, and they work currently (I don't like the fact that the read is set to True which is why I'm asking the question though):
{
"rules": {
"usernames": {
".read": true,
"$username": {
".write": "auth !== null && !data.exists()"
}
},
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid && !data.exists()",
".read": "auth !== null && auth.provider === 'password' && auth.uid === $uid",
"username": {
".validate": "(!root.child('users').child(newData.val()).exists() || root.child('usernames').child(newData.val()).val() == $uid)"
}
}
}
}
}
And this is my data:
{
"usernames" : {
"abcd" : "some-user-uid"
},
"users" : {
"\"some-user-uid\"" : {
"username" : "abcd"
}
}
}
Thanks!
There is unfortunately, no way to test whether the data exists without actually downloading it via the SDK. Data structures are going to be supreme here (recommended reading: NoSQL Data Structures and you shouldn't be afraid to denormalize a bit of data when optimization and scale are critical.
Generally speaking, you should keep your data well structured so payloads are small and fetch it. If you're fetching something that can't wait for the bytes to be fetched (e.g. games, strange one-off admin ops on very large data sets, et al) then here are a few reasonable approaches to simulate this:
Fetching a list of keys via the REST API
Using the attribute shallow=true in a call to the REST API will prevent loading of a large data set and return only the keys at that path. Note that if you store a million records, they still have to be loaded into memory on the server (slow) and you still have to fetch a million strings (expensive).
So one way to check the existence of data at a path, without actually downloading the data, would be to make a call to the path, such as https://<YOUR-FIREBASE-APP>.firebaseio.com/foo.json?shallow=true, and check whether any keys are returned.
Creating a denormalized index you can query instead
If you really need to squeeze some extra performance and speed out of your Firebase Database (hint: you don't need this unless you're running millions of queries per minute and probably only for gaming logic and similar), you can dual-write your records (i.e. denormalize) as follows:
/foo/data/$id/... data goes here...
/foo/index/$id/true (just a boolean value)
To dual write, you would use the update command, and a write similar to the following (Android SDK sample):
public void addRecord(Map<String, Object> data) {
DatabaseReference db = FirebaseDatabase.getInstance().getReference();
// create a new record id (a key)
String key = db.child("foo").push().getKey();
// construct the update map
Map<String, Object> dualUpdates = new HashMap<>();
dualUpdates.put("/data/" + key, /* data here */);
dualUpdates.put("/index/" + key, true);
// save the new record and the index at the same time
db.child("foo").updateChildren(dualUpdates);
}
Now to determine if a record exists, without actually downloading the data, I can simply query against /foo/index/$id and try DataSnapshot.exists(), at the cost of downloading a single boolean.

Clear firebase persistence after logout

Is there any way to clear data stored in Firebase Database persistence enabled via setPersistenceEnabled(true)?
Or eventually prevent read access to previously stored users data on the device?
And extra question - is there any way to check if any data is waiting to sync?
In my app I want to clear db when user is logging out and he confirm that unsynchronized changes will be removed. This app will be used on tablet by many people, and there'll be many data stored per user, so I want to clear unnecessary data stored on the device to:
shrink storage usage
prevent unauthorized person from read users data
It's a security flaw because any user, regardless of whether is signed in or not, has read access to all previously downloaded data! Fortunately we haven't write access there.
I tried to clear db on my own, in both offline and online mode, but data still persist on the device:
// first clear all data on my own
FirebaseDatabase db = FirebaseDatabase.getInstance();
db.goOffline(); // even when I clear db in online mode data persist on mobile device
db.getReference().setValue(null);
// and then logout
FirebaseAuth.getInstance().signOut();
db.goOnline();
eg data structure:
{
"someList": {
"userId1": {
"itemId11": {
"name": "name 11"
},
"itemId12": {
"name": "name 12"
},
...
"itemId1N": {
"name": "name 1N"
}
}
"userId2": {
"itemId21": {
"name": "name 21"
},
"itemId22": {
"name": "name 22"
},
...
"itemId2N": {
"name": "name 2N"
}
}
}
}
my rules:
{
"rules": {
".read": "false",
".write": "false",
"someList": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
Unfortunately there's no supported way to clear the persistence cache. You could try to manually delete the SQLite database used by Firebase Database (should be under /databases/ in your app's data folder), but you'd need to do it before initializing Firebase Database, so you'd have to force the app to restart after signing somebody out or similar.
Regarding knowing if there's "data waiting to sync", assuming you're talking about writes, the only way is to attach CompletionListeners to every write operation you do, and wait for them to complete.
Both of these are things we'd like to improve in future versions of the Firebase Database SDK.
clearPersistence() was launched in v6.2.0.
via
https://github.com/firebase/firebase-js-sdk/issues/449
if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) {
((ActivityManager) getContext().getSystemService(ACTIVITY_SERVICE))
.clearApplicationUserData();
}
source
Oh dear what a terrible hack this solution is, I apologise in advance...
This works tho, to clear the local cache:
disable persistence
take a full copy of the new node, e.g. for user 2
e.g. var copyData = await (await ref.once()).value;
enable persistence
replace the new node, e.g. user 2, with the copyData
In theory only data which has been changed will be written, which should mean no changes to the realtime database. Meanwhile the local cache is replaced with the data from the server.
I got this approach to work for my needs. It's the basis of a solution, which would be different depending on every use case.
Good luck!

Categories

Resources