From 7bf11e35a6c1ca5996802a43b3a2b6c61475e031 Mon Sep 17 00:00:00 2001 From: Robert Lowe Date: Fri, 26 Jul 2013 23:43:20 -0400 Subject: [PATCH 1/4] Adds oauth1 improves to support requestTokenSecrets & dynamic urls. Atmosphere package: https://github.com/RobertLowe/meteor-accounts-trello Fixes #1167 Closes #1227 --- packages/oauth1/oauth1_binding.js | 28 +++++++++++++++++++--------- packages/oauth1/oauth1_server.js | 23 +++++++++++++++++------ packages/oauth1/oauth1_tests.js | 4 +++- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index 8047f387c6..59b6fdb5d4 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -4,19 +4,18 @@ var querystring = Npm.require("querystring"); // An OAuth1 wrapper around http calls which helps get tokens and // takes care of HTTP headers // -// @param consumerKey {String} As supplied by the OAuth1 provider -// @param consumerSecret {String} As supplied by the OAuth1 provider +// @param config {Object} Keys, Secrets, etc // @param urls {Object} // - requestToken (String): url // - authorize (String): url // - accessToken (String): url // - authenticate (String): url -OAuth1Binding = function(consumerKey, consumerSecret, urls) { - this._consumerKey = consumerKey; - this._secret = consumerSecret; +OAuth1Binding = function(config, urls) { + this._config = config; this._urls = urls; }; + OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) { var self = this; @@ -27,15 +26,20 @@ OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) { var response = self._call('POST', self._urls.requestToken, headers); var tokens = querystring.parse(response.content); - // XXX should we also store oauth_token_secret here? + // XXX should we also store oauth_token_secret here? Yes, we should. if (!tokens.oauth_callback_confirmed) throw new Error("oauth_callback_confirmed false when requesting oauth1 token", tokens); self.requestToken = tokens.oauth_token; + self.requestTokenSecret = tokens.oauth_token_secret; }; -OAuth1Binding.prototype.prepareAccessToken = function(query) { +OAuth1Binding.prototype.prepareAccessToken = function(query, requestTokenSecret) { var self = this; + // support implemntations that use request token secrets + if (requestTokenSecret) + self.accessTokenSecret = requestTokenSecret + var headers = self._buildHeader({ oauth_token: query.oauth_token }); @@ -76,7 +80,7 @@ OAuth1Binding.prototype.post = function(url, params, callback) { OAuth1Binding.prototype._buildHeader = function(headers) { var self = this; return _.extend({ - oauth_consumer_key: self._consumerKey, + oauth_consumer_key: self._config.consumerKey, oauth_nonce: Random.id().replace(/\W/g, ''), oauth_signature_method: 'HMAC-SHA1', oauth_timestamp: (new Date().valueOf()/1000).toFixed().toString(), @@ -98,7 +102,7 @@ OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, access self._encodeString(parameters) ].join('&'); - var signingKey = self._encodeString(self._secret) + '&'; + var signingKey = self._encodeString(self._config.secret) + '&'; if (accessTokenSecret) signingKey += self._encodeString(accessTokenSecret); @@ -108,6 +112,12 @@ OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, access OAuth1Binding.prototype._call = function(method, url, headers, params, callback) { var self = this; + + // callback functions to support parameters/customization + if(typeof(url) == "function"){ + url = url(self); + } + // Get the signature headers.oauth_signature = self._getSignature(method, url, headers, self.accessTokenSecret, params); diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 2e2a530020..b4a6ab5f1c 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,5 +1,6 @@ // A place to store request tokens pending verification var requestTokens = {}; +var querystring = Npm.require("querystring"); OAuth1Test = {requestTokens: requestTokens}; @@ -12,8 +13,7 @@ Oauth._requestHandlers['1'] = function (service, query, res) { } var urls = service.urls; - var oauthBinding = new OAuth1Binding( - config.consumerKey, config.secret, urls); + var oauthBinding = new OAuth1Binding(config, urls); if (query.requestTokenAndRedirect) { // step 1 - get and store a request token @@ -22,10 +22,20 @@ Oauth._requestHandlers['1'] = function (service, query, res) { oauthBinding.prepareRequestToken(query.requestTokenAndRedirect); // Keep track of request token so we can verify it on the next step - requestTokens[query.state] = oauthBinding.requestToken; + requestTokens[query.state] = { + requestToken: oauthBinding.requestToken, + requestTokenSecret: oauthBinding.requestTokenSecret + }; + + // support for scope/name parameters + var redirectUrl = undefined; + if(typeof(urls.authenticate) == "function"){ + redirectUrl = urls.authenticate(oauthBinding); + } else { + redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken; + } // redirect to provider login, which will redirect back to "step 2" below - var redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken; res.writeHead(302, {'Location': redirectUrl}); res.end(); } else { @@ -34,7 +44,8 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // token and access token secret and log in as user // Get the user's request token so we can verify it and clear it - var requestToken = requestTokens[query.state]; + var requestToken = requestTokens[query.state].requestToken; + var requestTokenSecret = requestTokens[query.state].requestTokenSecret; delete requestTokens[query.state]; // Verify user authorized access and the oauth_token matches @@ -45,7 +56,7 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // subsequent call to the `login` method will be immediate. // Get the access token for signing requests - oauthBinding.prepareAccessToken(query); + oauthBinding.prepareAccessToken(query, requestTokenSecret); // Run service-specific handler. var oauthResult = service.handleOauthRequest(oauthBinding); diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index d7eb0e1979..272e0cb123 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -40,7 +40,9 @@ Tinytest.add("oauth1 - loginResultForCredentialToken is stored", function (test) }); // simulate logging in using twitterfoo - OAuth1Test.requestTokens[credentialToken] = twitterfooAccessToken; + OAuth1Test.requestTokens[credentialToken] = { + requestToken: twitterfooAccessToken + }; var req = { method: "POST", From 9e0897aecd41109e71c8c148364b4f9bd1b4ccb6 Mon Sep 17 00:00:00 2001 From: Robert Lowe Date: Fri, 30 Aug 2013 16:56:53 -0400 Subject: [PATCH 2/4] Adds documentation for `config` arg of `OAuth1Binding`'s constructor Context: https://github.com/meteor/meteor/pull/1253#commitcomment-3980200 --- packages/oauth1/oauth1_binding.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index 59b6fdb5d4..c3af432318 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -4,7 +4,9 @@ var querystring = Npm.require("querystring"); // An OAuth1 wrapper around http calls which helps get tokens and // takes care of HTTP headers // -// @param config {Object} Keys, Secrets, etc +// @param config {Object} +// - consumerKey (String): oauth consumer key +// - secret (String): oauth consumer secret // @param urls {Object} // - requestToken (String): url // - authorize (String): url @@ -15,7 +17,6 @@ OAuth1Binding = function(config, urls) { this._urls = urls; }; - OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) { var self = this; From 400778a55960f67de4178f0916fe166bd5666c26 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 25 Sep 2013 20:16:19 -0700 Subject: [PATCH 3/4] Cleanup. Whitespace, comments, style, unused variables. --- packages/oauth1/oauth1_binding.js | 22 ++++++++++++++-------- packages/oauth1/oauth1_server.js | 13 ++++++------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index c3af432318..bf9b88140d 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -27,9 +27,10 @@ OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) { var response = self._call('POST', self._urls.requestToken, headers); var tokens = querystring.parse(response.content); - // XXX should we also store oauth_token_secret here? Yes, we should. if (!tokens.oauth_callback_confirmed) - throw new Error("oauth_callback_confirmed false when requesting oauth1 token", tokens); + throw new Error( + "oauth_callback_confirmed false when requesting oauth1 token", tokens); + self.requestToken = tokens.oauth_token; self.requestTokenSecret = tokens.oauth_token_secret; }; @@ -37,9 +38,14 @@ OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) { OAuth1Binding.prototype.prepareAccessToken = function(query, requestTokenSecret) { var self = this; - // support implemntations that use request token secrets + // support implementations that use request token secrets. This is + // read by self._call. + // + // XXX make it a param to call, not something stashed on self? It's + // kinda confusing right now, everything except this is passed as + // arguments, but this is stored. if (requestTokenSecret) - self.accessTokenSecret = requestTokenSecret + self.accessTokenSecret = requestTokenSecret; var headers = self._buildHeader({ oauth_token: query.oauth_token @@ -113,14 +119,14 @@ OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, access OAuth1Binding.prototype._call = function(method, url, headers, params, callback) { var self = this; - - // callback functions to support parameters/customization - if(typeof(url) == "function"){ + // all URLs to be functions to support parameters/customization + if(typeof url === "function") { url = url(self); } // Get the signature - headers.oauth_signature = self._getSignature(method, url, headers, self.accessTokenSecret, params); + headers.oauth_signature = + self._getSignature(method, url, headers, self.accessTokenSecret, params); // Make a authorization string according to oauth1 spec var authString = self._getAuthHeaderString(headers); diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index b4a6ab5f1c..6a61fbd6c7 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,6 +1,5 @@ // A place to store request tokens pending verification var requestTokens = {}; -var querystring = Npm.require("querystring"); OAuth1Test = {requestTokens: requestTokens}; @@ -29,7 +28,7 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // support for scope/name parameters var redirectUrl = undefined; - if(typeof(urls.authenticate) == "function"){ + if(typeof urls.authenticate === "function") { redirectUrl = urls.authenticate(oauthBinding); } else { redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken; @@ -44,7 +43,7 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // token and access token secret and log in as user // Get the user's request token so we can verify it and clear it - var requestToken = requestTokens[query.state].requestToken; + var requestToken = requestTokens[query.state].requestToken; var requestTokenSecret = requestTokens[query.state].requestTokenSecret; delete requestTokens[query.state]; @@ -63,10 +62,10 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // Add the login result to the result map Oauth._loginResultForCredentialToken[query.state] = { - serviceName: service.serviceName, - serviceData: oauthResult.serviceData, - options: oauthResult.options - }; + serviceName: service.serviceName, + serviceData: oauthResult.serviceData, + options: oauthResult.options + }; } // Either close the window, redirect, or render nothing From eda50d2d1ed65ff40f0b85f8dc089d2c50ff6dd9 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 27 Sep 2013 14:38:47 -0700 Subject: [PATCH 4/4] add history.md note --- History.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/History.md b/History.md index 4154280e35..c8c64063d2 100644 --- a/History.md +++ b/History.md @@ -21,6 +21,9 @@ running on Linux machines with glibc 2.9 or newer (Ubuntu 10.04+, RHEL and CentOS 6+, Fedora 10+, Debian 6+). +* Support OAuth1 services that require request token secrets as well as + authentication token secrets. #1253 + ## v0.6.5.1 * Fix syntax errors on lines that end with a backslash. #1326