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 diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index 8047f387c6..bf9b88140d 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -4,16 +4,16 @@ 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} +// - consumerKey (String): oauth consumer key +// - secret (String): oauth consumer secret // @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; }; @@ -27,15 +27,26 @@ 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? 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; }; -OAuth1Binding.prototype.prepareAccessToken = function(query) { +OAuth1Binding.prototype.prepareAccessToken = function(query, requestTokenSecret) { var self = this; + // 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; + var headers = self._buildHeader({ oauth_token: query.oauth_token }); @@ -76,7 +87,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 +109,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,8 +119,14 @@ OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, access OAuth1Binding.prototype._call = function(method, url, headers, params, callback) { var self = this; + // 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 2e2a530020..6a61fbd6c7 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -12,8 +12,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 +21,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 +43,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,17 +55,17 @@ 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); // 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 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",