From 55f71b0a0056955837c0021658562b184fa24419 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Mon, 16 Nov 2015 16:58:00 -0700 Subject: [PATCH] Customizing refresh token flow Departs from oauth2, oh well. Fixes #58. --- app/authenticators/oauth2.js | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/app/authenticators/oauth2.js b/app/authenticators/oauth2.js index c5c9221..8af289b 100644 --- a/app/authenticators/oauth2.js +++ b/app/authenticators/oauth2.js @@ -1,6 +1,69 @@ import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant'; import config from '../config/environment'; +import parseBase64 from '../utils/parse-base64'; +const { RSVP: { Promise }, isEmpty, run, Logger: { warn } } = Ember; export default OAuth2PasswordGrant.extend({ serverTokenEndpoint: `${config.apiURL}/api/authenticate`, + serverTokenRefreshEndpoint: `${config.apiURL}/api/refresh`, + + authenticate: function(identification, password, scope = []) { + return new Promise((resolve, reject) => { + const data = { username: identification, password }; + const serverTokenEndpoint = this.get('serverTokenEndpoint'); + this.makeRequest(serverTokenEndpoint, data).then((response) => { + run(() => { + const token = parseBase64(response['access_token']); + const expiresAt = this._absolutizeExpirationTime(token['exp']); + this._scheduleAccessTokenRefresh(expiresAt, response['access_token']); + if (!isEmpty(expiresAt)) { + response = Ember.merge(response, { 'expires_at': expiresAt }); + } + resolve(response); + }); + }, (xhr) => { + run(null, reject, xhr.responseJSON || xhr.responseText); + }); + }); + }, + + _scheduleAccessTokenRefresh: function(expiresAt, accessToken) { + if (this.get('refreshAccessTokens')) { + const now = (new Date()).getTime(); + const offset = (Math.floor(Math.random() * 5) + 5) * 1000; + if (!isEmpty(accessToken) && !isEmpty(expiresAt) && expiresAt > now - offset) { + run.cancel(this._refreshTokenTimeout); + delete this._refreshTokenTimeout; + if (!Ember.testing) { + this._refreshTokenTimeout = run.later(this, this._refreshAccessToken, expiresAt, accessToken, expiresAt - now - offset); + } + } + } + }, + + _refreshAccessToken: function(expiresAt, accessToken) { + const data = { 'token': accessToken }; + const serverTokenRefreshEndpoint = this.get('serverTokenRefreshEndpoint'); + return new Promise((resolve, reject) => { + this.makeRequest(serverTokenRefreshEndpoint, data).then((response) => { + run(() => { + const token = parseBase64(response['access_token']); + const expiresAt = this._absolutizeExpirationTime(token['exp']); + const data = Ember.merge(response, { 'expires_at': expiresAt }); + this._scheduleAccessTokenRefresh(expiresAt, response['access_token']); + this.trigger('sessionDataUpdated', data); + resolve(data); + }); + }, (xhr, status, error) => { + warn(`Access token could not be refreshed - server responded with ${error}.`); + reject(); + }); + }); + }, + + _absolutizeExpirationTime: function(expiresAt) { + if (!isEmpty(expiresAt)) { + return new Date(expiresAt * 1000).getTime(); + } + } });