ducky/web
2015-06-30
Child:9b97c908a706
ducky/web/src/helpers/oauth-refresh.js
Implement OAuth token refreshing helper. Create an ampersand-sync/XHR request wrapper that will detect when a request fails because an OAuth2 token has expired, and will try to refresh the token then retry the request.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/helpers/oauth-refresh.js Tue Jun 30 01:21:42 2015 -0400 1.3 @@ -0,0 +1,112 @@ 1.4 +import app from 'ampersand-app' 1.5 +import xhr from 'xhr' 1.6 +import qs from 'qs' 1.7 +import Sync from 'ampersand-sync' 1.8 +import config from '../config' 1.9 + 1.10 +const getRefresh = (opts, callback) => { 1.11 + const refreshOpts = { 1.12 + url: config.urlBase + '/token', 1.13 + method: 'POST', 1.14 + headers: { 1.15 + 'Content-Type': 'application/x-www-form-urlencoded', 1.16 + 'Authorization': 'Basic ' + btoa(config.clientID + ':' + config.clientSecret), 1.17 + }, 1.18 + data: qs.stringify({ 1.19 + 'grant_type': 'refresh_token', 1.20 + 'refresh_token': app.me.refresh_token 1.21 + }), 1.22 + } 1.23 + xhr(refreshOpts, function(err, resp, body) { 1.24 + if (resp.statusCode != 200) { 1.25 + callback(err, resp, body) 1.26 + return 1.27 + } 1.28 + if (body && resp.headers['content-type'] == 'application/json') { 1.29 + try { 1.30 + body = JSON.parse(body) 1.31 + } catch (err) { 1.32 + app.trigger('token:refreshError', body) 1.33 + callback(err, resp, body) 1.34 + } 1.35 + } 1.36 + if (body.access_token) { 1.37 + app.me.set(body) 1.38 + } else { 1.39 + app.trigger('token:refreshError', body) 1.40 + } 1.41 + callback(err, resp, body) 1.42 + }) 1.43 +} 1.44 + 1.45 +const shouldRefreshXHR = (err, resp, body) => { 1.46 + if(body && resp.headers['Content-Type'] == 'application/json') { 1.47 + try { 1.48 + body = JSON.parse(body) 1.49 + } catch (err) { 1.50 + return false 1.51 + } 1.52 + } 1.53 + if (resp.statusCode == 401 && body.errors && body.errors[0] 1.54 + && body.errors[0].header == 'authorization' && body.errors[0].error == 'access_denied') { 1.55 + return true 1.56 + } 1.57 + return false 1.58 +} 1.59 + 1.60 +const doRefreshXHR = (opts, callback) => { 1.61 + opts.noRefresh = true 1.62 + getRefresh(opts, function(err, resp, body) { 1.63 + if (body.access_token) { 1.64 + opts.headers['Authorization'] = 'Bearer '+body.access_token 1.65 + xhr(opts, callback) 1.66 + } 1.67 + }) 1.68 +} 1.69 + 1.70 +const refreshXHR = (opts, callback) => { 1.71 + return xhr(opts, function(err, resp, body) { 1.72 + if (opts.noRefresh || !shouldRefreshXHR(err, resp, body)) { 1.73 + callback(err, resp, body) 1.74 + return 1.75 + } 1.76 + doRefreshXHR(opts, callback) 1.77 + }) 1.78 + xhr(opts, callback) 1.79 +} 1.80 + 1.81 +const shouldRefreshSync = (resp) => { 1.82 + if (resp && resp.errors && resp.errors[0] 1.83 + && resp.errors[0].header == 'authorization' && resp.errors[0].error == 'access_denied') { 1.84 + return true 1.85 + } 1.86 + return false 1.87 +} 1.88 + 1.89 +const doRefreshSync = (action, moc, opts) => { 1.90 + opts.noRefresh = true 1.91 + getRefresh(opts, function(err, resp, body) { 1.92 + if (body.access_token) { 1.93 + Sync(action, moc, opts) 1.94 + } 1.95 + }) 1.96 +} 1.97 + 1.98 +const refreshSync = (action, moc, opts) => { 1.99 + const oldError = opts.error 1.100 + opts.error = function(resp) { 1.101 + if(opts.noRefresh || !shouldRefreshSync) { 1.102 + oldError(resp) 1.103 + return 1.104 + } 1.105 + doRefreshSync(action, moc, opts) 1.106 + } 1.107 + Sync(action, moc, opts) 1.108 +} 1.109 + 1.110 +const refresh = { 1.111 + Sync: refreshSync, 1.112 + XHR: refreshXHR, 1.113 +} 1.114 + 1.115 +export default refresh