{"action":"created","comment":{"url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/comments/207796357","pull_request_review_id":143478629,"id":207796357,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwNzc5NjM1Nw==","diff_hunk":"@@ -0,0 +1,846 @@\n+(function (factory) {\n+  'use strict';\n+  if (typeof define === 'function' && define.amd) {\n+    // AMD. Register as an anonymous module.\n+    define([\n+      'jquery',\n+      'jquery-deparam',\n+      'pubsub-js'\n+    ], factory);\n+  } else if (typeof exports === 'object') {\n+    // Node/CommonJS\n+    module.exports = factory(\n+      require('jquery'),\n+      require('jquery-deparam'),\n+      require('pubsub-js')\n+    );\n+  } else {\n+    // Browser globals\n+    factory(window.jQuery, window.deparam, window.PubSub);\n+  }\n+}(function ($, deparam) {\n+  var root = Function('return this')(); // jshint ignore:line\n+\n+  // singleton baby\n+  if (root.ret) {\n+    return root.ret;\n+  }\n+\n+  // use for IE detection\n+  var nav = root.navigator;\n+\n+  // cookie/localStorage value keys\n+  var INITIAL_CONFIG_KEY  = 'default',\n+      SAVED_CONFIG_KEY    = 'currentConfigName',\n+      SAVED_CREDS_KEY     = 'authHeaders';\n+\n+  // broadcast message event name constants (use constants to avoid typos)\n+  var VALIDATION_SUCCESS             = 'auth.validation.success',\n+      VALIDATION_ERROR               = 'auth.validation.error';\n+\n+  var Ret = function () {\n+    // set flag so we know when plugin has been configured.\n+    this.configured = false;\n+\n+    // create promise for configuration + verification\n+    this.configDfd = null;\n+\n+    // configs hash allows for multiple configurations\n+    this.configs = {};\n+\n+    // default config will be first named config or \"default\"\n+    this.defaultConfigKey = null;\n+\n+    // save reference to user\n+    this.user = {};\n+\n+    // base config from which other configs are extended\n+    this.configBase = {\n+      apiUrl:                '/api',\n+      storage:               'cookies',\n+      proxyIf:               function() { return false; },\n+      proxyUrl:              '/proxy',\n+\n+      // TODO: add userId when available\n+      tokenFormat: {\n+        \"X-User-Id\": \"{{ user-id }}\",\n+        \"X-Initial-Nonce\": \"{{ initial-nonce }}\",\n+        \"X-Encrypted-Token\": \"{{ encrypted-token }}\"\n+      }\n+\n+    };\n+  };\n+\n+\n+  // mostly for testing. reset all config values\n+  Ret.prototype.reset = function() {\n+    // clean up session without relying on `getConfig`\n+    this.destroySession();\n+\n+    this.configs           = {};\n+\n+    // remove event listeners\n+    $(document).unbind('ajaxComplete', this.updateRetCredentials);\n+\n+    if (root.removeEventListener) {\n+      root.removeEventListener('message', this.handlePostMessage);\n+    }\n+\n+    // remove global ajax \"interceptors\"\n+    $.ajaxSetup({beforeSend: undefined});\n+  };\n+\n+\n+  Ret.prototype.invalidateTokens = function() {\n+    // clear user object, but don't destroy object in case of bindings\n+    for (var key in this.user) {\n+      delete this.user[key];\n+    }\n+\n+    // clear auth session data\n+    this.deleteData(SAVED_CONFIG_KEY);\n+    this.deleteData(SAVED_CREDS_KEY);\n+  };\n+\n+\n+  // throw clear errors when dependencies are not met\n+  Ret.prototype.checkDependencies = function() {\n+    var errors = [],\n+      warnings = [];\n+\n+      if (!$) {\n+        throw 'RET: jQuery not found. This module depends on jQuery.';\n+      }\n+\n+      if (!root.localStorage && !$.cookie) {\n+        errors.push(\n+          'This browser does not support localStorage. You must install '+\n+            'jquery-cookie to use RET with this browser.'\n+        );\n+      }\n+\n+      if (!deparam) {\n+        errors.push('Dependency not met: jquery-deparam.');\n+      }\n+\n+      if (errors.length) {\n+        var errMessage = errors.join(' ');\n+        throw 'RET: Please resolve the following errors: ' + errMessage;\n+      }\n+\n+      if (warnings.length && console && console.warn) {\n+        var warnMessage = warnings.join(' ');\n+        console.warn('RET: Warning: ' + warnMessage);\n+      }\n+  };\n+\n+  // need a way to destroy the current session without relying on `getConfig`.\n+  // otherwise we get into infinite loop territory.\n+  Ret.prototype.destroySession = function() {\n+    var sessionKeys = [\n+      SAVED_CREDS_KEY,\n+      SAVED_CONFIG_KEY\n+    ];\n+\n+    for (var key in sessionKeys) {\n+      key = sessionKeys[key];\n+\n+      // kill all local storage keys\n+      if (root.localStorage) {\n+        root.localStorage.removeItem(key);\n+      }\n+\n+      if ($.cookie) {\n+        // each config may have different cookiePath settings\n+        for (var config in this.configs) {\n+          var cookiePath = this.configs[config].cookiePath;\n+\n+          $.removeCookie(key, {\n+            path: cookiePath\n+          });\n+        }\n+\n+        // remove from base path in case config is not specified\n+        $.removeCookie(key, {\n+          path: \"/\"\n+        });\n+      }\n+    }\n+  };\n+\n+\n+  Ret.prototype.configure = function(opts, reset) {\n+    // destroy all session data. useful for testing\n+    if (reset) {\n+      this.reset();\n+    }\n+\n+    if (this.configured) {\n+      return this.configDfd;\n+    }\n+\n+    // set flag so configure isn't called again (unless reset)\n+    this.configured = true;\n+\n+    // normalize opts into object object\n+    if (!opts) {\n+      opts = {};\n+    }\n+\n+    // normalize so opts is always an array of objects\n+    if (opts.constructor !== Array) {\n+      // single config will always be called 'default' unless set\n+      // by previous session\n+      this.defaultConfigKey = INITIAL_CONFIG_KEY;\n+\n+      // config should look like {default: {...}}\n+      var defaultConfig = {};\n+      defaultConfig[this.defaultConfigKey] = opts;\n+\n+      // opts should look like [{default: {...}}]\n+      opts = [defaultConfig];\n+    }\n+\n+    // iterate over config items, extend each from defaults\n+    for (var i = 0; i < opts.length; i++) {\n+      var configName = getFirstObjectKey(opts[i]);\n+\n+      // set first set as default config\n+      if (!this.defaultConfigKey) {\n+        this.defaultConfigKey = configName;\n+      }\n+\n+      // save config to `configs` hash\n+      this.configs[configName] = $.extend(\n+        {}, this.configBase, opts[i][configName]\n+      );\n+    }\n+\n+    // ensure that setup requirements have been met\n+    this.checkDependencies();\n+\n+    // TODO: add config option for these bindings\n+    if (true) {\n+      // update auth creds after each request to the API\n+      $(document).ajaxComplete(root.ret.updateRetCredentials);\n+\n+      // intercept requests to the API, append auth headers\n+      $.ajaxSetup({beforeSend: root.ret.appendHeaders});\n+    }\n+\n+    // IE8 won't have this feature\n+    if (root.addEventListener) {\n+      root.addEventListener(\"message\", this.handlePostMessage, false);\n+    }\n+\n+    // pull creds from search bar if available\n+    this.processSearchParams();\n+\n+    // don't validate the token if we're just going to redirect anyway.\n+    // otherwise the page won't have time to process the response header and\n+    // the token may expire before the redirected page can validate.\n+    if (this.willRedirect) {\n+      return false;\n+    }\n+\n+    // don't validate with the server if the credentials were provided. this is\n+    // a case where the validation happened on the server and is being used to\n+    // initialize the client.\n+    else if (this.getConfig().initialCredentials) {\n+      // skip initial headers check (i.e. check was already done server-side)\n+      var c = this.getConfig();\n+      return new $.Deferred().resolve(c.initialCredentials.user);\n+    }\n+\n+    // otherwise check with server if any existing tokens are found\n+    else {\n+      // validate token if set\n+      this.configDfd = this.validateToken({config: this.getCurrentConfigName()});\n+      return this.configDfd;\n+    }\n+  };\n+\n+\n+  Ret.prototype.getApiUrl = function() {\n+    var config = this.getConfig();\n+    return (config.proxyIf()) ? config.proxyUrl : config.apiUrl;\n+  };\n+\n+\n+  // interpolate values of tokenFormat hash with ctx, return new hash\n+  Ret.prototype.buildRetHeaders = function(ctx) {\n+    var headers = {},\n+      fmt = this.getConfig().tokenFormat;\n+\n+      for (var key in fmt) {\n+        headers[key] = tmpl(fmt[key], ctx);\n+      }\n+\n+      return headers;\n+  };\n+\n+\n+\n+  Ret.prototype.handlePostMessage = function(ev) {\n+    var stopListening = false;\n+\n+    if (ev.data.message === 'deliverCredentials') {\n+      delete ev.data.message;\n+\n+      var initialHeaders = root.ret.normalizeTokenKeys(ev.data),\n+          authHeaders    = root.ret.buildRetHeaders(initialHeaders);\n+\n+      root.ret.broadcastEvent(VALIDATION_SUCCESS, user);\n+\n+      stopListening = true;\n+    }\n+\n+    if (ev.data.message === 'authFailure') {\n+\n+      stopListening = true;\n+    }\n+\n+  };\n+\n+\n+  // compensate for poor naming decisions made early on\n+  // TODO: fix API so this isn't necessary\n+  Ret.prototype.normalizeTokenKeys = function(params) {\n+    // normalize keys\n+    if (params.config) {\n+      this.persistData(\n+        SAVED_CONFIG_KEY,\n+        params.config,\n+        params.config\n+      );\n+      delete params.config;\n+    }\n+\n+\n+    return params;\n+  };\n+\n+\n+  Ret.prototype.processSearchParams = function() {\n+    var searchParams  = this.getQs(),\n+        newHeaders    = null;\n+\n+    searchParams = this.normalizeTokenKeys(searchParams);\n+\n+    // only bother with this if minimum search params are present\n+    if (searchParams['client-nonce'] && searchParams.uid) {\n+      newHeaders = this.buildRetHeaders(searchParams);\n+\n+      // save all token headers to session\n+      this.persistData(SAVED_CREDS_KEY, newHeaders);\n+\n+      // TODO: set uri flag on devise_token_auth for ORet confirmation\n+      // when using hard page redirects.\n+\n+      // set qs without auth keys/values\n+      var newLocation = this.getLocationWithoutParams([\n+        'client-nonce',\n+        'token',\n+        'auth_token',\n+        'config',\n+        'client',\n+        'client_id',\n+        'expiry',\n+        'uid',\n+        'reset_password',\n+        'account_confirmation_success'\n+      ]);\n+\n+      this.willRedirect = true;\n+      this.setLocation(newLocation);\n+    }\n+\n+    return newHeaders;\n+  };\n+\n+\n+  // this method is tricky. we want to reconstruct the current URL with the\n+  // following conditions:\n+  // 1. search contains none of the supplied keys\n+  // 2. anchor search (i.e. `#/?key=val`) contains none of the supplied keys\n+  // 3. all of the keys NOT supplied are presevered in their original form\n+  // 4. url protocol, host, and path are preserved\n+  Ret.prototype.getLocationWithoutParams = function(keys) {\n+    // strip all values from both actual and anchor search params\n+    var newSearch   = $.param(this.stripKeys(this.getSearchQs(), keys)),\n+        newAnchorQs = $.param(this.stripKeys(this.getAnchorQs(), keys)),\n+        newAnchor   = root.location.hash.split('?')[0];\n+\n+    if (newSearch) {\n+      newSearch = \"?\" + newSearch;\n+    }\n+\n+    if (newAnchorQs) {\n+      newAnchor += \"?\" + newAnchorQs;\n+    }\n+\n+    if (newAnchor && !newAnchor.match(/^#/)) {\n+      newAnchor = \"#/\" + newAnchor;\n+    }\n+\n+    // reconstruct location with stripped auth keys\n+    var newLocation = root.location.protocol +\n+        '//'+\n+        root.location.host+\n+        root.location.pathname+\n+        newSearch+\n+        newAnchor;\n+\n+    return newLocation;\n+  };\n+\n+\n+  Ret.prototype.stripKeys = function(obj, keys) {\n+    for (var q in keys) {\n+      delete obj[keys[q]];\n+    }\n+\n+    return obj;\n+  };\n+\n+\n+  // abstract publish method, only use if pubsub exists.\n+  // TODO: allow broadcast method to be configured\n+  Ret.prototype.broadcastEvent = function(msg, data) {\n+    if (PubSub.publish) {\n+      PubSub.publish(msg, data);\n+    }\n+  };\n+\n+\n+\n+  // always resolve after 0 timeout to ensure that ajaxComplete callback\n+  // has run before promise is resolved\n+  Ret.prototype.resolvePromise = function(evMsg, dfd, data) {\n+    var self = this,\n+        finished = $.Deferred();\n+\n+    setTimeout(function() {\n+      self.broadcastEvent(evMsg, data);\n+      dfd.resolve(data);\n+      finished.resolve();\n+    }, 0);\n+\n+    return finished.promise();\n+  };\n+\n+\n+  Ret.prototype.rejectPromise = function(evMsg, dfd, data, reason) {\n+    var self = this;\n+\n+    // jQuery has a strange way of returning error responses...\n+    data = $.parseJSON((data && data.responseText) || '{}');\n+\n+    // always reject after 0 timeout to ensure that ajaxComplete callback\n+    // has run before promise is rejected\n+    setTimeout(function() {\n+      self.broadcastEvent(evMsg, data);\n+      dfd.reject({\n+        reason: reason,\n+        data: data\n+      });\n+    }, 0);\n+\n+    return dfd;\n+  };\n+\n+\n+  // TODO: document\n+  Ret.prototype.validateToken = function(opts) {\n+    if (!opts) {\n+      opts = {};\n+    }\n+\n+    if (!opts.config) {\n+      opts.config = this.getCurrentConfigName();\n+    }\n+\n+    // if this check is already in progress, return existing promise\n+    if (this.configDfd) {\n+      return this.configDfd;\n+    }\n+\n+    var dfd = $.Deferred();\n+\n+    // no creds, reject promise without making API call\n+    if (!this.retrieveData(SAVED_CREDS_KEY)) {\n+      // clear any saved session data\n+      this.invalidateTokens();\n+\n+      // reject promise, broadcast event\n+      this.rejectPromise(\n+        VALIDATION_ERROR,\n+        dfd,\n+        {},\n+        'Cannot validate token; no token found.'\n+      );\n+    } else {\n+      var config = this.getConfig(opts.config),\n+          url    = this.getApiUrl() + config.tokenValidationPath;\n+\n+      // found saved creds, verify with API\n+      $.ajax({\n+        url:     url,\n+        context: this,\n+\n+        success: function(resp) {\n+          var user = config.handleTokenValidationResponse(resp);\n+\n+          this.resolvePromise(VALIDATION_SUCCESS, dfd, this.user);\n+        },\n+\n+        error: function(resp) {\n+          // clear any saved session data\n+          this.invalidateTokens();\n+\n+          this.rejectPromise(\n+            VALIDATION_ERROR,\n+            dfd,\n+            resp,\n+            'Cannot validate token; token rejected by server.'\n+          );\n+        }\n+      });\n+    }\n+\n+    return dfd.promise();\n+  };\n+\n+\n+\n+  // abstract storing of session data\n+  Ret.prototype.persistData = function(key, val, config) {\n+    val = JSON.stringify(val);\n+\n+    switch (this.getConfig(config).storage) {\n+      case 'localStorage':\n+        root.localStorage.setItem(key, val);\n+        break;\n+\n+      default:\n+        $.cookie(key, val, {\n+          expires: this.getConfig(config).cookieExpiry,\n+          path:    this.getConfig(config).cookiePath\n+        });\n+        break;\n+    }\n+  };\n+\n+\n+  // abstract reading of session data\n+  Ret.prototype.retrieveData = function(key) {\n+    var val = null;\n+\n+    switch (this.getConfig().storage) {\n+      case 'localStorage':\n+        val = root.localStorage.getItem(key);\n+        break;\n+\n+      default:\n+        val = $.cookie(key);\n+        break;\n+    }\n+\n+    // if value is a simple string, the parser will fail. in that case, simply\n+    // unescape the quotes and return the string.\n+    try {\n+      // return parsed json response\n+      return $.parseJSON(val);\n+    } catch (err) {\n+      // unescape quotes\n+      return unescapeQuotes(val);\n+    }\n+  };\n+\n+\n+  // this method cannot rely on `retrieveData` because `retrieveData` relies\n+  // on `getConfig` and we need to get the config name before `getConfig` can\n+  // be called. TL;DR prevent infinite loop by checking all forms of storage\n+  // and returning the first config name found\n+  Ret.prototype.getCurrentConfigName = function() {\n+    var configName = null;\n+\n+    if (this.getQs().config) {\n+      configName = this.getQs().config;\n+    }\n+\n+    if ($.cookie && !configName) {\n+      configName = $.cookie(SAVED_CONFIG_KEY);\n+    }\n+\n+    if (root.localStorage && !configName) {\n+      configName = root.localStorage.getItem(SAVED_CONFIG_KEY);\n+    }\n+\n+    configName = configName || this.defaultConfigKey || INITIAL_CONFIG_KEY;\n+\n+    return unescapeQuotes(configName);\n+  };\n+\n+\n+  // abstract deletion of session data\n+  Ret.prototype.deleteData = function(key) {\n+    switch (this.getConfig().storage) {\n+      case 'cookies':\n+        $.removeCookie(key, {\n+          path: this.getConfig().cookiePath\n+        });\n+        break;\n+\n+      default:\n+        root.localStorage.removeItem(key);\n+        break;\n+    }\n+  };\n+\n+\n+  // return the current config. config will take the following precedence:\n+  // 1. config by name saved in cookie / localstorage (current auth)\n+  // 2. first available configuration\n+  // 2. default config\n+  Ret.prototype.getConfig = function(key) {\n+    // configure if not configured\n+    if (!this.configured) {\n+      throw 'RET: `configure` must be run before using this plugin.';\n+    }\n+\n+    // fall back to default unless config key is passed\n+    key = key || this.getCurrentConfigName();\n+\n+    return this.configs[key];\n+  };\n+\n+\n+  // send auth credentials with all requests to the API\n+  Ret.prototype.appendHeaders = function(xhr, settings) {\n+    // fetch current auth headers from storage\n+    var currentHeaders = root.ret.retrieveData(SAVED_CREDS_KEY);\n+\n+    // don't leak tokens in GET / HEAD requests.\n+    var method = settings.type;\n+    if (currentHeaders !== null && ['GET', 'HEAD'].includes(method)) {\n+      return root.ret.appendRetAuthHeaders(xhr, settings);\n+    }\n+    else {\n+      return root.ret.appendRetHeaders(xhr, settings);\n+    }\n+  };\n+\n+  Ret.prototype.generateNonce = function() {\n+    console.warn('This implementation is TEMPORARY.  DO NOT rely on this!');\n+    return Math.random() + \"\";\n+  },\n+\n+  // send auth credentials with all requests to the API\n+  Ret.prototype.appendRetHeaders = function(xhr, settings) {\n+    xhr.setRequestHeader(\n+      'X-Initial-Nonce',\n+      root.ret.generateNonce()\n+    );\n+\n+    // set header for each key in `tokenFormat` config\n+    // for (var key in root.ret.getConfig().tokenFormat) {\n+    //   xhr.setRequestHeader(key, currentHeaders[key]);\n+    // }\n+  };","path":"app/assets/csrf-et.js","position":650,"original_position":650,"commit_id":"22fa664f3ff8a313e977bb6776c145a901c00ae8","original_commit_id":"22fa664f3ff8a313e977bb6776c145a901c00ae8","user":{"login":"houndci-bot","id":6697940,"node_id":"MDQ6VXNlcjY2OTc5NDA=","avatar_url":"https://avatars0.githubusercontent.com/u/6697940?v=4","gravatar_id":"","url":"https://api.github.com/users/houndci-bot","html_url":"https://github.com/houndci-bot","followers_url":"https://api.github.com/users/houndci-bot/followers","following_url":"https://api.github.com/users/houndci-bot/following{/other_user}","gists_url":"https://api.github.com/users/houndci-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci-bot/subscriptions","organizations_url":"https://api.github.com/users/houndci-bot/orgs","repos_url":"https://api.github.com/users/houndci-bot/repos","events_url":"https://api.github.com/users/houndci-bot/events{/privacy}","received_events_url":"https://api.github.com/users/houndci-bot/received_events","type":"User","site_admin":false},"body":"Expected an assignment or function call and instead saw an expression.","created_at":"2018-08-06T07:32:05Z","updated_at":"2018-08-06T07:32:05Z","html_url":"https://github.com/riboseinc/rails_encrypted_token/pull/7#discussion_r207796357","pull_request_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/comments/207796357"},"html":{"href":"https://github.com/riboseinc/rails_encrypted_token/pull/7#discussion_r207796357"},"pull_request":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7"}}},"pull_request":{"url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7","id":206311843,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA2MzExODQz","html_url":"https://github.com/riboseinc/rails_encrypted_token/pull/7","diff_url":"https://github.com/riboseinc/rails_encrypted_token/pull/7.diff","patch_url":"https://github.com/riboseinc/rails_encrypted_token/pull/7.patch","issue_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/7","number":7,"state":"closed","locked":false,"title":"RET fully working except specs","user":{"login":"ronaldtse","id":11865,"node_id":"MDQ6VXNlcjExODY1","avatar_url":"https://avatars1.githubusercontent.com/u/11865?v=4","gravatar_id":"","url":"https://api.github.com/users/ronaldtse","html_url":"https://github.com/ronaldtse","followers_url":"https://api.github.com/users/ronaldtse/followers","following_url":"https://api.github.com/users/ronaldtse/following{/other_user}","gists_url":"https://api.github.com/users/ronaldtse/gists{/gist_id}","starred_url":"https://api.github.com/users/ronaldtse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ronaldtse/subscriptions","organizations_url":"https://api.github.com/users/ronaldtse/orgs","repos_url":"https://api.github.com/users/ronaldtse/repos","events_url":"https://api.github.com/users/ronaldtse/events{/privacy}","received_events_url":"https://api.github.com/users/ronaldtse/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-08-06T07:31:53Z","updated_at":"2018-08-06T07:32:05Z","closed_at":"2018-08-06T07:32:05Z","merged_at":null,"merge_commit_sha":"b72a5a342a63f66ec42160fe286b3e9419015805","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7/commits","review_comments_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7/comments","review_comment_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/comments{/number}","comments_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/7/comments","statuses_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/statuses/22fa664f3ff8a313e977bb6776c145a901c00ae8","head":{"label":"riboseinc:wip","ref":"wip","sha":"22fa664f3ff8a313e977bb6776c145a901c00ae8","user":{"login":"riboseinc","id":24904375,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0OTA0Mzc1","avatar_url":"https://avatars0.githubusercontent.com/u/24904375?v=4","gravatar_id":"","url":"https://api.github.com/users/riboseinc","html_url":"https://github.com/riboseinc","followers_url":"https://api.github.com/users/riboseinc/followers","following_url":"https://api.github.com/users/riboseinc/following{/other_user}","gists_url":"https://api.github.com/users/riboseinc/gists{/gist_id}","starred_url":"https://api.github.com/users/riboseinc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/riboseinc/subscriptions","organizations_url":"https://api.github.com/users/riboseinc/orgs","repos_url":"https://api.github.com/users/riboseinc/repos","events_url":"https://api.github.com/users/riboseinc/events{/privacy}","received_events_url":"https://api.github.com/users/riboseinc/received_events","type":"Organization","site_admin":false},"repo":{"id":129228186,"node_id":"MDEwOlJlcG9zaXRvcnkxMjkyMjgxODY=","name":"rails_encrypted_token","full_name":"riboseinc/rails_encrypted_token","owner":{"login":"riboseinc","id":24904375,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0OTA0Mzc1","avatar_url":"https://avatars0.githubusercontent.com/u/24904375?v=4","gravatar_id":"","url":"https://api.github.com/users/riboseinc","html_url":"https://github.com/riboseinc","followers_url":"https://api.github.com/users/riboseinc/followers","following_url":"https://api.github.com/users/riboseinc/following{/other_user}","gists_url":"https://api.github.com/users/riboseinc/gists{/gist_id}","starred_url":"https://api.github.com/users/riboseinc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/riboseinc/subscriptions","organizations_url":"https://api.github.com/users/riboseinc/orgs","repos_url":"https://api.github.com/users/riboseinc/repos","events_url":"https://api.github.com/users/riboseinc/events{/privacy}","received_events_url":"https://api.github.com/users/riboseinc/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/riboseinc/rails_encrypted_token","description":"encrypted token-based CSRF for Rails","fork":false,"url":"https://api.github.com/repos/riboseinc/rails_encrypted_token","forks_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/forks","keys_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/keys{/key_id}","collaborators_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/teams","hooks_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/hooks","issue_events_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/events{/number}","events_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/events","assignees_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/assignees{/user}","branches_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/branches{/branch}","tags_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/tags","blobs_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/refs{/sha}","trees_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/trees{/sha}","statuses_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/statuses/{sha}","languages_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/languages","stargazers_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/stargazers","contributors_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/contributors","subscribers_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/subscribers","subscription_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/subscription","commits_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/commits{/sha}","git_commits_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/commits{/sha}","comments_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/comments{/number}","issue_comment_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/comments{/number}","contents_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/contents/{+path}","compare_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/compare/{base}...{head}","merges_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/merges","archive_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/downloads","issues_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues{/number}","pulls_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls{/number}","milestones_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/milestones{/number}","notifications_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/labels{/name}","releases_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/releases{/id}","deployments_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/deployments","created_at":"2018-04-12T09:38:16Z","updated_at":"2018-06-29T06:40:49Z","pushed_at":"2018-08-06T07:32:05Z","git_url":"git://github.com/riboseinc/rails_encrypted_token.git","ssh_url":"git@github.com:riboseinc/rails_encrypted_token.git","clone_url":"https://github.com/riboseinc/rails_encrypted_token.git","svn_url":"https://github.com/riboseinc/rails_encrypted_token","homepage":"","size":29,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":2,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"riboseinc:master","ref":"master","sha":"eee57c6e0f2b879a49f829cdfb3ccac9fa5002ef","user":{"login":"riboseinc","id":24904375,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0OTA0Mzc1","avatar_url":"https://avatars0.githubusercontent.com/u/24904375?v=4","gravatar_id":"","url":"https://api.github.com/users/riboseinc","html_url":"https://github.com/riboseinc","followers_url":"https://api.github.com/users/riboseinc/followers","following_url":"https://api.github.com/users/riboseinc/following{/other_user}","gists_url":"https://api.github.com/users/riboseinc/gists{/gist_id}","starred_url":"https://api.github.com/users/riboseinc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/riboseinc/subscriptions","organizations_url":"https://api.github.com/users/riboseinc/orgs","repos_url":"https://api.github.com/users/riboseinc/repos","events_url":"https://api.github.com/users/riboseinc/events{/privacy}","received_events_url":"https://api.github.com/users/riboseinc/received_events","type":"Organization","site_admin":false},"repo":{"id":129228186,"node_id":"MDEwOlJlcG9zaXRvcnkxMjkyMjgxODY=","name":"rails_encrypted_token","full_name":"riboseinc/rails_encrypted_token","owner":{"login":"riboseinc","id":24904375,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI0OTA0Mzc1","avatar_url":"https://avatars0.githubusercontent.com/u/24904375?v=4","gravatar_id":"","url":"https://api.github.com/users/riboseinc","html_url":"https://github.com/riboseinc","followers_url":"https://api.github.com/users/riboseinc/followers","following_url":"https://api.github.com/users/riboseinc/following{/other_user}","gists_url":"https://api.github.com/users/riboseinc/gists{/gist_id}","starred_url":"https://api.github.com/users/riboseinc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/riboseinc/subscriptions","organizations_url":"https://api.github.com/users/riboseinc/orgs","repos_url":"https://api.github.com/users/riboseinc/repos","events_url":"https://api.github.com/users/riboseinc/events{/privacy}","received_events_url":"https://api.github.com/users/riboseinc/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/riboseinc/rails_encrypted_token","description":"encrypted token-based CSRF for Rails","fork":false,"url":"https://api.github.com/repos/riboseinc/rails_encrypted_token","forks_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/forks","keys_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/keys{/key_id}","collaborators_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/teams","hooks_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/hooks","issue_events_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/events{/number}","events_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/events","assignees_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/assignees{/user}","branches_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/branches{/branch}","tags_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/tags","blobs_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/refs{/sha}","trees_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/trees{/sha}","statuses_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/statuses/{sha}","languages_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/languages","stargazers_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/stargazers","contributors_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/contributors","subscribers_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/subscribers","subscription_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/subscription","commits_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/commits{/sha}","git_commits_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/git/commits{/sha}","comments_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/comments{/number}","issue_comment_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/comments{/number}","contents_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/contents/{+path}","compare_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/compare/{base}...{head}","merges_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/merges","archive_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/downloads","issues_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues{/number}","pulls_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls{/number}","milestones_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/milestones{/number}","notifications_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/labels{/name}","releases_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/releases{/id}","deployments_url":"https://api.github.com/repos/riboseinc/rails_encrypted_token/deployments","created_at":"2018-04-12T09:38:16Z","updated_at":"2018-06-29T06:40:49Z","pushed_at":"2018-08-06T07:32:05Z","git_url":"git://github.com/riboseinc/rails_encrypted_token.git","ssh_url":"git@github.com:riboseinc/rails_encrypted_token.git","clone_url":"https://github.com/riboseinc/rails_encrypted_token.git","svn_url":"https://github.com/riboseinc/rails_encrypted_token","homepage":"","size":29,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":2,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7"},"html":{"href":"https://github.com/riboseinc/rails_encrypted_token/pull/7"},"issue":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/7"},"comments":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/issues/7/comments"},"review_comments":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7/comments"},"review_comment":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/pulls/7/commits"},"statuses":{"href":"https://api.github.com/repos/riboseinc/rails_encrypted_token/statuses/22fa664f3ff8a313e977bb6776c145a901c00ae8"}},"author_association":"NONE"}}
{"actor":{"display_login":"houndci-bot"}}