{"action":"created","comment":{"url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/comments/41379503","id":41379503,"diff_hunk":"@@ -0,0 +1,310 @@\n+\n+/**\n+ * Module dependencies.\n+ */\n+\n+var _ = require('lodash');\n+var AccessDeniedError = require('../errors/access-denied-error');\n+var AuthenticateHandler = require('../handlers/authenticate-handler');\n+var InvalidArgumentError = require('../errors/invalid-argument-error');\n+var InvalidClientError = require('../errors/invalid-client-error');\n+var InvalidRequestError = require('../errors/invalid-request-error');\n+var InvalidScopeError = require('../errors/invalid-scope-error');\n+var OAuthError = require('../errors/oauth-error');\n+var Promise = require('bluebird');\n+var Request = require('../request');\n+var Response = require('../response');\n+var ServerError = require('../errors/server-error');\n+var UnauthorizedClientError = require('../errors/unauthorized-client-error');\n+var is = require('../validator/is');\n+var tokenUtil = require('../utils/token-util');\n+var url = require('url');\n+\n+/**\n+ * Response types.\n+ */\n+\n+var responseTypes = {\n+  code: require('../response-types/code-response-type'),\n+  token: require('../response-types/token-response-type')\n+};\n+\n+/**\n+ * Constructor.\n+ */\n+\n+function AuthorizeHandler(options) {\n+  options = options || {};\n+\n+  if (!options.authorizationCodeLifetime) {\n+    throw new InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`');\n+  }\n+\n+  if (!options.model) {\n+    throw new InvalidArgumentError('Missing parameter: `model`');\n+  }\n+\n+  if (!options.model.getClient) {\n+    throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`');\n+  }\n+\n+  if (!options.model.saveAuthorizationCode) {\n+    throw new InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`');\n+  }\n+\n+  this.authorizationCodeLifetime = options.authorizationCodeLifetime;\n+  this.authenticateHandler = new AuthenticateHandler(options);\n+  this.model = options.model;\n+}\n+\n+/**\n+ * Authorize Handler.\n+ */\n+\n+AuthorizeHandler.prototype.handle = function(request, response) {\n+  if (!(request instanceof Request)) {\n+    throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');\n+  }\n+\n+  if (!(response instanceof Response)) {\n+    throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');\n+  }\n+\n+  if (false === request.body.allowed) {\n+    return Promise.reject(new AccessDeniedError('Access denied: user denied access to application'));\n+  }\n+\n+  var fns = [\n+    this.generateAuthorizationCode(),\n+    this.getAuthorizationCodeLifetime(),\n+    this.getClient(request),\n+    this.getUser(request, response)\n+  ];\n+\n+  return Promise.all(fns)\n+    .bind(this)\n+    .spread(function(authorizationCode, expiresAt, client, user) {\n+      var scope;\n+      var state;\n+\n+      return Promise.bind(this)\n+        .then(function() {\n+          scope = this.getScope(request);\n+          state = this.getState(request);\n+\n+          return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, user);\n+        })\n+        .then(function(code) {\n+          var responseType = this.getResponseType(request, code);\n+          var redirectUri = this.buildSuccessRedirectUri(client.redirectUri, responseType);\n+\n+          this.updateResponse(response, redirectUri, state);\n+\n+          return code;\n+        })\n+        .catch(function(e) {\n+          if (!(e instanceof OAuthError)) {\n+            e = new ServerError(e);\n+          }\n+\n+          var redirectUri = this.buildErrorRedirectUri(client.redirectUri, e);\n+\n+          this.updateResponse(response, redirectUri, state);\n+\n+          throw e;\n+        });\n+    });\n+};\n+\n+/**\n+ * Generate authorization code.\n+ */\n+\n+AuthorizeHandler.prototype.generateAuthorizationCode = function() {\n+  if (this.model.generateAuthorizationCode) {\n+    return Promise.try(this.model.generateAuthorizationCode);\n+  }\n+\n+  return tokenUtil.generateRandomToken();\n+};\n+\n+/**\n+ * Get authorization code lifetime.\n+ */\n+\n+AuthorizeHandler.prototype.getAuthorizationCodeLifetime = function() {\n+  var expires = new Date();\n+\n+  expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime);\n+\n+  return expires;\n+};\n+\n+/**\n+ * Get the client from the model.\n+ */\n+\n+AuthorizeHandler.prototype.getClient = function(request) {\n+  var clientId = request.body.client_id || request.query.client_id;\n+\n+  if (!clientId) {\n+    throw new InvalidRequestError('Missing parameter: `client_id`');\n+  }\n+\n+  if (!is.vschar(clientId)) {\n+    throw new InvalidRequestError('Invalid parameter: `client_id`');\n+  }\n+\n+  var redirectUri = request.body.redirect_uri || request.query.redirect_uri;\n+\n+  if (redirectUri && !is.uri(redirectUri)) {\n+    throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI');\n+  }\n+\n+  return Promise.try(this.model.getClient, clientId)\n+    .then(function(client) {\n+      if (!client) {\n+        throw new InvalidClientError('Invalid client: client credentials are invalid');\n+      }\n+\n+      if (!client.grants) {\n+        throw new InvalidClientError('Invalid client: missing client `grants`');\n+      }\n+\n+      if (!_.contains(client.grants, 'authorization_code')) {\n+        throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid');\n+      }\n+\n+      if (!client.redirectUri) {\n+        throw new InvalidClientError('Invalid client: missing client `redirectUri`');\n+      }\n+\n+      if (redirectUri && client.redirectUri !== redirectUri) {\n+        throw new InvalidClientError('Invalid client: `redirect_uri` does not match client value');\n+      }\n+\n+      return client;\n+    });\n+};\n+\n+/**\n+ * Get scope from the request.\n+ */\n+\n+AuthorizeHandler.prototype.getScope = function(request) {\n+  var scope = request.body.scope || request.query.scope;\n+\n+  if (!is.nqschar(scope)) {\n+    throw new InvalidScopeError('Invalid parameter: `scope`');\n+  }\n+\n+  return scope;\n+};\n+\n+/**\n+ * Get state from the request.\n+ */\n+\n+AuthorizeHandler.prototype.getState = function(request) {\n+  var state = request.body.state || request.query.state;\n+\n+  if (!state) {\n+    throw new InvalidRequestError('Missing parameter: `state`');\n+  }\n+\n+  if (!is.vschar(state)) {\n+    throw new InvalidRequestError('Invalid parameter: `state`');\n+  }\n+\n+  return state;\n+};\n+\n+/**\n+ * Get user by calling the authenticate middleware.\n+ */\n+\n+AuthorizeHandler.prototype.getUser = function(request, response) {\n+  return this.authenticateHandler.handle(request, response).then(function(token) {\n+    return token.user;\n+  });\n+};\n+\n+/**\n+ * Save authorization code.\n+ */\n+\n+AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, user) {\n+  var code = {\n+    authorizationCode: authorizationCode,\n+    expiresAt: expiresAt,\n+    scope: scope\n+  };\n+\n+  return Promise.try(this.model.saveAuthorizationCode, [code, client, user]);","path":"lib/handlers/authorize-handler.js","position":243,"original_position":243,"commit_id":"bcec6d266aca58d4098b8278540aae5af0c8015e","original_commit_id":"bcec6d266aca58d4098b8278540aae5af0c8015e","user":{"login":"mjsalinger","id":2452620,"avatar_url":"https://avatars.githubusercontent.com/u/2452620?v=3","gravatar_id":"","url":"https://api.github.com/users/mjsalinger","html_url":"https://github.com/mjsalinger","followers_url":"https://api.github.com/users/mjsalinger/followers","following_url":"https://api.github.com/users/mjsalinger/following{/other_user}","gists_url":"https://api.github.com/users/mjsalinger/gists{/gist_id}","starred_url":"https://api.github.com/users/mjsalinger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mjsalinger/subscriptions","organizations_url":"https://api.github.com/users/mjsalinger/orgs","repos_url":"https://api.github.com/users/mjsalinger/repos","events_url":"https://api.github.com/users/mjsalinger/events{/privacy}","received_events_url":"https://api.github.com/users/mjsalinger/received_events","type":"User","site_admin":false},"body":"Might be useful to save redirect uri here as well, as with multiple redirects it is good practice to match them at the token stage.","created_at":"2015-10-07T11:35:50Z","updated_at":"2015-10-07T11:35:50Z","html_url":"https://github.com/thomseddon/node-oauth2-server/pull/203#discussion_r41379503","pull_request_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203","_links":{"self":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/comments/41379503"},"html":{"href":"https://github.com/thomseddon/node-oauth2-server/pull/203#discussion_r41379503"},"pull_request":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203"}}},"pull_request":{"url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203","id":40515432,"html_url":"https://github.com/thomseddon/node-oauth2-server/pull/203","diff_url":"https://github.com/thomseddon/node-oauth2-server/pull/203.diff","patch_url":"https://github.com/thomseddon/node-oauth2-server/pull/203.patch","issue_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues/203","number":203,"state":"open","locked":false,"title":"Refactor project","user":{"login":"nunofgs","id":92085,"avatar_url":"https://avatars.githubusercontent.com/u/92085?v=3","gravatar_id":"","url":"https://api.github.com/users/nunofgs","html_url":"https://github.com/nunofgs","followers_url":"https://api.github.com/users/nunofgs/followers","following_url":"https://api.github.com/users/nunofgs/following{/other_user}","gists_url":"https://api.github.com/users/nunofgs/gists{/gist_id}","starred_url":"https://api.github.com/users/nunofgs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nunofgs/subscriptions","organizations_url":"https://api.github.com/users/nunofgs/orgs","repos_url":"https://api.github.com/users/nunofgs/repos","events_url":"https://api.github.com/users/nunofgs/events{/privacy}","received_events_url":"https://api.github.com/users/nunofgs/received_events","type":"User","site_admin":false},"body":"This PR completely restructures the project into a more modern Promise-based approach. Among many many fixes, it also adds support for scopes, changes the license to MIT, validates user input according to the OAuth spec, removes express-specific middlewares, improves error handling and is fully tested.\r\n\r\nI know this is a monumental PR and is quite a big shift in style and approach, it is my opinion that it greatly enhances this library and further establishes it as the de-facto OAuth server library for node.\r\n\r\nI'd love to know what you think.\r\n\r\n# OAuth2-Server\r\n\r\nIntroduces a new OAuth server implementation with the following changes:\r\n\r\n  * Added a promise-based API which supports both promises and *node-style* callbacks.\r\n  * Added support for scopes for all grant types.\r\n  * Added syntax-checking to all user input as defined in the [spec](https://tools.ietf.org/html/rfc6749#appendix-A).\r\n  * Added OAuth client validation to authorization requests which fixes an edge-case where an auth code would be generated even if the OAuth client did not support *authorization codes*.\r\n  * Fixed authorization requests now defer to the authentication middleware in order to verify that the request is authenticated (in the process, multiple bugs were fixed since the original library was doing this in place and consequently skipping many important checks).\r\n  * Fixed incorrect (non-spec compliant) validation of the *redirectUri* in the *authorization_code* grant.\r\n  * Fixed incorrect handling of custom grant types (the spec requires that extended grants be specified in the form of a *uri*).\r\n  * Fixed non-spec compliant behavior when validating unsupported grant types.\r\n  * Fixed non-[spec](https://tools.ietf.org/html/rfc6749) compliant errors being returned to the response.\r\n  * Fixed objects retrieved from the model are no longer injected into the current execution context and are instead returned in the original promise.\r\n  * Fixed parameters given to model calls are now objects previously fetched from the model which reduces the number of database queries per OAuth request.\r\n  * Fixed tokens are no longer allowed to be passed by GET parameter as recommended by the OAuth spec, for security reasons.\r\n  * Improved *authorization_code* grant by allowing the *redirect_uri* parameter to be omitted (as required by the spec).\r\n  * Improved *authorization_code* grant type by revoking the authorization code immediately upon issuing an access token (as required by the spec).\r\n  * Improved *refresh_token* grant type by revoking the previous *refresh_token* immediately upon issuing an access token (as required by the spec).\r\n  * Improved architecture by separating the *grant-types*, *response-types* and *token-types* into strongly typed classes.\r\n  * Improved error handling by throwing strongly-typed errors so they can be caught in our backend.\r\n  * Improved error handling by wrapping model errors in a *server_error*, as required by the [spec](https://tools.ietf.org/html/rfc6749).\r\n  * Improved validation of all objects retrieved from the model (no validation was being done on expiry dates, for example).\r\n  * Removed dependency on the *express* framework.\r\n  * Removed injection of response body and headers in favor of strongly-typed *Request* and *Response* classes.\r\n  * Renamed *authorise*, *grant* and *authCodeGrant* middlewares to more OAuth spec-friendly *authenticate*, *token* and *authorize*, respectively.","created_at":"2015-07-21T23:14:51Z","updated_at":"2015-10-07T11:35:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"040ee397794352ce7aaf0f3912136e3b8d1e54e6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203/commits","review_comments_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203/comments","review_comment_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/comments{/number}","comments_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues/203/comments","statuses_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/statuses/bcec6d266aca58d4098b8278540aae5af0c8015e","head":{"label":"seegno-forks:enhancement/refactor-project","ref":"enhancement/refactor-project","sha":"bcec6d266aca58d4098b8278540aae5af0c8015e","user":{"login":"seegno-forks","id":10717345,"avatar_url":"https://avatars.githubusercontent.com/u/10717345?v=3","gravatar_id":"","url":"https://api.github.com/users/seegno-forks","html_url":"https://github.com/seegno-forks","followers_url":"https://api.github.com/users/seegno-forks/followers","following_url":"https://api.github.com/users/seegno-forks/following{/other_user}","gists_url":"https://api.github.com/users/seegno-forks/gists{/gist_id}","starred_url":"https://api.github.com/users/seegno-forks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seegno-forks/subscriptions","organizations_url":"https://api.github.com/users/seegno-forks/orgs","repos_url":"https://api.github.com/users/seegno-forks/repos","events_url":"https://api.github.com/users/seegno-forks/events{/privacy}","received_events_url":"https://api.github.com/users/seegno-forks/received_events","type":"Organization","site_admin":false},"repo":{"id":16441877,"name":"node-oauth2-server","full_name":"seegno-forks/node-oauth2-server","owner":{"login":"seegno-forks","id":10717345,"avatar_url":"https://avatars.githubusercontent.com/u/10717345?v=3","gravatar_id":"","url":"https://api.github.com/users/seegno-forks","html_url":"https://github.com/seegno-forks","followers_url":"https://api.github.com/users/seegno-forks/followers","following_url":"https://api.github.com/users/seegno-forks/following{/other_user}","gists_url":"https://api.github.com/users/seegno-forks/gists{/gist_id}","starred_url":"https://api.github.com/users/seegno-forks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seegno-forks/subscriptions","organizations_url":"https://api.github.com/users/seegno-forks/orgs","repos_url":"https://api.github.com/users/seegno-forks/repos","events_url":"https://api.github.com/users/seegno-forks/events{/privacy}","received_events_url":"https://api.github.com/users/seegno-forks/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/seegno-forks/node-oauth2-server","description":"Complete, compliant and well tested module for implementing an OAuth2 Server/Provider with express in node.js","fork":true,"url":"https://api.github.com/repos/seegno-forks/node-oauth2-server","forks_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/forks","keys_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/teams","hooks_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/hooks","issue_events_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/issues/events{/number}","events_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/events","assignees_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/assignees{/user}","branches_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/branches{/branch}","tags_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/tags","blobs_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/statuses/{sha}","languages_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/languages","stargazers_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/stargazers","contributors_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/contributors","subscribers_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/subscribers","subscription_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/subscription","commits_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/contents/{+path}","compare_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/merges","archive_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/downloads","issues_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/issues{/number}","pulls_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/pulls{/number}","milestones_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/milestones{/number}","notifications_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/labels{/name}","releases_url":"https://api.github.com/repos/seegno-forks/node-oauth2-server/releases{/id}","created_at":"2014-02-01T20:24:13Z","updated_at":"2015-08-16T17:10:34Z","pushed_at":"2015-09-24T16:15:45Z","git_url":"git://github.com/seegno-forks/node-oauth2-server.git","ssh_url":"git@github.com:seegno-forks/node-oauth2-server.git","clone_url":"https://github.com/seegno-forks/node-oauth2-server.git","svn_url":"https://github.com/seegno-forks/node-oauth2-server","homepage":"https://npmjs.org/package/node-oauth2-server","size":775,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":2,"forks":2,"open_issues":2,"watchers":2,"default_branch":"master"}},"base":{"label":"thomseddon:master","ref":"master","sha":"b36a06b445ad0a676e6175d68a8bd0b2f3353dbf","user":{"login":"thomseddon","id":747138,"avatar_url":"https://avatars.githubusercontent.com/u/747138?v=3","gravatar_id":"","url":"https://api.github.com/users/thomseddon","html_url":"https://github.com/thomseddon","followers_url":"https://api.github.com/users/thomseddon/followers","following_url":"https://api.github.com/users/thomseddon/following{/other_user}","gists_url":"https://api.github.com/users/thomseddon/gists{/gist_id}","starred_url":"https://api.github.com/users/thomseddon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thomseddon/subscriptions","organizations_url":"https://api.github.com/users/thomseddon/orgs","repos_url":"https://api.github.com/users/thomseddon/repos","events_url":"https://api.github.com/users/thomseddon/events{/privacy}","received_events_url":"https://api.github.com/users/thomseddon/received_events","type":"User","site_admin":false},"repo":{"id":9367940,"name":"node-oauth2-server","full_name":"thomseddon/node-oauth2-server","owner":{"login":"thomseddon","id":747138,"avatar_url":"https://avatars.githubusercontent.com/u/747138?v=3","gravatar_id":"","url":"https://api.github.com/users/thomseddon","html_url":"https://github.com/thomseddon","followers_url":"https://api.github.com/users/thomseddon/followers","following_url":"https://api.github.com/users/thomseddon/following{/other_user}","gists_url":"https://api.github.com/users/thomseddon/gists{/gist_id}","starred_url":"https://api.github.com/users/thomseddon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thomseddon/subscriptions","organizations_url":"https://api.github.com/users/thomseddon/orgs","repos_url":"https://api.github.com/users/thomseddon/repos","events_url":"https://api.github.com/users/thomseddon/events{/privacy}","received_events_url":"https://api.github.com/users/thomseddon/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/thomseddon/node-oauth2-server","description":"Complete, compliant and well tested module for implementing an OAuth2 Server/Provider with express in node.js","fork":false,"url":"https://api.github.com/repos/thomseddon/node-oauth2-server","forks_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/forks","keys_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/teams","hooks_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/hooks","issue_events_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues/events{/number}","events_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/events","assignees_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/assignees{/user}","branches_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/branches{/branch}","tags_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/tags","blobs_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/statuses/{sha}","languages_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/languages","stargazers_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/stargazers","contributors_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/contributors","subscribers_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/subscribers","subscription_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/subscription","commits_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/contents/{+path}","compare_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/merges","archive_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/downloads","issues_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues{/number}","pulls_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls{/number}","milestones_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/milestones{/number}","notifications_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/labels{/name}","releases_url":"https://api.github.com/repos/thomseddon/node-oauth2-server/releases{/id}","created_at":"2013-04-11T10:39:52Z","updated_at":"2015-10-07T05:30:38Z","pushed_at":"2015-09-26T04:45:57Z","git_url":"git://github.com/thomseddon/node-oauth2-server.git","ssh_url":"git@github.com:thomseddon/node-oauth2-server.git","clone_url":"https://github.com/thomseddon/node-oauth2-server.git","svn_url":"https://github.com/thomseddon/node-oauth2-server","homepage":"https://npmjs.org/package/node-oauth2-server","size":1993,"stargazers_count":786,"watchers_count":786,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":277,"mirror_url":null,"open_issues_count":65,"forks":277,"open_issues":65,"watchers":786,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203"},"html":{"href":"https://github.com/thomseddon/node-oauth2-server/pull/203"},"issue":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues/203"},"comments":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/issues/203/comments"},"review_comments":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203/comments"},"review_comment":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/pulls/203/commits"},"statuses":{"href":"https://api.github.com/repos/thomseddon/node-oauth2-server/statuses/bcec6d266aca58d4098b8278540aae5af0c8015e"}}}}