Ghost/ghost/admin/app/authenticators/oauth2.js
Kevin Ansfield ae759d39ea Fixed multiple 401s caused by token revocation requests
no issue
- disabled Ember Simple Auth's default token revocation
    - we trigger session invalidation on a 401 which means our token isn't valid so the revoke requests will also fail
- renamed application route's `invalidateSession` to `logout` in order to distinguish it from any ESA methods
    - added the token revocation requests to this action, we can be fairly sure at this point that the current tokens will be valid so the requests will succeed
- added check to `ajax.handleResponse` so that we don't invalidate the session for requests to external services
- removed pointless assertion from the ajax integration test
2018-04-16 17:55:21 +01:00

92 lines
3.3 KiB
JavaScript

import Authenticator from 'ember-simple-auth/authenticators/oauth2-password-grant';
import RSVP from 'rsvp';
import {assign} from '@ember/polyfills';
import {computed} from '@ember/object';
import {isEmpty} from '@ember/utils';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {makeArray as wrap} from '@ember/array';
export default Authenticator.extend({
ajax: service(),
session: service(),
config: service(),
ghostPaths: service(),
init() {
this._super(...arguments);
let handler = run.bind(this, () => {
this.onOnline();
});
window.addEventListener('online', handler);
},
serverTokenEndpoint: computed('ghostPaths.apiRoot', function () {
return `${this.get('ghostPaths.apiRoot')}/authentication/token`;
}),
// disable general token revocation because the requests will always 401
// (revocation is triggered by invalid access token so it's already invalid)
// we have a separate logout procedure that sends revocation requests
serverTokenRevocationEndpoint: null,
makeRequest(url, data) {
/* eslint-disable camelcase */
data.client_id = this.get('config.clientId');
data.client_secret = this.get('config.clientSecret');
/* eslint-enable camelcase */
let options = {
data,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded'
};
return this.get('ajax').post(url, options);
},
/**
* Invoked when "navigator.online" event is trigerred.
* This is a helper function to handle intermittent internet connectivity. Token is refreshed
* when browser status becomes "online".
*/
onOnline() {
if (this.get('session.isAuthenticated')) {
let autoRefresh = this.get('refreshAccessTokens');
if (autoRefresh) {
let expiresIn = this.get('session.data.authenticated.expires_in');
let token = this.get('session.data.authenticated.refresh_token');
return this._refreshAccessToken(expiresIn, token);
}
}
},
authenticate(identification, password, scope = [], headers = {}) {
return new RSVP.Promise((resolve, reject) => {
let data = {grant_type: 'password', username: identification, password};
let serverTokenEndpoint = this.get('serverTokenEndpoint');
let scopesString = wrap(scope).join(' ');
if (!isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data, headers).then((response) => {
run(() => {
/* eslint-disable camelcase */
let expiresAt = this._absolutizeExpirationTime(response.expires_in);
this._scheduleAccessTokenRefresh(response.expires_in, expiresAt, response.refresh_token);
/* eslint-enable camelcase */
if (!isEmpty(expiresAt)) {
response = assign(response, {expires_at: expiresAt});
}
resolve(response);
});
}, (error) => {
reject(error);
});
});
}
});