ducky/web

Paddy 2015-06-30 Child:9b97c908a706

11:3bdc03963abe Go to Latest

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.

History
     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