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.
Related
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;
}
};
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".
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 currently working on an app using firebase and angularJS (ionic). Basically this is a car management app, so you have people sharing their cars with others. I tried to structure the data as flat as possible to be efficient. My issue here is that if without problem I can display the list of the car_id of the different cars shared with the logged user, I can't find a way to display the list of cars shared with the user displaying the year and the model.
Thank you in advance for your help !
{
"rules": {
"users": {
".write": true,
"$uid": {
".read": "auth != null && auth.uid == $uid"
},
"cars": {
"car_id":true,
"role":true // Owner, borower...
}
},
"cars": {
"car_id":true,
"model":true,
"year":true
}
}
}
carapp.controller("carsController", function($scope, $firebaseObject, $ionicPopup, $ionicHistory) {
$ionicHistory.clearHistory();
$scope.list = function() {
frbAuth = frb.getAuth();
if(frbAuth) {
var userObject = $firebaseObject(frb.child("users/" + frbAuth.uid));
userObject.$bindTo($scope, "user");
$scope.cars = frb.child("cars");
}}
$scope.createCar = function() {
$ionicPopup.prompt({
model: 'Create a new car',
inputType: 'text'
})
.then(function(result) {
if(result !== "") {
var newCar = $scope.cars.push({
model: result
})
var newCarId = newCar.key();
$scope.user.cars.push({car_id: newCarId, role: "owner" });
} else {
console.log("Action not completed");
}
});
}
});
<div class="list">
<a ng-repeat="car in user.cars" >
<h2>{{car.car_id}}</h2> ----> works fine !
<h2>{{car.model}}</h2> ----> How to get this working ?
<h2>{{car.year}}</h2> ----> How to get this working ?
</a>
</div>
In the users/ path, begin by storing the list of cars by index, instead of in an array. So your structure would be:
{
"users": {
"kato": {
"cars": {
"DeLorean": true
}
}
},
"cars": {
"DeLorean": {
model: "DeLorean",
year: "1975"
}
}
}
To join this using AngularFire, you have several approaches available. An AngularFire-only solution might look like this, taking advantage of $extend:
app.factory('CarsByUser', function($firebaseArray) {
return $firebaseArray.$extend({
$$added: function(snap) {
return new Car(snap);
},
$$updated: function(snap) {
// nothing to do here; the value of the index is not used
},
$$removed: function(snap) {
this.$getRecord(snap.key()).destroy();
},
// these could be implemented in a manner consistent with the
// use case and above code, for simplicity, they are disabled here
$add: readOnly,
$save: readOnly
});
var carsRef = new Firebase(...).child('cars');
function Car(snap) {
// create a reference to the data for a specific car
this.$id = snap.key();
this.ref = carsRef.child(this.$id);
// listen for changes to the data
this.ref.on('value', this.updated, this);
}
Car.prototype.updated = function(snap) {
this.model = data.model;
this.year = data.year;
}
Car.prototype.destroy = function() {
this.ref.off('value', this.meta, this);
};
function readOnly() { throw new Error('This is a read only list'); }
});
app.controller('...', function($scope, CarsByUser, authData) {
// authenticate first, preferably with resolve
var ref = new Firebase(...).child(authData.uid);
$scope.cars = CarsByUser($scope);
});
For a more sophisticated and elegant approach, one could utilize NormalizedCollection and pass that ref into the AngularFire array:
app.controller('...', function($scope, $firebaseArray) {
var ref = new Firebase(...);
var nc = new Firebase.util.NormalizedCollection(
ref.child('users/' + authData.uid),
ref.child('cars')
)
.select('cars.model', 'cars.year')
.ref();
$scope.cars = $firebaseArray(nc);
});
Ok, so I'm working with AngularJS and Firebase and trying to create a simple exchange between two users. Right now my data structure is set up under "users/uId/" and then their email, date they joined, and gold.
Under gold (users/uId/gold) I have "sent" which captures the amount, time and to whom (email). This is the code snippet below. It also updates their total gold.
Now I'm stuck updating the person they're sending the gold to. I capture the email address, but everything under scope relates to the current logged in user. How would I update the new users users/uId/gold/received with the amount, time and email who it was from, along with updating their total gold?
I feel like I might be going about this the wrong way, any help would be appreciated, thanks!
ledger.controller('TransferController', function (
$scope, $firebase, $routeParams, $location, $rootScope, FIREBASE_URL) {
$scope.whichuser = $routeParams.uId;
$scope.goldsends = goldsendList;
var ref = new Firebase(FIREBASE_URL + '/users/' + $scope.whichuser + '/gold/' + '/sent/');
var hopperRef = new Firebase(FIREBASE_URL + '/users/' + $scope.whichuser + '/gold/');
var usersRef = ref.child("users");
var goldsendList = $firebase(ref).$asArray();
$scope.sendGold = function () {
var sendgoldObj = $firebase(ref); //this var has to match the sendgoldObj.$push var down below, and that's it
var myData = {
amount: $scope.user.amount,
email: $scope.user.email,
date: Firebase.ServerValue.TIMESTAMP
};
sendgoldObj.$push(myData).then(function () {
// $location.path('/myledger/'); //page redirect
}); //data sent to firebase.
if ($scope.currentUser.gold.total - Math.abs($scope.user.amount) > 0) { //
var hopperRefff = hopperRef.child("gold");
hopperRef.update({
"total": $scope.currentUser.gold.total - $scope.user.amount
}); //update total gold
var receive = new Firebase(FIREBASE_URL);
ref.child('users').orderByChild('email').equalTo(emailAddress).once('value', function (snap) {
console.log(snap.name() + (snap.val() === null ? ' DOES NOT' : ' does') + ' exist');
}); //trying to find user to send gold to
} //if user has enough gold statement
else {
return {
scope: {
errormessage: 'You don\'t have enough money',
}
};
console.log("not enough money!");
} //else note enough gold statement
} //sendgold
}); //TransferController
You could store the users by email where the # is replaced by _ and a . is replaced with -
So you have a JSON structure like this in Firebase
users: {
"bob_hoskins-com": {
email: "bob#hoskins.com",
date: "09-09-1999",
gold: {
...
}
}
}
However, I don't think this is a great approach for this problem.
I would create a node service that is observing a requests Firebase location on each user for added children. The node service will then do the calculations and write the data to the correct paths, then can delete the request once processed.
So you would have rules on your Firebase like this
{
"rules": {
"$userId": {
"requests": {
".read": "auth != null && $userId == auth.id",
".write": "auth != null && $userId == auth.id"
},
"responses": {
".read": "auth != null && $userId == auth.id",
".write": "auth != null && $userId == auth.id"
}
}
}
Here is some request code
var Firebase = require('firebase');
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
function guid() {
return s4() + s4() + s4() + s4();
}
var _ref = new Firebase('https://YOUR_FIREBASE.firebaseio.com/');
//Log me in
var guid = guid();
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator("YOUR_TOKEN");
var TOKEN = tokenGenerator.createToken({uid: guid, user: "node server"},{admin: true});
_ref.authWithCustomToken(TOKEN, function (error) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", guid);
}
});
_ref.on('child_added', function (user) {
var requests = user.ref().child('requests');
requests.on('child_added', function(req) {
handleRequest(req);
});
});
var handleRequest = function (request) {
// Process the request
// Write stuff back to Firebase
// Delete the request
request.ref().remove();
};
Setting a value to the user is just a variation of checking if that user exists. Once you have a snapshot, you can get back to a ref by calling ref
ref.child('users').orderByChild('email').equalTo(emailAddress).once('value', function (snap) {
snap.ref().update({ "total": snap.val().total + amount });
});
Not that this is just a sample, so you'll probably have to update it for your actual data structure.
Update
The above will get you the value of the users node.
You either need to capture the once('child_added' or forEach over the on('value'. I'll give an example of both.
Listening to child_added:
ref.child('users').orderByChild('email').equalTo(emailAddress).once('child_added', function (snap) {
snap.ref().update({ "total": snap.val().total + amount });
});
An example of looping over the value:
ref.child('users').orderByChild('email').equalTo(emailAddress).once('value', function (snap) {
snap.forEach(function(childsnap) {
childsnap.ref().update({ "total": snap.val().total + amount });
});
Here's a jsbin with both samples: http://jsbin.com/fenavu/1/edit?js,console. Note that the code here writes out the ref.toString(), which gives you the full URL of the node (since every piece of data in Firebase has its own unique URL). That can be a handy way to figure out what URL your node maps to.