After seeming to bang my head on a wall for what seems to be a simple problem I thought it best to ask for some help.
I am creating an EmberFire application that allows users to authenticate using the simple login. Once authenticated the user can store particular items for later retrieval.
I have models defined as so:
USER:
export default DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
dateOfBirth: DS.attr('date'),
registeredAt: DS.attr('date'),
compentencies: DS.hasMany('competency', { async: true })
});
COMPETENCY:
export default DS.Model.extend({
title: DS.attr('string'),
endDate: DS.attr('date'),
user: DS.belongsTo('user', { async: true })
});
These are stored in the firebase db in a denormalised for as I would like them to be.
My issue arises on retrieval of persisted data. I am not sure how I am supposed to lock the competencies down to a particular user?
The way the rules cascade in FireBase I am not sure if this is even possible in this form but it does not seem right that I would have to store each competency in an embedded form under each user node.
I know that I could just allow read access to all 'Competency' models with this.store.find('competency') and then filter them on the client but that does not fit the security I would expect.
Any help would be great thanks.
Ryan
To only allow read access to a competency if the competency belongs to the user who is currently logged in you can use $competencyId (you can replace "competencyId" with any valid string). Using $competencyId limits access to a specific node under competencies/ instead of allowing access to the entire tree.
"rules": {
"competencies": {
"$competencyId": {
".read": "data.child('user').val() === auth.uid"
}
}
}
Related
I'm having issues implementing basic security rules with Firebase (I read documentation on Firebase and StackExchange but cannot make security rules work):
The model (Emberjs representation of the model):
App.User = DS.Model.extend({
uid: DS.attr('string'),
displayName: DS.attr('string'),
books: DS.hasMany('statistic', { inverse: 'user', async: true}),
actions: DS.hasMany('action', { inverse: 'user', async: true}),
});
App.Action = DS.Model.extend({
date: DS.attr('date'),
actionType: DS.attr('string'),
comment: DS.attr('string'),
user: DS.belongsTo('user', {inverse: 'actions', async: true} )
});
App.Book = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
user: DS.belongsTo('user', { inverse: 'books', async: true} )
});
The 3 nodes (models) are stored directly in the root of the Firebase app. The Book and Action models have a user field (property).
What are the rules to writes so that:
Only the user identified in the user field of the Book and Action models (nodes) can have read and write access to their own data? (The value of the user field in Book and Action must be equal to the value of auth.uid in Firebase for the user to be granted the read and write privileges.)
That the users can only access the information of the User model (node) that pertain to them?
Thanks
It is important to understand the structure of data in the Firebase.
Basically, there are two ways to write security rules. You either set up security rules right under books/ or you write security rules for each model attribute separately. Or a combination of both but make sure you understand the top-down principle first.
I prefer to write rules for each attribute separately, it is more easily maintainable and testable.
But in your case, because other users don't need to acces some part of books or users it is easy to write the rules for the whole model:
"rules" :{
"books": {
"$book_id": {
".read": "data.child('user').val() === auth.uid && auth !== null",
".write": "!data.exists() && newData.child('user').val() === auth.uid || data.child('user').val() === newData.child('uid').val() && auth !== null"
},
"users": {
"$user_id": {
".read": "data.child('uid') === auth.uid",
".write": "!data.exists() && newData.child('uid').val() === auth.uid || data.child('uid').val() === newData.child('uid').val()"
}
}
}
}
I did not test these rules, they can contain flaws, please use the simulator tool to make them bulletproof :]
Check my medium post for more info: https://medium.com/#martinmalinda/emberfire-is-awesome-but-querying-data-and-writing-security-rules-can-be-a-pain-f5370f4decb
I am trying to serialize a user object and pass it to an ember client. My app uses the RESTserializer.
A user object has_one address object. There is no separate endpoint for just the address so I can't just provide a foreign key id and sideload it.
The JSON received just includes address as an object and looks something like this:
{"user":
{
"id":"5",
"name":"Andrew",
"address":
{
"id":"3",
"addressable_id":"5",
"street":"1",
"country_code":"US"
}
}
On the ember side I have a user model
App.User = DS.Model.extend({
name: DS.attr('string'),
address: DS.belongsTo('address'),
//hasOne via belongsTo as suggested by another SO post:
//http://stackoverflow.com/questions/14686253/how-to-have-hasone-relation-with-embedded-always-relation
});
and an address model
App.Address = DS.Model.extend({
addressable_id: DS.attr('string'),
street: DS.attr('string'),
country_code: DS.attr('string'),
user: DS.belongsTo('user')
});
Currently running this code throw an error in the console:
TypeError: Cannot read property 'typeKey' of undefined
which can be fixed by removing the
address: DS.belongsTo('address'),
line in the user model but then the relationship doesn't load properly.
So what am I doing wrong configuring this? I am having a hell of a time finding up to date documentation on this.
You need to use the DS.EmbeddedRecordsMixin on a per-type serializer.
In your case, you would need to do the following :
App.UserSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
address: {embedded: 'always'}
}
});
as explained in this excellent answer.
This is really confusing me at the moment and it's probably dead simple to fix.
I have two model, related between themselves:
App.User = DS.Model.extend({
name: DS.attr('string'),
zip_code: DS.attr('string'),
address_line: DS.attr('string'),
country: DS.attr('string'),
active: DS.attr('boolean'),
state: DS.attr('string'),
comments: DS.attr('string'),
user_type: DS.belongsTo('user_type', { async: true })
});
App.UserType = DS.Model.extend({
name: DS.attr('string'),
users: DS.hasMany('users', { async: true })
});
I'm trying to the a User's UserType.name:
Simple enough right? Wrong!
// Doesn't work.
user.get('user_type.name');
// Also doesn't work.
user.get('user_type').get('name');
In my console, I can clearly see the object is there and related.
You will probably need to "resolve" the UserType:
user.get('user_type').then(function(userType) {
console.log("User type is:", userType.get('name'));
});
Note that this introduces asynchrony to your method... Depending on what you're trying to do, you'll need to accomodate for that. Normally, when bound to a Handlebars template, this resolving of related types happens automatically, so you should be able to (in a template) use {{user.user_type.name}}
Because you've set the relationship as {async: true}, Ember presumes that the relationship is asynchronous, i.e. it needs to load the UserType model separately. So, at least the first time it's called, user.get('user_type') actually returns a promise which, at the point at which the chained method is called, is not guaranteed to have loaded content.
Even if it's already in the store, (if memory serves) Ember is not going to attempt to set it until the next run loop. Calling .get('name') on that promise immediately will thus give you null.
The user.get('user_type.name') syntax will just resolve internally to the same thing as user.get('user_type').get('name') so that result will be no different.
Steve H.'s response should work. Alternatively, if you're working with a server (not fixtures) and the server response is such that the userType is sideloaded with the user – or if you are in a position to change the response – you should be able to remove the {async: true} conditions, and both of the expressions in your question should work as expected.
I am using the Ember local storage adapter for an app that has two models: Organizations and Members. The relationship is that Organizations "hasMany" Members, and Members "belongsTo" Organizations, as follows:
App.Org = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
members: DS.hasMany('App.Member')
});
App.Member = DS.Model.extend({
name: DS.attr('string'),
org: DS.belongsTo('App.Org')
});
In the jsbin of the app, you can add members and the organizations to the local storage, which works perfectly (meaning that when I add a new organization, its selected members are added in the "members" hasMany relationship).
In addition to adding the new organization information, I would like to serialize the newly created organization record to be sent over to a server via AJAX (no I not want to use the RESTAdapter).
However, the "members" hasMany relationship is completely missing from the serialization, so this is the serialization that I'm getting (you can see for yourself if you add some members and then open up the console):
{
name: "Organization name",
description: "description of the org"
}
and this is the serialization that I would like:
{
name: "Organization name",
description: "description of the org",
members: ["id_of_member_1", "id_of_member_2", "id_of_member_3"]
}
This is how I am doing the serialization:
App.OrganizationController = Ember.ArrayController.extend({
createOrg: function(){
var neworg = App.Org.createRecord({
name: this.get('newname'),
description: this.get('newdescription')
});
neworg.get('members').pushObjects(this.get('newmembers'));
neworg.save();
var serializer = DS.RESTSerializer.create({});
console.log(serializer.serialize(neworg));
}
...
If serialization is not the way to go, another way might be making "members" a string array attribute, but so far this path has failed me.
In addition to suggestions, please provide a working jsbin based on the current jsbin of the app. Because I've tried things that don't work.
Keep in mind this app is set up with an Ember Data local storage adapter.
You can do it with the DS.EmbeddedRecordsMixin. org-serializer.js would look like:
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
members: { serialize: 'ids' }
}
});
See the documentation here.
I have the following models:
App.Company = DS.Model.extend({
name: DS.attr('string'),
accounts: DS.hasMany('App.Account', {
inverse: 'company'
})
});
App.Account = DS.Model.extend({
login: DS.attr('string'),
first_name: DS.attr('string'),
last_name: DS.attr('string'),
email: DS.attr('string'),
password: DS.attr('string'),
password_confirmation: DS.attr('string'),
company: DS.belongsTo('App.Company')
});
The company is defined as being embedded in the account:
DS.RESTAdapter.map('App.Account', {
company: { embedded: 'always' }
});
When I create a new account, the company data is correctly embedded in the account data and I'm seeing the POST request that I expect on the server side:
Started POST "/accounts" for 127.0.0.1 at 2013-06-27 13:30:53 +0200
Processing by AdminUsersController#create as JSON
Parameters: {"account"=>{"login"=>"fsdfdf", "first_name"=>"fgdfgh", "last_name"=>"fsfdsfdsfsd#fgfdgdfgf.de", "email"=>"dsfdsgds#frgdfgfgdfg.de", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "company"=>{"name"=>"gfdhgtrhzrh"}}}
However, I'm also seeing an additional POST request for the company itself:
Started POST "/companies" for 127.0.0.1 at 2013-06-27 13:30:53 +0200
Processing by CompaniesController#create as JSON
Parameters: {"company"=>{"name"=>"gfdhgtrhzrh"}}
I'm setting up the models as follows:
this.transaction = this.get('store').transaction();
var account = this.transaction.createRecord(App.Account, {});
account.set('company', this.transaction.createRecord(App.Company, {}));
When the user clicks save, I simply commit the transaction:
this.transaction.commit();
Is that a bug or am I doing something wrong? Spent quite some time on that already...
Thanks for help!
this.transaction.createRecord(App.Company, {})
The code fragment creates the separate company entity. Is it really such a surprise there is a post action for it?
As far as I remember that was never actually supported in the (old) version of Ember Data I used back then. Newer versions handle that case differently anyway so I'd say this is outdated and close it.