Firebase JSON Security and Arrays - firebase

We'd like to use Firepad in our (mostly non-Firebase hosted) project, but we're having some troubles figuring out the best way to approach the problem.
Basically, we have many users, and each user can be a member of many groups. These "groups" each have their own Firepad which users can edit. We already have a deeply developed database structure using MySQL and don't really want to migrate our user data into Firebase right now, so we figured we'd get more creative.
We don't want users being able to edit the Firepads of groups they do not belong to. As such, as part of our authentication token, we figured we'd try sending along the user ID and the list of groups they belong to. Then, using the Firebase JSON security system, we could verify that the Firepad currently being edited is in the list of groups the user belongs to.
The problem is, the JSON system doesn't seem to accept many commands. There's no indexOf, and I can't call hasChild on the auth variable.
How can we ensure that users can only edit the Firepads of groups they belong to, without migrating all of our data to Firebase? (Or maintaining two copies of the database - one on MySQL and one on Firebase)

The trick here is to use an object instead of an array to store the groups (a tad awkward, I know. We'll try to make this easier / more intuitive). So in your auth token, you'd store something like:
{ userid: 'blah', groups: { 'group1': true, 'group2': true, ... } }
And then in your security rules you could have something like:
{
...
"$group": {
".read": "auth.groups[$group] == true",
".write": "auth.groups[$group] == true"
}
}
And then a user will have read/write access to /groups/<group> only if <group> is in their auth token.

Related

Security Rules: Storing unique usernames in one document

On my app I am trying to make it so that users have to have a unique username.
My current method is to have a Social Collection with one document called Usernames. That document will store the users userID as the key for the field and then their username for the value.
I am struggling to write the correct security rules for this. I would like it so that:
All signed-in users can get this document
Users can only update their own data in the document, formatted as [theirUserId: theirUsername]
There can be no duplicate usernames, e.g.
userIdA: "foo"
userIdB: "foo"
At the moment the only point that I can't get to work is checking to see whether a username is already taken.
Another solution I have thought of is to reverse the fields (username: userID). But I can't figure out a way how to write the security rules for this method either.
Current Rules
// Usernames
match /Social/Usernames {
allow get: if request.auth.uid != null;
allow update: if isUserNameAvailable();
}
// Functions
function isUserNameAvailable() {
// This line works
return (request.writeFields.hasOnly([request.auth.uid]))
// This one doesn't
&& !(resource.data.values().hasAny([request.writeFields[request.auth.uid]]));
}
Firestore Data Structure
Any help is greatly appreciated, many thanks!

Firebase - Database : How to restrict access to sub-levels of other trees according to users

I am a bit stuck on my project.
I have a list of users which include the events allowed.
I have a list of events with users allowed.
I want to allow a user to get only the list of their events in "users".
And of course, I want the json sent to only include the the content of their events.
See the Database
See the Rules
Thank you for your help
If I understand correctly, you want to allow a user access (read/write ?) to a specific part of the your database/events.
According to the Firebase Secure Your Data docs, you can specify who gets what.
If you want the current user to be able to see the data in an event, I think this rule should work :
{
"rules": {
"events": {// allows read to /events
"$eventID": {
".read": "data.child('users').child(auth.uid).exists()",
".write": false
}
}
}
}
If you want the user to be able to edit the information as well, copy the rule from .read to .write.
As for sending the JSON of this event specifically, you need to do it with a db query to that specific node in the DB.

In Firebase Firestore, is there any way to pass info to the Security Rules which is not part of the path?

I would like to send some info to Firestore database (Firebase), preferably in key-value pairs (but not necessarily), so that it can use it to evaluate access in their rules (both when reading and writing).
However, I don't want this info to be part of the path.
For example, suppose I had some passParameters method:
DocumentReference docRef =
db.collection("cities")
.document("SF")
.passParameters("abc", 123);
Then I could access this info when writing rules, like so:
service cloud.firestore {
match /databases/{database}/documents/cities/SF/ {
allow read, write: if request.parameters.abc == 123;
}
}
Please note, the above is just an example. Real-life uses cases are more complicated. In other words, don't pay too much attention to the example itself, but answer the more generic question: Is there any way to pass info to the Security Rules which is not part of the path?
You can send such parameters using custom tokens. Include those values as claims in the custom token, and use that token in your client when sending request to firestore (or signin).
This link explains how to-
1) create custom tokens, 2) include custom claims in those tokens, and 3) access those claims in the security rules.
You can have a cloud function to generate that custom token with custom claims for a specific user.
If the information you want to pass to firebase as parameter changes frequently, then this is going to be a cloud function call everytime you want to change the parameter value you are passing- so a bit costly. But if parameter tend to change less frequently (like- some role or special privilege that the user have), then this solution should work perfect and that's one of the primary benefits of custom token.
Even though it is not as simple as your example expectation snippet, still this I believe is one way to achieve what you want.
That's not supported. It wouldn't be a very "secure" security rule if the client could just specify whatever security parameters it wants with a query. That's really no different than allowing a client to pass a plaintext password that gives someone access to something. I would expect that sort of information to be discovered by an attacker.

Firebase - Adding properties to authenticated user [duplicate]

I'd like to add a property to a Firebase user object. The user documentation says that I can only store additional properties using the Firebase real time database.
I am unsure on how this can works in practice.
What does the following mean in practice?
You cannot add other properties to the Firebase User object directly;
instead, you can store the additional properties in your Firebase
Realtime Database.
I interpret it as following:
"you cannot modify properties of a FIRUser object but you can combine this with additional objects"
I found the set function documentation which I interpet in this way:
var userRef = ref.child("users");
userRef.set({
newfield: "value"
});
Is this a sensible approach?
You're almost there. In the legacy Firebase documentation, we had a section on storing such additional user data.
The key is to store the additional information under the user's uid:
let newUser = [
"provider": authData.provider,
"displayName": authData.providerData["displayName"] as? NSString as? String
]
// Create a child path with a key set to the uid underneath the "users" node
// This creates a URL path like the following:
// - https://<YOUR-FIREBASE-APP>.firebaseio.com/users/<uid>
ref.childByAppendingPath("users")
.childByAppendingPath(authData.uid).setValue(newUser)
I've added a note that we should add this information in the new documentation too. We just need to find a good spot for it.
According to the Custom Claims documentation,
The Firebase Admin SDK supports defining custom attributes on user accounts. [...] User roles can be defined for the following common cases:
Add an additional identifier on a user. For example, a Firebase user could map to a different UID in another system.
[...] Custom claims payload must not exceed 1000 bytes.
However, do this only for authentication-related user data, not for general profile information, per the Best Practices:
Custom claims are only used to provide access control. They are not designed to store additional data (such as profile and other custom data). While this may seem like a convenient mechanism to do so, it is strongly discouraged as these claims are stored in the ID token and could cause performance issues because all authenticated requests always contain a Firebase ID token corresponding to the signed in user.
Use custom claims to store data for controlling user access only. All other data should be stored separately via the real-time database or other server side storage.

Firebase - secure location to be targeted with query

Hello guys,
I started to work on securing my Firebase. I have a location containing a list of events :
events:
$event1: {
name: test
...
}
$event2: {
name: test2
...
}
In my app, I let the user add events using their name. For that, after the user validates his input (eventName), I'm looking if that name exists using
ref.child("events").orderByChild("name").equalTo(eventName)
And if so, I will add it to the list of user's events. My issue is with security. If I define security rules like so :
"events": {
"$eventId": {
".read": "auth != null"
}
}
Then it fails - it's completely expected since "events" location, which is targeted by the Firebase reference, has no "read" rule. The problem is, I don't want to open the "events" location for read by all connected users, since events are private and must be shared directly by their name.
So, since this name must be unique, I could use it as the event object key in place of the Firebase generated key (I currently use .push() when a new event is created to add it to the list, Firebase generates me the key), but I really like the way it is now. So I ask before changing : is there another way to reach what I'm aiming to do ?
Thanks a lot ahead.
The reason things don't work as expected here is that security rules are not filters. Rules are applied in an atomic manner. That means that a read or write operation is failed immediately if there isn't a rule at that location or at a parent location that grants access. Even if every child path is accessible, reading at the parent location will fail completely.
So, you would only be able to read from the /events/ node if you can read every child of that node. So if you are not logged in or you are not logged in with the correct user, the read will fail.
To accomplish what you want, you should add an /events/ node to each user in your /users/ node which just list the eventId for each event they are a part of. Then you don't need you event query and can instead just easily get a users events by doing a read at /users/$uid/events/. Then you can look up the event details from the /events/$eventId/ node and avoid any reads at the top-level /events/ node which is disallowed by security rules.

Resources