0

I am extending Ember Simple Auth's base authentication class to allow authentication with Google. So far, it works on Safari 8 and Chrome 41 (both on Yosemite) with no errors. However, on Firefox 35, it throws an Error that does not occur on the other browsers. Here is my Google authenticator class:

App.GoogleAuthenticator = SimpleAuth.Authenticators.Base.extend({
    // constants for Google API
    GAPI_CLIENT_ID: 'the client id',
    GAPI_SCOPE: ['email'],
    GAPI_TOKEN_VERIFICATION_ENDPOINT: 'https://www.googleapis.com/oauth2/v2/tokeninfo',

    // method for scheduleing a single token refresh
    // time in milliseconds
    scheduleSingleTokenRefresh: function(time) {
        var self = this;
        return new Ember.RSVP.Promise(function(resolve, reject) {
            Ember.run.later(self, function() {
                gapi.auth.authorize({
                    client_id: self.GAPI_CLIENT_ID,
                    scope: self.GAPI_SCOPE,
                    immediate: true
                }, function(data) {
                    if (data && !data.error) {
                        resolve(data);
                    } else {
                        reject((data || {}).error);
                    }
                });
            }, time);
        });
    },
    // WIP: recursive method that reschedules another token refresh after the previous scheduled one was fulfilled
    // usage: scheduleTokenRefreshes(time until token should refresh for the first time, time between subsequent refreshes)
    // usage: scheduleTokenRefreshes(time between refreshes)
    scheduleTokenRefreshes: function(time1, time2) {
        var self = this;
        // if there is a time2, schedule a single refresh, wait for it to be fulfilled, then call myself to schedule again
        if (!Ember.isEmpty(time2)) {
            self.scheduleSingleTokenRefresh(time1)
            .then(function() {
                self.scheduleTokenRefreshes(time2);
            });
        // if there isn't a time2, simply schedule a single refresh, then call myself to schedule again
        } else {
            self.scheduleSingleTokenRefresh(time1)
            .then(function() {
                self.scheduleTokenRefreshes(time1);
            });
        }
    },

    // method that restores the session on reload
    restore: function(data) {
        var self = this;
        return new Ember.RSVP.Promise(function(resolve, reject) {
            console.log(data);
            if (Ember.isEmpty(data.access_token)) {
                reject();
                return;
            }
            // schedule a refresh 15 minutes before it expires or immediately if it expires in < 15
            var timeNow = Math.floor(Date.now() / 1000);
            var expiresAt = +data.expires_at;
            var timeDifference = expiresAt - timeNow;
            var schedulingDelay = Math.floor(timeDifference - 15 * 60);
            schedulingDelay = schedulingDelay < 0 ? 0 : schedulingDelay;
            self.scheduleTokenRefreshes(schedulingDelay * 1000, 45 * 60);
            resolve(data);
        });
    },
    // method that authenticates
    authenticate: function() {
        var self = this;
        return new Ember.RSVP.Promise(function(resolve, reject) {
            gapi.auth.authorize({
                client_id: self.GAPI_CLIENT_ID,
                scope: self.GAPI_SCOPE
            }, function(data) {
                if (data && !data.error) {
                    // schedule a refresh in 45 minutes
                    var schedulingDelay = 45 * 60;
                    self.scheduleTokenRefreshes(schedulingDelay * 1000);
                    resolve(data);
                } else {
                    reject((data || {}).error);
                }
            });
        });
    },
    // method that logs the user out and revokes the token
    invalidate: function(data) {
        var self = this;
        return new Ember.RSVP.Promise(function(resolve, reject) {
            // send a GET request to revoke the token
            Ember.$.ajax({
                type: 'GET',
                url: 'https://accounts.google.com/o/oauth2/revoke?token=' + self.get('session.access_token'),
                contentType: 'application/json',
                dataType: 'jsonp'
            })
            .done(function(successData) {
                resolve(successData);
            })
            .fail(function(error) {
                reject(error);
            });
        });
    }
});

When the popup window closes after a successful login on Google's end, this error appears on Firefox's console:

Error: Assertion Failed: Error: Permission denied to access property 'toJSON' ember.js:13749
"__exports__.default<.persist@http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1524:1
__exports__.default<.updateStore@http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1195:11
__exports__.default<.setup@http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1149:9
__exports__.default<.authenticate/</<@http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1066:13
tryCatch@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47982:16
invokeCallback@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47994:17
publish@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47965:11
@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:29462:9
Queue.prototype.invoke@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:848:11
Queue.prototype.flush@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:913:13
DeferredActionQueues.prototype.flush@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:718:13
Backburner.prototype.end@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:143:11
createAutorun/backburner._autorun<@http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:546:9
" ember.js:29488

Here is the version information:

DEBUG: Ember             : 1.9.1
DEBUG: Ember Data        : 1.0.0-beta.14.1
DEBUG: Handlebars        : 2.0.0
DEBUG: jQuery            : 2.1.3
DEBUG: Ember Simple Auth : 0.7.2

The most confounding thing is that this only appears on Firefox. Is it a bug in Ember Simple Auth or Ember? How do I fix it?

Jonathan Chan
  • 354
  • 2
  • 12

2 Answers2

0

I do not know about only Firefox throwing an error (I've had a similar error with Chrome 40), but there is a bug in ember-simple-auth 0.7.2 with Ember 1.9 that prohibits sending an actual error response in the authenticate method in the authenticator.

If you return reject() in the rejection function of authenticate it will not throw an additional error. This will however not propagate the errorstatus or message, so I consider this a bug.

A work-around was proposed on github about this issue by setting Ember.onerror=Ember.K temporarily so additional errors will not be propagated, although it will propagate the original authenticate rejection with the error-status.

The issue in the github repo only mentions problems with testing this, but I've had this problem in normal code.

see: https://github.com/simplabs/ember-simple-auth/issues/407

Density 21.5
  • 1,955
  • 14
  • 17
  • Thank you for the info, but returning `reject()` does not fix the issue. This appears to be another issue. For some reason, the [examples in Ember Simple Auth](https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-multiple-external-providers.html) do not get this error. For now, I'm trying to change my code to be more like the example and figure out what is wrong. – Jonathan Chan Jan 31 '15 at 08:44
  • This issue appears to have been addressed in [Ember 1.10](https://github.com/emberjs/ember.js/releases/tag/v1.10.0-beta.2), specifically in the 5th point. Hopefully it addresses what you're dealing with. – Jonathan Chan Jan 31 '15 at 15:55
0

Turns out the error was on the resolve part of the authenticate method. Here is what fixed it:

App.GoogleAuthenticator = SimpleAuth.Authenticators.Base.extend({
    authenticate: function() {
        return new Ember.RSVP.Promise(function(resolve, reject) {
            gapi.auth.authorize({
                client_id: 'the client id',
                scope: ['the scopes'],
            }, function(data) {
                if (data && !data.error) {
                    resolve({
                        access_token: data.access_token // !! passing the entire 'data' object caused the error somehow
                    });
                } else {
                    reject((data || {}).error);
                }
            });
        });
    },
    // ...
});

I'm still not quite sure why this caused the error. Perhaps the Google API's response (in its entirety) is somehow incompatible with Ember Simple Auth.

Jonathan Chan
  • 354
  • 2
  • 12