UNPKG

google-admin-sdk

Version:

node.js library that wraps the Directory and Groups APIs in the Google Admin SDK

144 lines (132 loc) 6.08 kB
quest = require 'quest' dotty = require 'dotty' _ = require 'underscore' _.str = require 'underscore.string' retry = require 'retry' # base google api class. provides functions for: # static methods for: # 1. Generating an initial oauth redirect with scopes you want # 2. Trading in authorization codes for tokens # class object is instantiated with oauth token info class GoogleAPIAdminSDK constructor: (@options) -> throw new Error('Must initialize GoogleAPI with token info') unless @options.token throw new Error('Must provide either a refresh token or an access token') unless @options.token.refresh or @options.token.access # client secret needed for auto-refresh throw new Error('If providing a refresh token, must provide client id and secret') unless not @options.token.refresh or (@options.client?.id and @options.client?.secret) retry_options: {maxTimeout: 3 * 60 * 1000, retries: 5, randomize: true} @retry_err: (err, resp) -> return err if err? # 500/502 = "backend error" # 403/503 = rate limiting errors # 404 # 412 if resp.statusCode in [403, 404, 412, 500, 502, 503] return new Error "Status code #{resp.statusCode} from Google" return null # default response interpreters--APIs should most likely specialize these # error_handler returns true if it found/handled an error @error_handler: (cb) -> (err, resp, body) -> return cb(err) or true if err unless 200 <= resp.statusCode < 299 return cb( code: resp.statusCode body: body req: _(resp?.req or {}).chain().pick('method', 'path', '_headers').extend({body:"#{resp?.req?.res?.request?.body}"}).value() ) or true return false @response_handler: (cb) -> (err, resp, body) -> return cb err, body if err? return cb null, {'204': 'Operation success'} if resp.statusCode is 204 return cb body, null if resp.statusCode >= 400 handle_entry = (entry) -> obj = { id: entry.id.$t, updated: entry.updated.$t, link: entry.link, title: entry.title?.$t, feedLink: entry.gd$feedLink, who: entry.gd$who } _(entry).chain().keys().filter((k) -> k.match /^apps\$/).each (apps_key) -> key = apps_key.match(/^apps\$(.*)$/)[1] if key is 'property' obj[prop.name] = prop.value for prop in entry[apps_key] else obj[key] = entry[apps_key] obj if body?.feed? return cb null, { id: body.feed.id, link: body.feed.link, data: _(body.feed.entry or []).map(handle_entry) } else if body?.entry return cb null, handle_entry(body.entry) else if _.str.include(body?.kind, 'admin#directory') return cb null, body else if resp.statusCode is 200 # catch all for success return cb null, body else console.log 'WARNING: unhandled body', resp.statusCode, body return cb null, body @request_token: (options, cb) => # need code, client.id, client.secret for required in ['code', 'redirect_uri', 'client.id', 'client.secret'] if not dotty.exists(options, required) return cb new Error("Error: '#{required}' is necessary to request a token") options = method: 'post' uri: 'https://accounts.google.com/o/oauth2/token' json: true form: code : options.code redirect_uri : options.redirect_uri client_id : options.client.id client_secret : options.client.secret grant_type : 'authorization_code' operation = retry.operation @retry_options operation.attempt => quest options, (err, resp, body) => unless operation.retry GoogleAPIAdminSDK.retry_err(err, resp) (@response_handler(cb)) err, resp, body request_refresh_token: (cb) => options = method: 'post' uri: 'https://accounts.google.com/o/oauth2/token' json: true form: refresh_token : @options.token.refresh client_id : @options.client.id client_secret : @options.client.secret grant_type : 'refresh_token' operation = retry.operation @retry_options operation.attempt => quest options, (err, resp, body) => return if operation.retry GoogleAPIAdminSDK.retry_err(err, resp) @options.token.access = body.access_token if body.access_token? @options.token.id = body.id_token if body.id_token? console.log 'Failed to refresh Google token!' if resp.statusCode isnt 200 (GoogleAPIAdminSDK.response_handler(cb)) err, resp, body oauth2_request: (options, refreshed_already, cb) => if _(refreshed_already).isFunction() cb = refreshed_already refreshed_already = false refresh = () => @request_refresh_token (err, body) => return cb err, null, body if err @oauth2_request options, true, cb return refresh() unless @options.token.access options.headers = {} unless options.headers _(options.headers).extend { Authorization: "Bearer #{@options.token.access}" } operation = retry.operation @retry_options operation.attempt => quest options, (err, resp, body) => # 401 means invalid credentials so we should refresh our token. # But, if we refreshed_already, then we genuinely have invalid credential problems. if not refreshed_already and resp?.statusCode is 401 and @options.token.refresh return refresh() # keep retrying until there is no err and resp.statusCode is not an error code unless operation.retry GoogleAPIAdminSDK.retry_err(err, resp) cb err, resp, body tokeninfo: (cb) => operation = retry.operation @retry_options operation.attempt => quest uri: 'https://www.googleapis.com/oauth2/v1/tokeninfo' qs: { access_token: @options.token.access } , (err, resp, body) => unless operation.retry GoogleAPIAdminSDK.retry_err(err, resp) (GoogleAPIAdminSDK.response_handler(cb)) err, resp, body module.exports = GoogleAPIAdminSDK