Firebase Database - Share Data When UID Unknown? - database

In Firebase Database; If user_a has data they can access and they want to share this data with user_b, what is the best practice and database structure for securely sharing this data between specific users?
Important: user_a doesn't have any information about user_b account, e.g. uid.
Detailed Example:
1) user_a has a list of clientele.
"users": {
"user_a": {
"clients": [
"clientUid",
"clientUid2"
]
}
},
"clients": {
"clientUid": {
"firstName": "John",
"lastName": "Doe"
},
"clientUid2": {
"firstName": "Joe",
"lastName": "Bloggs"
}
}
2) user_b signs up. user_a now wants to share user_b data in clients with the user_b account that signed up.
Put another way: user_a has a list of clients, one of them creates an account and needs to link to the information user_a has already entered for them.
An important element here is that there is no list of users or accounts that a 'friend request' can be made from for more data. I have experimented with creating a short unique id a user can enter when they sign up to access their data, but am unsure if this is a good way forward and have encountered issues.
This is different to previously asked 'shared data' questions as it focuses on, how is the link between the two users made? Not just the before and after.

I know if two ways:
Either the users must both know a certain value, known as a shared secret.
Or you allow the users to search for each other.
Shared secret
A shared secret can be easily modeled in the database as a location that you can read once you know it. Your short unique id is an example of such a shared secret, where user_a "determines" the secret, and then sends it to user_b out of band.
A simple flow:
user_a clicks a Get secret button in the app
The app determines a secret code, e.g. correct-horse-battery-staple and:
2a. Writes the secret code to the database in a non-queryable-but-readable location, together with the actual information to share, in your case the UID of user_a.
2b. Shows the secret code to user_a.
user_a copies the code and sends it to user_b via some other communication mechanism, e.g. text, email, chat.
user_b clicks Enter secret button in the app, and pastes the secret.
The app reads the location, and is able to read the UID of user_a.
The data structure for this could be something like:
"secrets": {
"correct-horse-battery-staple": "uidOfUserA"
}
Which you secure with these rules:
{
"rules": {
"secrets": {
"$secret": {
".read": true
}
}
}
}
So the above rules don't allow anyone to read /secrets, hence they can't get a list of all secrets. But once they know a secret, they can look up the associated data under it.
Allowing search
The alternative is to have a list of users in your database, and allow user_b to search on some property that they know from user_a. A common property might be their email address, or their display name, which you could store in the database like:
users: {
"uidOfUserA": {
email: "userA#domain.com",
displayName: "user A"
},
"uidOfUserB": {
email: "userB#anotherdomain.com",
displayName: "user B"
}
}
To allow users to search each other is a bit more involved, as in: it will require server-side code. The reason for this is that being able to search over a dataset requires that you can read that dataset. And in Firebase, if you can read a dataset, you can get all data under it. In other words, to allow search a user you need to be able to read all of /users and allowing this would disclose too much information.
So to implement a search securely, you'll need to have rules like this:
{
"rules": {
"users": {
"$uid": {
".read": true,
".write": "auth.uid === $uid"
}
}
}
}
In words: anyone can read the profile for a user for whom they know the UID, but each individual user can only modify their own profile.
With this, you can implement a Cloud Function that then takes the email or display name as input, and searches across all /users. Since Cloud Functions access the database with administrative privileges, they are not restricted by the above security rules.

Related

Adding extra information to registration user Firebase

I would like to make an application in React Native that allows to work in two modes, parent and child. The initial stage is registration with Firebase, then after adding additional information about the role (parent / child), registering both the child and parent, and after logging in both of them, the child will share location and parent will receive it.
I would like to add additional fields such as role (parent / child) in my application in React Native + Firebase, to later create other functionalities of the application based on the role.
Registration:
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(userCredentials => {
return userCredentials
.user.updateProfile({
displayName: name,
})
.additionalUserInfo.profile = {
role: role,
}
})
Homescreen
const { displayName } = firebase.auth().currentUser;
const { role } = firebase.additionalUserInfo.profile;
this.setState({displayName, role});
and role returns undefined.
The properties that you can store on a user profile are defined by Firebase Authentication. You can't just add additional properties to it as you see fit. At best Firebase will simply ignore those, but likely it will also explicitly reject them (throwing an error).
If you want to store additional information about a user, you have two main options:
Store the additional information as custom claims in the user's authentication token.
Store the additional information in an external database, such as the Firestore and Realtime Database that are part of Firebase.
While storing the data as custom claims is pretty close to what you want to accomplish, you'll need to keep a few things in mind:
Custom claims are used for authorization purposes, and for that reason can only be set from a trusted environment (such as your development machine, a server you control, or Cloud Functions). So you can't simply set the role from within the app, and will need a separate process to add that claim.
After setting a custom claim on a user profile it may take up to an hour before that change is visible in the client. If you need it sooner, you can force the user to sign in again, or refresh their ID token.
Custom claims are sent with every request you make to Firebase resources, and for that reason are very limited in size. There's a maximum size of 1000 bytes for custom claims for a user. While your current role will easily fit into that, it may limit what you can add later.
If instead you store user data in an external database, you'll typically combine it with other information about that user into a users node/collection. In here you'd store a document/node for each user based on that user's UID, and then their profile information.
So something like:
users: {
uidOfAleksandra: {
username: "Aleksandra",
displayName: "Aleksandra Lastname",
role: "parent",
registrationDate: "2020-02-01"
},
uidOfPuf: {
username: "puf",
displayName: "Frank van Puffelen",
role: "child",
registrationDate: "2015-03-07"
},
}
Having this list of user profiles does not only allow you to store the additional information for each user, but would also allow you to query that list of users from within your app, something that the Authentication API doesn't allow from within application code.

Can't add 'otherMails' with Graph-Tester

I am trying to add another email address to a User.
PATCH https://graph.microsoft.com/v1.0/users/user#domain.de
Body:
{
"givenName":"Meier",
"surname":"Meeier",
"otherMails":["emaissssl#domain.de"]
}
Response: Success - Statuscode 204
The result is givenName changed to Meier, surname changed to Meeier, but the email doesn't get added to otherMails[].
A related question is, can I change the primary address of the User?
Edit: I get the same behavior, if i also include the current address:
{
"givenName": "Meier",
"surname": "Meeier",
"otherMails": ["user#domain.de", "emaissssl#domain.de"]
}
You need spesific permissions in AAD to update otherMails. From the documentation:
Updating another user's businessPhones, mobilePhone, or otherMails property is only allowed on users who are non-administrators or assigned one of the following roles: Directory Readers, Guest Inviter, Message Center Reader, and Reports Reader. For more details, see Helpdesk (Password) Administrator in Azure AD available roles. This is the case for apps granted either the User.ReadWrite.All or Directory.ReadWrite.All delegated or application permissions.
The wording is a little poor but basically, if the User record you are updating is an Administrator or assigned any of the mentioned roles (Directory Readers, Guest Inviter, Message Center Reader, or Reports Reader), it will ignore the change request.
As for changing the primary email address, that isn't possible. The primary email address is automatically constructed based on the mailNickname and the default domain for the tenant (mailNickname#default.dom).

Get email address of logged-in user if multiple emails are associated with same account

Often, users associates multiple email addresses with the same account. But question is, is there any way to know using which email user has logged in into the system?
Note : We are strictly using email for log in and not username.
Mongodb users emails array structure:
"emails" : [
{
"address" : "xyz#abc.com",
"verified" : true
},
{
"address" : "prq#abc.com",
"verified" : true
},
{
"address" : "jkl#abc.com",
"verified" : true
}
],
Problem statement -
One need to order X item, while putting order in system we need email address of logged in user to save in this particular order. So that one can receive notifications related to this particular order.
If we save email address of logged in user to custom database field while logging in it may work but issue is if another user owning same account logs in then second users email will be updated to database and his/her email will get save in order placed by user 1.
Thanks in advance.
The best way to achieve this is to store the last email used for each user while logging in in a custom field in the user model.
Using the Account.onLogin hook on the server side or your custom login form you can have the user's last email used. Then you should save this email address for each user in a custom field such as user.lastEmail.
If you do so and need then the lastEmail on the user side, do not forget to publish the custom field to your client such as described in the documentation.

Firebase Authentication (multiple types of users)

i am working on a react.js project which authenticate from firebase but i have 3 multiple type of user's in it (super Admin,vendor admin,vendor staff) with different privileges. how can i authenticate them from firebase and get to know this is my venor admin or vendor staff etc ???? because firebase just authenticate single type of user!
You can control access via custom claims using the Firebase Admin SDK on your own server of through Cloud Functions for Firebase.
Set claims server side for a specific bid with a method like this:
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
Then set up your database with a structure that separates admin and vendor admin content:
/
/admin
/data for admins here
/vendorAdmin
/ data for vendor admins here
/staff
// data for staff here, or perhaps this data is accessible to all since the admins may need access to it.
In the Firebase Console, customize the rules to restrict these locations to those who include the proper claim:
{
"rules": {
"admin": {
".read": "auth.token.admin === true",
".write": "auth.token.admin === true",
}
"vendorAdmin": {
".read": "auth.token.vendoradmin === true",
".write": "auth.token.vendoradmin === true",
}
"staff": {
".read": "auth.token.staff === true",
".write": "auth.token.staff === true",
}
}
}
This is a simplified example, so you'll have to customize it further to meet the needs of your app.
You can maintain a users table in your database, and every time you sign up a user just add them there as well, using the uid.

Securing system-generated nodes in firebase

I've been going through the rules guide but haven't found an answer to this.
App users are able to submit "scores" of different types, which are then processed in JS and written to a "ranking" node. I have it set up so that every time a new score is submitted, the rankings are automatically recalculated and a new child is written if the user doesn't exist or updated if the user exists.
My question is how to secure this "ranking" node. Everyone should be able to read it, nobody except the system should be able to write it. This would prevent people from submitting their own rankings and aggregate scores.
EDIT
This is the operation:
Ref.child('rankings').child(uid).once('value', function (snapshot) {
if (snapshot.exists()) {
snapshot.ref().update(user); //user object created upstream
} else {
var payload = {};
payload[uid] = user;
snapshot.ref().parent().update(payload);
}
});
How would I add custom authentication to this call? Also, since I'm using AngularJS, is there any way to hide this custom token or would I have to route it through a backend server?
The key part of your problem definition is:
only the system should be able to write it.
This requires that you are able to recognize "the system" in your security rules. Since Firebase security is user-based, you'll have to make your "system" into a user. You can do this by either recording the uid from a regular user account or by minting a custom token for your "system".
Once you have that, the security for your ranking node becomes:
".read": true,
".write": "auth.uid == 'thesystem'"
In the above I assume you mint a custom token and specify thesystem as the uid.

Resources