So I'm have 3 collections
1- cases
3- owner (inside cases collection)
2- invitations
in normal senario when user add invitation I check if user that tray this action is owner or not by , take caseId from newDate and uid from auth.uid and compare these data by exist in cases collection
but it's always give me permission denied.
// Any one have cases
"cases":{
".write" : "auth.uid != null"
// Store id of owner only when case created first time
"owner" : {
".write": "!data.exists()"
}
}
//only Owner of case can add invitations
"invitations":{
".write" :"root.child('cases').child(newData.child('caseId').val()).child('owner').child(auth.uid).exists()",
},
the data that I'm sending to invitations collection are
Invitation(
endAt: DateTime.now().add(Duration(days: 1)),
id: invitationId,
caseId: caseId,
);
Edit
When I try replace newData.child('caseId').val() with caseId value as static string value , it's working !!
For whom faced this issue , I was wrong due to I used newDate in parent not child that I will write so i put my rules under "$invitationId" and its' working.
//only Owner of case can add invitations
"invitations":{
"$invitationId":{
".write" :"root.child('cases').child(newData.child('caseId').val()).child('owner').child(auth.uid).exists()",
}
},
Related
Trying to put my question in its general concept so that your reply will stay relevant to all the ones who are stuck firebase rules for a chat app.
My chat database is a simple one and has 3 nodes: Messages / Connections / Users as below:
Database
-Messages
--message :
--$uid
...
-Connections
--Connection1
--Connection2
--ConnectionX
...
-Users
--UID1
--UID2
Here's what I need to proofread pls for my realtime database rules:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null" && "$uid/current_page" == "3w_stringofmywebsite/*",
".write": "auth != null" && "$uid/current_page" == "3w_stringofmywebsite/*"
}
// .read & .write to authenticated users & variable $uid/current_page= "3w_stringofmywebsite/*" (allow access from requests from all pages from my web site only)
}
"connections": {
"$uid": {
".read": "auth != null" && root.child(‘users’).child($uid).child(“current_page ") == "
3 w_stringofmywebsite/*"
}
// .read to authenticated users & variable from another path “users/$uid/current_page” = "3w_stringofmywebsite/*" (allow access from from all pages from my web site only)
}
"messages": {
".read" : "auth != null" && root.child(‘users’).child($uid).child(“current_page") == "3w_stringofmywebsite/*" ,
".validate": "auth!=null" && data.child('msg').val ().length <= 140 && data.child('msg').isString"
}
// .read to authenticated users & variable from another path “users/$uid/current_page” = "3w_stringofmywebsite/*" (allow access from all pages from my web site only)
// .write to authenticated users & variable “msg” is a string & < 140 characters
}
Thank you for your generous correction to the code above so that it serves all other newcomers.
Thank you dear Frank,
the syntax of commands has many errors (tried many times to do the corrections but the list is long) and I don't want to fill the ticket here with beginners mistakes.
=> what I need is someone who masters the syntax and relieve me from bumping my head at each step in each command!
*For the "users" node:
Read: right to only authenticated connections and the ones that are coming from my website only (as the chat tool is on this website)
Write: right similar to above.
For the "connections" node:
Read to authenticated users & the connections that are coming from my website [there's a variable "current_page" from another path “users/ "ABC"/current_page” = "3w_stringofmywebsite/" that I can use to match the string of my website as a test
NB: "ABC" above is a string auto generated by the node and I don't know how I can point to the variable "current_page" below it since it keeps varying from one node entry to another!
*For the "messages" node:
Read: privilege to authenticated and again check if the variable “users/ "ABC"/current_page” is from my web site
Write: privilege to authenticated and the message is a string < 140 characters.
Appreciate your support in this.
Thank you.
I'm a bit confused about firebase rules. This is my realtime database. Each node inside "1" is created using the firebase unique id of the user. And in the user's node there is a list of objects.
The objective is for the user to be able to create this node if it doesn't exist, and allow the user to read/write only inside this node.
I tried this but it doesn't work. I get permission error.
{
"rules": {
"1": {
"$key": {
".read": "auth != null && auth.uid == $key",
".write": "auth != null && auth.uid == $key"
}
}
}
}
Note: In the future there will be other parent nodes ("2","3" etc) So it is important to keep the "1". Also in case it matters I am using firebase anonymous sign in.
I appreciate the help.
UPDATE:
I retrieve the installationId like this:
Task<String> getIdTask = FirebaseInstallations.getInstance().getId()
and access the database like this:
FirebaseDatabase.getInstance()
.getReference()
.child("1")
.child(installationId)
Trying to access the database using above code gives this:
Listen at /1/cKYZwWrlRmSof79rtfuX82 failed: DatabaseError: Permission denied
SOLUTION:
I just realized the magnitude of my mistake. To retrieve the userId I was using
FirebaseInstallations.getInstance().getId()
instead of this which is what firebase sees as userId:
FirebaseAuth.getInstance().getCurrentUser().getUid();
Using the later one solved the issue.
Unless you'd add another node, that actually links the UID with your user ID, how shall it know about it? I'd suggest to reconsider the structure and get rid of that superfluous node altogether; just use the UID. It's not that it wouldn't be possible, to lookup values by UID ... but it might be an unfortunate database design, which ignores the given environment.
I have a small, personal Firebase webapp that uses Firebase Database. I want to secure (lock down) this app to any user from a single, specific domain. I want to authenticate with Google. I'm not clear how to configure the rules to say "only users from a single, specific domain (say #foobar.com) can read and write to this database".
(Part of the issue that I see: it's hard to bootstrap a Database with enough info to make this use case work. I need to know the user's email at the time of authentication, but auth object doesn't contain email. It seems to be a chicken-egg problem, because I need to write Firebase rules that refer to data in the Database, but that data doesn't exist yet because my user can't write to the database.)
If auth had email, then I could write the rules easily.
Thanks in advance!
If you're using the new Firebase this is now possible, since the email is available in the security rules.
In the security rules you can access both the email address and whether it is verified, which makes some great use-cases possible. With these rules for example only an authenticated, verified gmail user can write their profile:
{
"rules": {
".read": "auth != null",
"gmailUsers": {
"$uid": {
".write": "auth.token.email_verified == true &&
auth.token.email.matches(/.*#gmail.com$/)"
}
}
}
}
You can enter these rules in the Firebase Database console of your project.
Here is code working fine with my database , I have set rule that only my company emails can read and write data of my firebase database .
{
"rules": {
".read": "auth.token.email.matches(/.*#yourcompany.com$/)",
".write": "auth.token.email.matches(/.*#yourcompany.com$/)"
}
}
Code which is working for me.
export class AuthenticationService {
user: Observable<firebase.User>;
constructor(public afAuth: AngularFireAuth) {
this.user = afAuth.authState;
}
login(){
var provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({'hd': '<your domain>'});
this.afAuth.auth.signInWithPopup(provider)
.then(response => {
let token = response.credential.accessToken;
//Your code. Token is now available.
})
}
}
WARNING: do not trust this answer. Just here for discussion.
tldr: I don't think it's possible, without running your own server.
Here's my attempt thus far:
{
"rules": {
".read": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('#foobar.com')",
".write": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('#foobar.com')",
"users": {
"$user_id": {
".write": "auth.provider === 'google' && $user_id === auth.uid && newData.child('email').val().endsWith('#foobar.com')"
}
}
}
}
I believe the above says "only allow people to create a new user if they are authenticated by Google, are trying to write into the database node for themselve ($user_id === auth.uid) and their email ends in foobar.com".
However, a problem was pointed out: any web client can easily change their email (using the dev console) before the message is sent to Firebase. So we can't trust the user entry's data when stored into Firebase.
I think the only thing we can actually trust is the auth object in the rules. That auth object is populated by Firebase's backend. And, unfortunately, the auth object does not include the email address.
For the record, I am inserting my user this way:
function authDataCallback(authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider + " and has displayName " + authData.google.displayName);
// save the user's profile into the database so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData),
email: authData.google.email
});
As you might be able to imagine, a determined user could overwrite the value of email here (by using the DevTools, for examples).
This should work for anyone looking for a Cloud Firestore option, inspired by Frank van Puffelen's answer.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// Allows all users to access data if they're signed into the app with an email of the domain "company.com"
allow read, write: if request.auth.uid != null && request.auth.token.email.matches(".*#company.com$");
}
}
}
For anyone really not wanting to have unverified accounts logging in. Maybe dirty, but very effective.
This is my workaround (Angular app):
this.userService.login(this.email.value, this.password.value).then(data => {
if (data.user.emailVerified === true) {
//user is allowed
} else {
//user not allowed, log them out immediatly
this.userService.logout();
}
}).catch(error => console.log(error));
$id The key where this record is stored. The same as obj.$ref().key
To get the id of an item in a $firebaseArray within ng-repeat, call $id on that item.
These two are from the angular fire reference:
https://github.com/firebase/angularfire/blob/master/docs/reference.md
What I understand is if there is firebase object created with :
var object = $firebaseObject(objectRef);
then I can use uid always.
uid : object.uid
But I saw examples where the firebase auth user is used with $id.
return Auth.$requireSignIn().then(function (firebaseUser) {
return Users.getProfile(firebaseUser.uid).$loaded().then(function (profile) {
**profile.uid or profile.$id here**
Also is it possible the object to have uid but not to have $id (obj.$ref().key). Aren't they the same thing? Does the object have to be loaded first with $loaded() to use $id or uid?
Regards
You seem to be confusing two concepts:
the object.$id of an AngularFire object contains the key of that object in the Firebase Database.
a firebaseUser.uid in Firebase terms is the identification of a Firebase Authentication user.
It is common to store your Firebase Authentication users in the database under their uid, in which case user.$id would be their uid. But they are still inherently different things.
Users
uid1
displayName: "makkasi"
uid2
displayName: "Frank van Puffelen"
So if you we look at the code snippet you shared:
return Auth.$requireSignIn().then(function (firebaseUser) {
return Users.getProfile(firebaseUser.uid).$loaded().then(function (profile) {
The first line requires that the user is signed-in; only then will it execute the next line with the firebaseUser that was signed in. This is a regular JavaScript object (firebase.User), not an AngularFire $firebaseObject.
The second line then uses the firebaseUser.uid property (the identification of that user) to load the user's profile from the database into an AngularFire $firebaseObject. Once that profile is loaded, it executes the third line.
If the users are stored in the database under their uid, at this stage profile.$id and firebaseUser.uid will be the same value.
In my main page I have a list of users and i'd like to choose and open a channel to chat with one of them.
I am thinking if use the id is the best way and control an access of a channel like USERID1-USERID2.
But of course, user 2 can open the same channel too, so I'd like to find something more easy to control.
Please, if you want to help me, give me an example in javascript using a firebase url/array.
Thank you!
A common way to handle such 1:1 chat rooms is to generate the room URL based on the user ids. As you already mention, a problem with this is that either user can initiate the chat and in both cases they should end up in the same room.
You can solve this by ordering the user ids lexicographically in the compound key. For example with user names, instead of ids:
var user1 = "Frank"; // UID of user 1
var user2 = "Eusthace"; // UID of user 2
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
user1 = "Eusthace";
user2 = "Frank";
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
A common follow-up questions seems to be how to show a list of chat rooms for the current user. The above code does not address that. As is common in NoSQL databases, you need to augment your data model to allow this use-case. If you want to show a list of chat rooms for the current user, you should model your data to allow that. The easiest way to do this is to add a list of chat rooms for each user to the data model:
"userChatrooms" : {
"Frank" : {
"Eusthace_Frank": true
},
"Eusthace" : {
"Eusthace_Frank": true
}
}
If you're worried about the length of the keys, you can consider using a hash codes of the combined UIDs instead of the full UIDs.
This last JSON structure above then also helps to secure access to the room, as you can write your security rules to only allow users access for whom the room is listed under their userChatrooms node:
{
"rules": {
"chatrooms": {
"$chatroomid": {
".read": "
root.child('userChatrooms').child(auth.uid).child(chatroomid).exists()
"
}
}
}
}
In a typical database schema each Channel / ChatGroup has its own node with unique $key (created by Firebase). It shouldn't matter which user opened the channel first but once the node (& corresponding $key) is created, you can just use that as channel id.
Hashing / MD5 strategy of course is other way to do it but then you also have to store that "route" info as well as $key on the same node - which is duplication IMO (unless Im missing something).
We decided on hashing users uid's, which means you can look up any existing conversation,if you know the other persons uid.
Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.
Hashing with js-sha256 module worked for me with directions of Frank van Puffelen and Eduard.
import SHA256 from 'crypto-js/sha256'
let agentId = 312
let userId = 567
let chatHash = SHA256('agent:' + agentId + '_user:' + userId)