ducky/web
11:3bdc03963abe Browse Files
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.
package.json src/helpers/oauth-refresh.js
1.1 --- a/package.json Tue Jun 30 01:18:52 2015 -0400 1.2 +++ b/package.json Tue Jun 30 01:21:42 2015 -0400 1.3 @@ -8,8 +8,10 @@ 1.4 "ampersand-collection": "^1.4.5", 1.5 "ampersand-model": "^5.0.3", 1.6 "ampersand-react-mixin": "^0.1.2", 1.7 + "ampersand-rest-collection": "^4.0.0", 1.8 "ampersand-router": "^3.0.2", 1.9 "ampersand-sync": "^3.0.7", 1.10 + "andlog": "^1.0.0", 1.11 "babel": "^5.1.13", 1.12 "babel-loader": "^5.0.0", 1.13 "css-loader": "^0.12.0", 1.14 @@ -33,7 +35,8 @@ 1.15 "sass-loader": "0.4.2", 1.16 "style-loader": "^0.12.0", 1.17 "url-loader": "^0.5.5", 1.18 - "webpack": "1.8.9" 1.19 + "webpack": "1.8.9", 1.20 + "xhr": "^2.0.2" 1.21 }, 1.22 "devDependencies": { 1.23 "react-hot-loader": "^1.2.5",
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/src/helpers/oauth-refresh.js Tue Jun 30 01:21:42 2015 -0400 2.3 @@ -0,0 +1,112 @@ 2.4 +import app from 'ampersand-app' 2.5 +import xhr from 'xhr' 2.6 +import qs from 'qs' 2.7 +import Sync from 'ampersand-sync' 2.8 +import config from '../config' 2.9 + 2.10 +const getRefresh = (opts, callback) => { 2.11 + const refreshOpts = { 2.12 + url: config.urlBase + '/token', 2.13 + method: 'POST', 2.14 + headers: { 2.15 + 'Content-Type': 'application/x-www-form-urlencoded', 2.16 + 'Authorization': 'Basic ' + btoa(config.clientID + ':' + config.clientSecret), 2.17 + }, 2.18 + data: qs.stringify({ 2.19 + 'grant_type': 'refresh_token', 2.20 + 'refresh_token': app.me.refresh_token 2.21 + }), 2.22 + } 2.23 + xhr(refreshOpts, function(err, resp, body) { 2.24 + if (resp.statusCode != 200) { 2.25 + callback(err, resp, body) 2.26 + return 2.27 + } 2.28 + if (body && resp.headers['content-type'] == 'application/json') { 2.29 + try { 2.30 + body = JSON.parse(body) 2.31 + } catch (err) { 2.32 + app.trigger('token:refreshError', body) 2.33 + callback(err, resp, body) 2.34 + } 2.35 + } 2.36 + if (body.access_token) { 2.37 + app.me.set(body) 2.38 + } else { 2.39 + app.trigger('token:refreshError', body) 2.40 + } 2.41 + callback(err, resp, body) 2.42 + }) 2.43 +} 2.44 + 2.45 +const shouldRefreshXHR = (err, resp, body) => { 2.46 + if(body && resp.headers['Content-Type'] == 'application/json') { 2.47 + try { 2.48 + body = JSON.parse(body) 2.49 + } catch (err) { 2.50 + return false 2.51 + } 2.52 + } 2.53 + if (resp.statusCode == 401 && body.errors && body.errors[0] 2.54 + && body.errors[0].header == 'authorization' && body.errors[0].error == 'access_denied') { 2.55 + return true 2.56 + } 2.57 + return false 2.58 +} 2.59 + 2.60 +const doRefreshXHR = (opts, callback) => { 2.61 + opts.noRefresh = true 2.62 + getRefresh(opts, function(err, resp, body) { 2.63 + if (body.access_token) { 2.64 + opts.headers['Authorization'] = 'Bearer '+body.access_token 2.65 + xhr(opts, callback) 2.66 + } 2.67 + }) 2.68 +} 2.69 + 2.70 +const refreshXHR = (opts, callback) => { 2.71 + return xhr(opts, function(err, resp, body) { 2.72 + if (opts.noRefresh || !shouldRefreshXHR(err, resp, body)) { 2.73 + callback(err, resp, body) 2.74 + return 2.75 + } 2.76 + doRefreshXHR(opts, callback) 2.77 + }) 2.78 + xhr(opts, callback) 2.79 +} 2.80 + 2.81 +const shouldRefreshSync = (resp) => { 2.82 + if (resp && resp.errors && resp.errors[0] 2.83 + && resp.errors[0].header == 'authorization' && resp.errors[0].error == 'access_denied') { 2.84 + return true 2.85 + } 2.86 + return false 2.87 +} 2.88 + 2.89 +const doRefreshSync = (action, moc, opts) => { 2.90 + opts.noRefresh = true 2.91 + getRefresh(opts, function(err, resp, body) { 2.92 + if (body.access_token) { 2.93 + Sync(action, moc, opts) 2.94 + } 2.95 + }) 2.96 +} 2.97 + 2.98 +const refreshSync = (action, moc, opts) => { 2.99 + const oldError = opts.error 2.100 + opts.error = function(resp) { 2.101 + if(opts.noRefresh || !shouldRefreshSync) { 2.102 + oldError(resp) 2.103 + return 2.104 + } 2.105 + doRefreshSync(action, moc, opts) 2.106 + } 2.107 + Sync(action, moc, opts) 2.108 +} 2.109 + 2.110 +const refresh = { 2.111 + Sync: refreshSync, 2.112 + XHR: refreshXHR, 2.113 +} 2.114 + 2.115 +export default refresh