ember.jsfirebasefirebase-realtime-databasefirebase-securityemberfire

Ember Data Firebase rules confusion


I'm getting started with my first Ember/Firebase application and having trouble finding documentation that goes beyond public data.

My goal is to have an application where signed in users can create and view their own data. I see that Firebase suggests this rule for such a situation:

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}

But I can't find out any information about how this would work on the Ember end. For example, assuming I have an "entry" model that I am saving:

save(model) {
  model.save().then( () => {
    this.transitionToRoute('index');
  }, error => {
    console.error(`error: ${error}`);
  })
},

Not sure if I need to be storing a uid in the model?

And then if I want the user to get a listing of their own entries:

import Ember from 'ember';
export default Ember.Route.extend({
  model() {
    return this.store.findAll('entry');
  }
});

This produces the following error:

ember.debug.js:30610 Error while processing route: index permission_denied at /entries: Client doesn't have permission to access the desired data. Error: permission_denied at /entries: Client doesn't have permission to access the desired data.

At this point I'm not sure what I should be doing –– do I need to build a custom URL or add a namespace in my firebase adapter to add a users/xxx prefix? Or etc?

Cannot find any documentation/tutorials/walkthroughs that cover anything beyond public read/write data.


Solution

  • I made this work eventually. It basically boiled down to storing the uid from Firebase auth in a service after login (I added ember-local-storage in case of page refresh), and then adding a pathForType method to the application adapter to nest all users data under users/${uid}, so in firebase data is ending up in this configuration: users/${uid}/entries/${entry_id}.

    application/adapter.js

    import FirebaseAdapter from 'emberfire/adapters/firebase';
    import Ember from 'ember';
    export default FirebaseAdapter.extend({
    
        /* inject service to access uid */
      journalist: Ember.inject.service('journalist'),
    
        /* path for type nests /entries under users/${uid} */
        pathForType(type) {
          let uid =  this.get('journalist.uid');
          let path = Ember.String.pluralize(type);
        return  `users/${uid}/${path}`;
       }
    
    });
    

    application/route.js

    import Ember from 'ember';
    
    export default Ember.Route.extend({
    
        /* injecting service to set uid */
      journalist: Ember.inject.service('journalist'),
    
      beforeModel() {
        return this.get('session').fetch().catch();
      },
      actions: {
        signIn(provider) {
          this.get('session').open('firebase', {provider: provider}).then(data => {
    
            /* setting uid in service for later retrieval elsewhere */
            this.get('journalist').setUID(data.uid);
            this.transitionTo('entries');
        });
        },
        signOut() {
          this.get('session').close().then(() => this.transitionTo('application'));
    
        }
      }
    });
    

    entries/route.js

    import Ember from 'ember';
    export default Ember.Route.extend({
    
        /* nothing special required for ember data save, findAll, etc, adapter takes care of everything */
        model() {
            return this.store.findAll('entry');
        }
    });
    

    services/journalist.js:

    import Ember from 'ember';
    import { storageFor } from 'ember-local-storage';
    export default Ember.Service.extend({
        uid: null,
        localStore: storageFor('journalist-session'),
        init() {
            const localStore = this.get('localStore');
        this.set('uid', localStore.get('uid'));
      },
        setUID(id) {
            this.set('uid', id);
            this.set('localStore.uid', id);
        }
    });
    

    storages/journalist-session:

    import StorageObject from 'ember-local-storage/local/object';
    const Storage = StorageObject.extend();
     Storage.reopenClass({
       initialState() {
         return {
           uid: null
         };
       }
     });
    export default Storage;
    

    firebase rules:

    {
      "rules": {
        "users": {
          "$uid": {
            ".read": "auth != null && auth.uid == $uid",
            ".write": "auth != null && auth.uid == $uid"
          }
        }
      }
    }