I am trying to make a simple test of storing notes objects in Firebase, with user security rules that ensure the notes can be read and write only to the author.
Here is what the data stored in firebase looks like:
my_firebase_db
- notes
- K835Tw_H28XXb-Sj4b
- text: "note 1 from author 1",
- user_id: "11b09925-534b-4955-ac55-3e234809432f"
- K835Tw_H28XXb-Sj4b
- text: "note 1 from author 2",
- user_id: "11b09925-534b-4955-ac55-4d223893382c"
- K835Tw_H28XXb-Sj4b
- text: "note 2 from author 2",
- user_id: "11b09925-534b-4955-ac55-4d223893382c"
Angular code (AngularFire) that authenticates the user with a custom token, load notes and method to add a note:
var ref = new Firebase("https://xxx.firebaseio.com");
// Authenticate users with a custom authentication token
$firebaseAuth(ref).$authWithCustomToken(data.token).then(function(authData) {
console.log("Logged in as:", authData.uid);
$scope.user_id = authData.uid;
}).catch(function(error) {
console.error("Authentication failed:", error);
});
// Load notes
$scope.notes = $firebaseArray(ref.child('notes'));
$scope.addNote = function() {
note = {
user_id: $scope.user_id,
text: $scope.newNote.text
};
$scope.notes.$add(note);
};
Security & rules setup in Firebase:
{
"rules": {
"notes": {
".read": "auth != null && data.child('user_id').val() === auth.uid",
".write": "auth != null && newData.child('user_id').val() === auth.uid"
}
}
}
With these rules, read & write is not allowed.
If I change the rules to this, then read & write are allowed (but authors can read everybody's notes):
{
"rules": {
"notes": {
".read": "auth != null",
".write": "auth != null"
}
}
}
How can I write a security rule in firebase that will allow the authenticated user to read & write their own notes?
Turns out the answer was here: "Rules Are Not Filters".
Related
I have a bot which is supposed to create a channel under a specific category and then add two users to this channel.
The following bit of code is "supposed" to work and add user two, but fails with DiscordAPIError: Missing Permissions.
What I can't figure out is the actual permission required for this?
function addUserToChannel(ch, user, altCh) {
console.log(user.username,"sendmsg",ch.permissionsFor(client.user).has("MANAGE_CHANNELS", false)); //returns true
if (!ch.permissionsFor(user).has("SEND_MESSAGES", false)) {
console.log("User", user.username, "lacks view-channel permission");
ch.updateOverwrite(user, { //fails here obviously
SEND_MESSAGES: true,
VIEW_CHANNEL: true
});
} else {
console.log("WARN:", user.username, " already in channel ", ch.name);
altCh.send(user.toString() + " join conversation here: " + ch.toString());
}
}
The channel is created using the following code:
function createPrivateChannel(guild, convo, user) {
let everyoneRole = guild.roles.cache.find(r => r.name === "#everyone");
let parent = guild.channels.cache.find(ch => {
//console.log(ch.id,secret.convoCat.id);
return ch.id == secret.convoCat.id;
});
return guild.channels.create(convo.chName, {
type: "text",
parent: parent,
permissionOverwrites: [
{
id: everyoneRole.id, //private channel
deny: ["VIEW_CHANNEL", "SEND_MESSAGES"]
},
{
id: client.user.id, //bot permissions
allow: [ "VIEW_CHANNEL", "SEND_MESSAGES", "READ_MESSAGE_HISTORY", "MANAGE_CHANNELS" ]
},
{
id: user.user.id, //a different user
allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "READ_MESSAGE_HISTORY"]
}
]
});
}
According to the Discord documentation for the 'Edit Channel Permissions' route (which is what updateOverwrite ultimately uses):
Requires the MANAGE_ROLES permission. Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel).
When using the angularFire $authWithPassword method in my app I am receiving a Permission_Denied from firebase. I'm very new to the Firebase Security rules and the mistake lies somewhere in how I've set them up (as it works fine when read and write are true globally). What I'm trying to do is disallow users from deleting the major collections of my architecture but allow them to CRUD child items.
So I have gifts, events, persons, and users. And my rules look like this:
{
"rules": {
".read": true,
"events": {
"$id": {
".write": true
}
},
"gifts": {
"$id": {
".write": true
}
},
"person": {
".indexOn": "created_by",
"$id": {
".write": true
}
},
"user": {
"$id": {
".write": true
}
}
}
}
As I said, there are no issues when using the default rules of:
{
"rules": {
".read": true,
".write": true,
"person": {
".indexOn": "created_by"
}
}
}
What am I doing wrong?
EDIT:
It is not the $authWithPassword doing the write operation, it is $createUser where I create the user and in the .then I am doing a 'CreateProfile' function which uses ref.set to update properties to /users/uid.
$scope.createAccount = function(email, pass, name, confirm) {
loaderSvc.toggleOn('Creating account...');
$scope.err = null;
if( !pass ) {
$scope.err = 'Please enter a password';
}
else if ( !name ) {
$scope.err = 'Please enter a display name';
}
else if( pass !== confirm ) {
$scope.err = 'Passwords do not match';
}
else {
Auth.$createUser({email: email, password: pass})
.then(function () {
// authenticate so we have permission to write to Firebase
return Auth.$authWithPassword({email: email, password: pass}, {rememberMe: true});
})
.then(createProfile)
.then(redirect, showError);
}
function createProfile(user) {
var ref = Ref.child('users/' + user.uid), def = $q.defer();
ref.set({email: email, name: name}, function(err) {
$timeout(function() {
if( err ) {
def.reject(err);
}
else {
sendWelcomeEmail(email, name);
toastr.success('Account created');
Analytics.trackEvent('profile', 'account created', user.uid);
def.resolve(ref);
}
});
});
return def.promise;
}
};
This is my model on firebase :
Texts
-KAa-aU_RjZwM7FLQcWt
id: "5f9fe424-4323-4370-a280-9cb216dc6410"
text: "gregegreg"
-KAa-bZC2ouIRQ54YWWr
id: "5f9fe424-4323-4370-a280-9cb216dc6410"
text: "gregegreg"
And these are my rules :
"rules": {
"Texts": {
".read": "auth.id === data.child('id').val()",
".write": true
}
}
Unfortunatly, i'm still able to read All of my model TEXTS while i'm logged with another UID than 5f9fe424-4323-4370-a280-9cb216dc6410
Any idea appreciated thank you !
The authenticating part works well with angularJs and login-password :
var firebaseObj = new Firebase("https://blinding-heat-xxxx.firebaseio.com");
$scope.authObj = $firebaseAuth(firebaseObj);
$scope.login = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$authWithPassword({
email: $scope.email,
password: $scope.password
}).then(function(authData) {
console.log("Logged with id:", authData.uid);
$scope.loadData();
}).catch(function(error) {
console.error("Auth Failed :", error);
$scope.error = error;
});
};
Data is finally loaded on the web page like that :
$scope.loadData = function(){
var ref = new Firebase("https://blinding-heat-xxxx.firebaseio.com");
$scope.Texts = $firebaseArray(ref.child('Texts'));
}
And into the HTML , the angularJs is there :
<div ng-repeat="text in Texts">text.text</div>
What is really strange is that the following rule works very well, for example :
"rules": {
"Texts": {
".read": " auth != null ",
".write": true
}
}
Almost there! It's actually auth.uid not auth.id.
Also, you need to provide a wildcard, a $ variable, that applies the rule to any child below /Texts.
"rules": {
"Texts": {
"$text_id": {
".read": "auth.uid === data.child('id').val()",
".write": true
}
}
}
Without the wildcard, the rule is applied to the static path of /Texts.
Here's the output in the Simulator.
I'm using firebase via angularFire. Can anyone help me figure out how the best way to write a new node to /users but then prevent anyone with a different id from writing?
Here's my unfortunate security rules. Was thinking if the data doesn't exist AND there's new data present, that would cover this use case. it doesn't.
{
"rules": {
".read": true,
"users": {
".write": "!data.exists() && newData.exists()",
"$user": {
".write": "auth.uid == $user"
}
}
}
}
Here's the register method that's creating a user and then updating the db:
register: function(obj){
var authRef = new fb(fbRef);
var authObj = $firebaseAuth(authRef);
authObj.$createUser({
email: obj.email,
password: obj.pass
}).then(function(userData) {
console.log("User " + userData.uid + " created successfully!");
return authObj.$authWithPassword({
email: obj.email,
password: obj.pass
});
}).then(function(authData) {
var fbObjRef = authRef.child('users');
var fbObj = $firebaseObject(fbObjRef).$loaded().then(function(data){
obj.pass = null;
data[authData.uid] = obj;
data.$save();
});
console.log("Logged in as:", authData.uid);
}).catch(function(error) {
console.error("Error: ", error);
});
}
Somewhere in the threads was mentioned that there's a bug with angularFire but that thread was from 2013. I recall coming across this use case before but cannot find the post. Security rules are the scariest part. Thank you for helping!
I'm open to learning a better way but here's a solution. Basically, I'm able to check if the user's id exists, if not then write is allowed. It's probably not the safest method so feel free to point out my misunderstanding or proper validating :)
{
"rules": {
".read": true,
// ".write": true,
"users": {
".write": "(auth !== null && !root.child('users/'+auth.uid).exists())",
"$user": {
".write": "auth.uid == $user"
}
}
}
}
I am pretty confused about the Firebase security rules - about WORK FROM THE TOP-DOWN as the docs.
So I have this rule:
"rules": {
"taskUsers": {
".read": "auth != null",
"$uid": {
".read": "auth != null",
".write": "auth != null && auth.uid == $uid",
},
},
}
So all the user data can only be updated by the user who created them b/c of auth.uid == $uid. But under the firebasepath/taskUsers/$uid, I have a value called notification that I want other users can able to write. Like in a chat notification system - when other users contact this user, the "other users" can change / write the value notification (like +1). But with the above rule, "other users" when they post the contact, it will return permission denied. So if I do the rule like
"rules": {
"taskUsers": {
".read": "auth != null",
"$uid": {
".read": "auth != null",
".write": "auth != null && auth.uid == $uid",
"notification": {
".write": "auth != null",
},
},
},
}
The rule got ignored...So how can I create a rule just allow notification value under the taskUsers/$uid to be written by everyone who login?
I have not tested it but this should point you in the right direction at the very least. Their rules get kind of tricky... I still maintain that one should have a WebService in front of Firebase for any data manipulation. How else does one implement non-trivial business logic?
"rules": {
"taskUsers": {
".read": "auth != null",
"$uid": {
// authenticated users can write as long as they have a notification property
".write": "auth != null && (auth.uid == $uid || newData.hasChild('notification'))",
"notification": {
// Bonus rule: only increment the number by 1 or -1
".validate": "newData.isNumber() && ((!data.exists() && newData.val() === 1 || newData.val() === -1) || newData.val() === data.val()+1 || newData.val() === data.val()-1)",
}
},
},
}