mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Allow OAuth1 callback to specify query string parameters.
They are then parsed and provided to underlying HTTP package. We have to parse them so that signing of requests works properly. In addition, nonce used in the request is stored in the response so that user can verify JWT payloads returned from the server.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
var crypto = Npm.require("crypto");
|
||||
var querystring = Npm.require("querystring");
|
||||
var urlModule = Npm.require("url");
|
||||
|
||||
// An OAuth1 wrapper around http calls which helps get tokens and
|
||||
// takes care of HTTP headers
|
||||
@@ -27,9 +28,9 @@ OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) {
|
||||
var response = self._call('POST', self._urls.requestToken, headers);
|
||||
var tokens = querystring.parse(response.content);
|
||||
|
||||
if (!tokens.oauth_callback_confirmed)
|
||||
throw new Error(
|
||||
"oauth_callback_confirmed false when requesting oauth1 token", tokens);
|
||||
if (! tokens.oauth_callback_confirmed)
|
||||
throw _.extend(new Error("oauth_callback_confirmed false when requesting oauth1 token"),
|
||||
{response: response});
|
||||
|
||||
self.requestToken = tokens.oauth_token;
|
||||
self.requestTokenSecret = tokens.oauth_token_secret;
|
||||
@@ -55,6 +56,15 @@ OAuth1Binding.prototype.prepareAccessToken = function(query, requestTokenSecret)
|
||||
var response = self._call('POST', self._urls.accessToken, headers);
|
||||
var tokens = querystring.parse(response.content);
|
||||
|
||||
if (! tokens.oauth_token || ! tokens.oauth_token_secret) {
|
||||
var error = new Error("missing oauth token or secret");
|
||||
// We provide response only if no token is available, we do not want to leak any tokens
|
||||
if (! tokens.oauth_token && ! tokens.oauth_token_secret) {
|
||||
_.extend(error, {response: response});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
self.accessToken = tokens.oauth_token;
|
||||
self.accessTokenSecret = tokens.oauth_token_secret;
|
||||
};
|
||||
@@ -66,7 +76,7 @@ OAuth1Binding.prototype.call = function(method, url, params, callback) {
|
||||
oauth_token: self.accessToken
|
||||
});
|
||||
|
||||
if(!params) {
|
||||
if(! params) {
|
||||
params = {};
|
||||
}
|
||||
|
||||
@@ -123,6 +133,20 @@ OAuth1Binding.prototype._call = function(method, url, headers, params, callback)
|
||||
url = url(self);
|
||||
}
|
||||
|
||||
headers = headers || {};
|
||||
params = params || {};
|
||||
|
||||
// Extract all query string parameters from the provided URL
|
||||
var parsedUrl = urlModule.parse(url, true);
|
||||
// Merge them in a way that params given to the method call have precedence
|
||||
params = _.extend({}, parsedUrl.query, params);
|
||||
|
||||
// Reconstruct the URL back without any query string parameters
|
||||
// (they are now in params)
|
||||
parsedUrl.query = {};
|
||||
parsedUrl.search = '';
|
||||
url = urlModule.format(parsedUrl);
|
||||
|
||||
// Get the signature
|
||||
headers.oauth_signature =
|
||||
self._getSignature(method, url, headers, self.accessTokenSecret, params);
|
||||
@@ -132,12 +156,21 @@ OAuth1Binding.prototype._call = function(method, url, headers, params, callback)
|
||||
|
||||
// Make signed request
|
||||
try {
|
||||
return HTTP.call(method, url, {
|
||||
var response = HTTP.call(method, url, {
|
||||
params: params,
|
||||
headers: {
|
||||
Authorization: authString
|
||||
}
|
||||
}, callback);
|
||||
}, callback && function (error, response) {
|
||||
if (! error) {
|
||||
response.nonce = headers.oauth_nonce;
|
||||
}
|
||||
callback(error, response);
|
||||
});
|
||||
// We store nonce so that JWTs can be validated
|
||||
if (response)
|
||||
response.nonce = headers.oauth_nonce;
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw _.extend(new Error("Failed to send OAuth1 request to " + url + ". " + err.message),
|
||||
{response: err.response});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
var url = Npm.require("url");
|
||||
|
||||
// connect middleware
|
||||
OAuth._requestHandlers['1'] = function (service, query, res) {
|
||||
|
||||
var config = ServiceConfiguration.configurations.findOne({service: service.serviceName});
|
||||
if (!config) {
|
||||
if (! config) {
|
||||
throw new ServiceConfiguration.ConfigError(service.serviceName);
|
||||
}
|
||||
|
||||
@@ -29,7 +31,13 @@ OAuth._requestHandlers['1'] = function (service, query, res) {
|
||||
if(typeof urls.authenticate === "function") {
|
||||
redirectUrl = urls.authenticate(oauthBinding);
|
||||
} else {
|
||||
redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken;
|
||||
// Parse the URL to support additional query parameters in urls.authenticate
|
||||
var redirectUrlObj = url.parse(urls.authenticate, true);
|
||||
redirectUrlObj.query = redirectUrlObj.query || {};
|
||||
redirectUrlObj.query.oauth_token = oauthBinding.requestToken;
|
||||
redirectUrlObj.search = '';
|
||||
// Reconstruct the URL back with provided query parameters merged with oauth_token
|
||||
redirectUrl = url.format(redirectUrlObj);
|
||||
}
|
||||
|
||||
// redirect to provider login, which will redirect back to "step 2" below
|
||||
@@ -42,6 +50,10 @@ OAuth._requestHandlers['1'] = function (service, query, res) {
|
||||
// Get the user's request token so we can verify it and clear it
|
||||
var requestTokenInfo = OAuth._retrieveRequestToken(query.state);
|
||||
|
||||
if (! requestTokenInfo) {
|
||||
throw new Error("Unable to retrieve request token");
|
||||
}
|
||||
|
||||
// Verify user authorized access and the oauth_token matches
|
||||
// the requestToken from previous step
|
||||
if (query.oauth_token && query.oauth_token === requestTokenInfo.requestToken) {
|
||||
|
||||
Reference in New Issue
Block a user