Search code examples
ember.jsinitializerember-simple-auth

Synchronously inject current user after login


In my EmberJS application, I have a current user initializer, which injects the user into all controllers, routes, and views. It works great when logged in. I need the synchronous loading of the current user object so I can check some user permissions right off the bat.

Here's my initializer:

App.CurrentUserInitializer - Ember.Initializer.extent({

    name: 'current-user',
    after: 'authentication',

    initialize: function(container, app) {

      var store = container.lookup('store:main');

      app.deferReadiness();

      store.find('user', 'me').then(function (user) {

        app.register('session:user', user, {instantiate: false});

        _.forEach(['route', 'controller', 'view'], function (place) {
          app.inject(place, 'currentUser', 'session:user');
        });

        app.advanceReadiness();

      }).catch(function () {
        app.advanceReadiness();
      });
    }
});

Where this breaks down for me, is during login. When the app boots, the initializer is run, but the /users/me route returns a 401 error. If I don't catch the error and advanceReadiness, booting halts. By catching the error, the app boots, but the initializer isn't run again after login, so the current user isn't loaded.

What are my options here? I can't use @marcoow's recommended method of adding a computed property to the Session, as I need the user loaded at boot time.

I've tried forcing loading the user object on the IndexRoute as a hack, but that doesn't seem to work.

Any tips are appreciated.


Solution

  • I'd register a session:current object with user property being null. That would be injected into controllers and routes (not sure injecting inside views is a good idea).

    So at boot time the user is unknown, but the user lookup is done before the router goes deeper than application route, the root:

    In the beforeModel hook of the application route, you'll load that current user. Then:

    • either you got the user and you set it this.set('session.user', model)
    • or you'll go in the error hook of the application route, in which case you'd have to check why, and if 401 then you can redirect the user to the login route this.transitionTo('login')

    Don't forget to set a flag on session if you got the 401 so that transitionTo will make our user lookup of beforeModel to not happen again until we reach the login route

    The code to be used to load the session user and initialise it could be placed in that session:current object so that you could call it from the application route or the login controller.

    This is for example my session initialiser (not exactly as I explained, but loading in the initialiser, so closer to what you hve). I used a session model so that I do /session/current and then got a user into it (or not) which has the correct id and not me which then would make the store to load the same user with another id, and so have twice the same user as 2 different records:

    app/models/session.js:

    import DS from 'ember-data';
    import Ember from 'ember';
    
    export default DS.Model.extend({
      user:            DS.belongsTo('user'),
      isAuthenticated: Ember.computed.bool('user.isClaimed')
    });
    

    app/initializers/session.js:

    import Ember from 'ember';
    
    export default {
      name:  'session',
      after: 'store',
    
      initialize: function (container, app) {
        var store = container.lookup('store:main'),
            sid = Ember.$.cookie('sid');
        // used to register a session
        var register = function (session) {
          app.register('session:main', session, {instantiate: false});
          app.inject('route', 'session', 'session:main');
          app.inject('controller', 'session', 'session:main');
        };
        // used to create a new session and trigger the backend to get details about it
        // useful if the server is able to give an existing session while the browser doesn't know about it
        // with external providers for example
        var newSession = function () {
          var session = store.createRecord('session');
          // be sure to wipe out any invalid session ID
          Ember.$.removeCookie('sid');
          register(session);
          return session.save().then(function (model) {
            // if we got a valid new session, save its ID
            Ember.$.cookie('sid', model.get('id'));
          }).catch(function () {
            Ember.debug('error saving new session: ' + Array.prototype.join.call(arguments, ', '));
          });
        };
        // overall logic ==================
        app.deferReadiness();
        if (sid) {
          // try to load the existing session
          store.find('session', sid).then(function (model) {
            register(model);
            app.advanceReadiness();
          }).catch(function () {
            // there was a cookie for the session but it might have expired or the server invalidated it
            Ember.debug('error loading session: ' + Array.prototype.join.call(arguments, ', '));
            newSession().finally(function () {
              app.advanceReadiness();
            });
          });
        }
        else {
          // we don't have any trace of a session, let's just create a new one
          newSession().finally(function () {
            app.advanceReadiness();
          });
        }
      }
    };
    

    app/router.js:

    import Ember from 'ember';
    
    var Router = Ember.Router.extend();
    
    Router.map(function () {
      this.resource('session', {path: '/'}, function(){
        this.route('login');
        this.route('logout');
      });
    });
    
    export default Router;
    

    app/templates/application.hbs (as an example):

    <h2 id='title'>Welcome to my app</h2>
    {{#if session.isAuthenticated}}
      <a {{action 'session.logout'}}>Logout</a>
    {{else}}
      {{#link-to 'session.login'}}Login{{/link-to}}
    {{/if}}
    {{outlet}}
    

    Then once in login controller, when the user actually logs in, the server will return the session model with the user linked into it, so the Ember binding magic will just update the session object.