ducky/web
ducky/web/src/helpers/oauth-refresh.js
Update our profiles collection to use ampersand-rest-collection. Use ampersand-rest-collection instead of ampersand-collection, so we can take advantage of the excellent "getOrFetch" function to fall back on the server when looking for a member of the collection that isn't downloaded yet. Also, use the correct Authorization header when making profile collection requests. Finally, if the profile collection requests fail due to an expired access token, use the refresh token to acquire a new access token, then retry the request.
| paddy@11 | 1 import app from 'ampersand-app' |
| paddy@11 | 2 import xhr from 'xhr' |
| paddy@11 | 3 import qs from 'qs' |
| paddy@11 | 4 import Sync from 'ampersand-sync' |
| paddy@11 | 5 import config from '../config' |
| paddy@11 | 6 |
| paddy@11 | 7 const getRefresh = (opts, callback) => { |
| paddy@11 | 8 const refreshOpts = { |
| paddy@11 | 9 url: config.urlBase + '/token', |
| paddy@11 | 10 method: 'POST', |
| paddy@11 | 11 headers: { |
| paddy@11 | 12 'Content-Type': 'application/x-www-form-urlencoded', |
| paddy@11 | 13 'Authorization': 'Basic ' + btoa(config.clientID + ':' + config.clientSecret), |
| paddy@11 | 14 }, |
| paddy@11 | 15 data: qs.stringify({ |
| paddy@11 | 16 'grant_type': 'refresh_token', |
| paddy@11 | 17 'refresh_token': app.me.refresh_token |
| paddy@11 | 18 }), |
| paddy@11 | 19 } |
| paddy@11 | 20 xhr(refreshOpts, function(err, resp, body) { |
| paddy@11 | 21 if (resp.statusCode != 200) { |
| paddy@11 | 22 callback(err, resp, body) |
| paddy@11 | 23 return |
| paddy@11 | 24 } |
| paddy@11 | 25 if (body && resp.headers['content-type'] == 'application/json') { |
| paddy@11 | 26 try { |
| paddy@11 | 27 body = JSON.parse(body) |
| paddy@11 | 28 } catch (err) { |
| paddy@11 | 29 app.trigger('token:refreshError', body) |
| paddy@11 | 30 callback(err, resp, body) |
| paddy@11 | 31 } |
| paddy@11 | 32 } |
| paddy@11 | 33 if (body.access_token) { |
| paddy@11 | 34 app.me.set(body) |
| paddy@11 | 35 } else { |
| paddy@11 | 36 app.trigger('token:refreshError', body) |
| paddy@11 | 37 } |
| paddy@11 | 38 callback(err, resp, body) |
| paddy@11 | 39 }) |
| paddy@11 | 40 } |
| paddy@11 | 41 |
| paddy@11 | 42 const shouldRefreshXHR = (err, resp, body) => { |
| paddy@11 | 43 if(body && resp.headers['Content-Type'] == 'application/json') { |
| paddy@11 | 44 try { |
| paddy@11 | 45 body = JSON.parse(body) |
| paddy@11 | 46 } catch (err) { |
| paddy@11 | 47 return false |
| paddy@11 | 48 } |
| paddy@11 | 49 } |
| paddy@11 | 50 if (resp.statusCode == 401 && body.errors && body.errors[0] |
| paddy@11 | 51 && body.errors[0].header == 'authorization' && body.errors[0].error == 'access_denied') { |
| paddy@11 | 52 return true |
| paddy@11 | 53 } |
| paddy@11 | 54 return false |
| paddy@11 | 55 } |
| paddy@11 | 56 |
| paddy@11 | 57 const doRefreshXHR = (opts, callback) => { |
| paddy@11 | 58 opts.noRefresh = true |
| paddy@11 | 59 getRefresh(opts, function(err, resp, body) { |
| paddy@11 | 60 if (body.access_token) { |
| paddy@11 | 61 opts.headers['Authorization'] = 'Bearer '+body.access_token |
| paddy@11 | 62 xhr(opts, callback) |
| paddy@11 | 63 } |
| paddy@11 | 64 }) |
| paddy@11 | 65 } |
| paddy@11 | 66 |
| paddy@11 | 67 const refreshXHR = (opts, callback) => { |
| paddy@11 | 68 return xhr(opts, function(err, resp, body) { |
| paddy@11 | 69 if (opts.noRefresh || !shouldRefreshXHR(err, resp, body)) { |
| paddy@11 | 70 callback(err, resp, body) |
| paddy@11 | 71 return |
| paddy@11 | 72 } |
| paddy@11 | 73 doRefreshXHR(opts, callback) |
| paddy@11 | 74 }) |
| paddy@11 | 75 xhr(opts, callback) |
| paddy@11 | 76 } |
| paddy@11 | 77 |
| paddy@11 | 78 const shouldRefreshSync = (resp) => { |
| paddy@11 | 79 if (resp && resp.errors && resp.errors[0] |
| paddy@11 | 80 && resp.errors[0].header == 'authorization' && resp.errors[0].error == 'access_denied') { |
| paddy@11 | 81 return true |
| paddy@11 | 82 } |
| paddy@11 | 83 return false |
| paddy@11 | 84 } |
| paddy@11 | 85 |
| paddy@11 | 86 const doRefreshSync = (action, moc, opts) => { |
| paddy@11 | 87 opts.noRefresh = true |
| paddy@11 | 88 getRefresh(opts, function(err, resp, body) { |
| paddy@11 | 89 if (body.access_token) { |
| paddy@11 | 90 Sync(action, moc, opts) |
| paddy@11 | 91 } |
| paddy@11 | 92 }) |
| paddy@11 | 93 } |
| paddy@11 | 94 |
| paddy@11 | 95 const refreshSync = (action, moc, opts) => { |
| paddy@11 | 96 const oldError = opts.error |
| paddy@11 | 97 opts.error = function(resp) { |
| paddy@11 | 98 if(opts.noRefresh || !shouldRefreshSync) { |
| paddy@11 | 99 oldError(resp) |
| paddy@11 | 100 return |
| paddy@11 | 101 } |
| paddy@11 | 102 doRefreshSync(action, moc, opts) |
| paddy@11 | 103 } |
| paddy@11 | 104 Sync(action, moc, opts) |
| paddy@11 | 105 } |
| paddy@11 | 106 |
| paddy@11 | 107 const refresh = { |
| paddy@11 | 108 Sync: refreshSync, |
| paddy@11 | 109 XHR: refreshXHR, |
| paddy@11 | 110 } |
| paddy@11 | 111 |
| paddy@11 | 112 export default refresh |