1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/rocketchat_ynh.git synced 2024-09-03 20:16:25 +02:00
rocketchat_ynh/sources/programs/server/packages/oauth1.js
2016-04-29 16:32:48 +02:00

456 lines
44 KiB
JavaScript

(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var Random = Package.random.Random;
var ServiceConfiguration = Package['service-configuration'].ServiceConfiguration;
var OAuth = Package.oauth.OAuth;
var Oauth = Package.oauth.Oauth;
var _ = Package.underscore._;
var check = Package.check.check;
var Match = Package.check.Match;
var HTTP = Package.http.HTTP;
var HTTPInternals = Package.http.HTTPInternals;
var MongoInternals = Package.mongo.MongoInternals;
var Mongo = Package.mongo.Mongo;
/* Package-scope variables */
var OAuth1Binding, OAuth1Test;
(function(){
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/oauth1/oauth1_binding.js //
// //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
var crypto = Npm.require("crypto"); // 1
var querystring = Npm.require("querystring"); // 2
var urlModule = Npm.require("url"); // 3
// 4
// An OAuth1 wrapper around http calls which helps get tokens and // 5
// takes care of HTTP headers // 6
// // 7
// @param config {Object} // 8
// - consumerKey (String): oauth consumer key // 9
// - secret (String): oauth consumer secret // 10
// @param urls {Object} // 11
// - requestToken (String): url // 12
// - authorize (String): url // 13
// - accessToken (String): url // 14
// - authenticate (String): url // 15
OAuth1Binding = function(config, urls) { // 16
this._config = config; // 17
this._urls = urls; // 18
}; // 19
// 20
OAuth1Binding.prototype.prepareRequestToken = function(callbackUrl) { // 21
var self = this; // 22
// 23
var headers = self._buildHeader({ // 24
oauth_callback: callbackUrl // 25
}); // 26
// 27
var response = self._call('POST', self._urls.requestToken, headers); // 28
var tokens = querystring.parse(response.content); // 29
// 30
if (! tokens.oauth_callback_confirmed) // 31
throw _.extend(new Error("oauth_callback_confirmed false when requesting oauth1 token"), // 32
{response: response}); // 33
// 34
self.requestToken = tokens.oauth_token; // 35
self.requestTokenSecret = tokens.oauth_token_secret; // 36
}; // 37
// 38
OAuth1Binding.prototype.prepareAccessToken = function(query, requestTokenSecret) { // 39
var self = this; // 40
// 41
// support implementations that use request token secrets. This is // 42
// read by self._call. // 43
// // 44
// XXX make it a param to call, not something stashed on self? It's // 45
// kinda confusing right now, everything except this is passed as // 46
// arguments, but this is stored. // 47
if (requestTokenSecret) // 48
self.accessTokenSecret = requestTokenSecret; // 49
// 50
var headers = self._buildHeader({ // 51
oauth_token: query.oauth_token, // 52
oauth_verifier: query.oauth_verifier // 53
}); // 54
// 55
var response = self._call('POST', self._urls.accessToken, headers); // 56
var tokens = querystring.parse(response.content); // 57
// 58
if (! tokens.oauth_token || ! tokens.oauth_token_secret) { // 59
var error = new Error("missing oauth token or secret"); // 60
// We provide response only if no token is available, we do not want to leak any tokens // 61
if (! tokens.oauth_token && ! tokens.oauth_token_secret) { // 62
_.extend(error, {response: response}); // 63
} // 64
throw error; // 65
} // 66
// 67
self.accessToken = tokens.oauth_token; // 68
self.accessTokenSecret = tokens.oauth_token_secret; // 69
}; // 70
// 71
OAuth1Binding.prototype.call = function(method, url, params, callback) { // 72
var self = this; // 73
// 74
var headers = self._buildHeader({ // 75
oauth_token: self.accessToken // 76
}); // 77
// 78
if(! params) { // 79
params = {}; // 80
} // 81
// 82
return self._call(method, url, headers, params, callback); // 83
}; // 84
// 85
OAuth1Binding.prototype.get = function(url, params, callback) { // 86
return this.call('GET', url, params, callback); // 87
}; // 88
// 89
OAuth1Binding.prototype.post = function(url, params, callback) { // 90
return this.call('POST', url, params, callback); // 91
}; // 92
// 93
OAuth1Binding.prototype._buildHeader = function(headers) { // 94
var self = this; // 95
return _.extend({ // 96
oauth_consumer_key: self._config.consumerKey, // 97
oauth_nonce: Random.secret().replace(/\W/g, ''), // 98
oauth_signature_method: 'HMAC-SHA1', // 99
oauth_timestamp: (new Date().valueOf()/1000).toFixed().toString(), // 100
oauth_version: '1.0' // 101
}, headers); // 102
}; // 103
// 104
OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, accessTokenSecret, params) {
var self = this; // 106
var headers = self._encodeHeader(_.extend({}, rawHeaders, params)); // 107
// 108
var parameters = _.map(headers, function(val, key) { // 109
return key + '=' + val; // 110
}).sort().join('&'); // 111
// 112
var signatureBase = [ // 113
method, // 114
self._encodeString(url), // 115
self._encodeString(parameters) // 116
].join('&'); // 117
// 118
var secret = OAuth.openSecret(self._config.secret); // 119
// 120
var signingKey = self._encodeString(secret) + '&'; // 121
if (accessTokenSecret) // 122
signingKey += self._encodeString(accessTokenSecret); // 123
// 124
return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64'); // 125
}; // 126
// 127
OAuth1Binding.prototype._call = function(method, url, headers, params, callback) { // 128
var self = this; // 129
// 130
// all URLs to be functions to support parameters/customization // 131
if(typeof url === "function") { // 132
url = url(self); // 133
} // 134
// 135
headers = headers || {}; // 136
params = params || {}; // 137
// 138
// Extract all query string parameters from the provided URL // 139
var parsedUrl = urlModule.parse(url, true); // 140
// Merge them in a way that params given to the method call have precedence // 141
params = _.extend({}, parsedUrl.query, params); // 142
// 143
// Reconstruct the URL back without any query string parameters // 144
// (they are now in params) // 145
parsedUrl.query = {}; // 146
parsedUrl.search = ''; // 147
url = urlModule.format(parsedUrl); // 148
// 149
// Get the signature // 150
headers.oauth_signature = // 151
self._getSignature(method, url, headers, self.accessTokenSecret, params); // 152
// 153
// Make a authorization string according to oauth1 spec // 154
var authString = self._getAuthHeaderString(headers); // 155
// 156
// Make signed request // 157
try { // 158
var response = HTTP.call(method, url, { // 159
params: params, // 160
headers: { // 161
Authorization: authString // 162
} // 163
}, callback && function (error, response) { // 164
if (! error) { // 165
response.nonce = headers.oauth_nonce; // 166
} // 167
callback(error, response); // 168
}); // 169
// We store nonce so that JWTs can be validated // 170
if (response) // 171
response.nonce = headers.oauth_nonce; // 172
return response; // 173
} catch (err) { // 174
throw _.extend(new Error("Failed to send OAuth1 request to " + url + ". " + err.message), // 175
{response: err.response}); // 176
} // 177
}; // 178
// 179
OAuth1Binding.prototype._encodeHeader = function(header) { // 180
var self = this; // 181
return _.reduce(header, function(memo, val, key) { // 182
memo[self._encodeString(key)] = self._encodeString(val); // 183
return memo; // 184
}, {}); // 185
}; // 186
// 187
OAuth1Binding.prototype._encodeString = function(str) { // 188
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A"); // 189
}; // 190
// 191
OAuth1Binding.prototype._getAuthHeaderString = function(headers) { // 192
var self = this; // 193
return 'OAuth ' + _.map(headers, function(val, key) { // 194
return self._encodeString(key) + '="' + self._encodeString(val) + '"'; // 195
}).sort().join(', '); // 196
}; // 197
// 198
/////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/oauth1/oauth1_server.js //
// //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
var url = Npm.require("url"); // 1
// 2
// connect middleware // 3
OAuth._requestHandlers['1'] = function (service, query, res) { // 4
var config = ServiceConfiguration.configurations.findOne({service: service.serviceName}); // 5
if (! config) { // 6
throw new ServiceConfiguration.ConfigError(service.serviceName); // 7
} // 8
// 9
var urls = service.urls; // 10
var oauthBinding = new OAuth1Binding(config, urls); // 11
// 12
var credentialSecret; // 13
// 14
if (query.requestTokenAndRedirect) { // 15
// step 1 - get and store a request token // 16
var callbackUrl = OAuth._redirectUri(service.serviceName, config, { // 17
state: query.state, // 18
cordova: (query.cordova === "true"), // 19
android: (query.android === "true") // 20
}); // 21
// 22
// Get a request token to start auth process // 23
oauthBinding.prepareRequestToken(callbackUrl); // 24
// 25
// Keep track of request token so we can verify it on the next step // 26
OAuth._storeRequestToken( // 27
OAuth._credentialTokenFromQuery(query), // 28
oauthBinding.requestToken, // 29
oauthBinding.requestTokenSecret); // 30
// 31
// support for scope/name parameters // 32
var redirectUrl = undefined; // 33
if(typeof urls.authenticate === "function") { // 34
redirectUrl = urls.authenticate(oauthBinding, { // 35
query: query // 36
}); // 37
} else { // 38
// Parse the URL to support additional query parameters in urls.authenticate // 39
var redirectUrlObj = url.parse(urls.authenticate, true); // 40
redirectUrlObj.query = redirectUrlObj.query || {}; // 41
redirectUrlObj.query.oauth_token = oauthBinding.requestToken; // 42
redirectUrlObj.search = ''; // 43
// Reconstruct the URL back with provided query parameters merged with oauth_token // 44
redirectUrl = url.format(redirectUrlObj); // 45
} // 46
// 47
// redirect to provider login, which will redirect back to "step 2" below // 48
// 49
res.writeHead(302, {'Location': redirectUrl}); // 50
res.end(); // 51
} else { // 52
// step 2, redirected from provider login - store the result // 53
// and close the window to allow the login handler to proceed // 54
// 55
// Get the user's request token so we can verify it and clear it // 56
var requestTokenInfo = OAuth._retrieveRequestToken( // 57
OAuth._credentialTokenFromQuery(query)); // 58
// 59
if (! requestTokenInfo) { // 60
throw new Error("Unable to retrieve request token"); // 61
} // 62
// 63
// Verify user authorized access and the oauth_token matches // 64
// the requestToken from previous step // 65
if (query.oauth_token && query.oauth_token === requestTokenInfo.requestToken) { // 66
// 67
// Prepare the login results before returning. This way the // 68
// subsequent call to the `login` method will be immediate. // 69
// 70
// Get the access token for signing requests // 71
oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret); // 72
// 73
// Run service-specific handler. // 74
var oauthResult = service.handleOauthRequest( // 75
oauthBinding, { query: query }); // 76
// 77
var credentialToken = OAuth._credentialTokenFromQuery(query); // 78
credentialSecret = Random.secret(); // 79
// 80
// Store the login result so it can be retrieved in another // 81
// browser tab by the result handler // 82
OAuth._storePendingCredential(credentialToken, { // 83
serviceName: service.serviceName, // 84
serviceData: oauthResult.serviceData, // 85
options: oauthResult.options // 86
}, credentialSecret); // 87
} // 88
// 89
// Either close the window, redirect, or render nothing // 90
// if all else fails // 91
OAuth._renderOauthResults(res, query, credentialSecret); // 92
} // 93
}; // 94
// 95
/////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/oauth1/oauth1_pending_request_tokens.js //
// //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// // 1
// _pendingRequestTokens are request tokens that have been received // 2
// but not yet fully authorized (processed). // 3
// // 4
// During the oauth1 authorization process, the Meteor App opens // 5
// a pop-up, requests a request token from the oauth1 service, and // 6
// redirects the browser to the oauth1 service for the user // 7
// to grant authorization. The user is then returned to the // 8
// Meteor Apps' callback url and the request token is verified. // 9
// // 10
// When Meteor Apps run on multiple servers, it's possible that // 11
// 2 different servers may be used to generate the request token // 12
// and to verify it in the callback once the user has authorized. // 13
// // 14
// For this reason, the _pendingRequestTokens are stored in the database // 15
// so they can be shared across Meteor App servers. // 16
// // 17
// XXX This code is fairly similar to oauth/pending_credentials.js -- // 18
// maybe we can combine them somehow. // 19
// 20
// Collection containing pending request tokens // 21
// Has key, requestToken, requestTokenSecret, and createdAt fields. // 22
OAuth._pendingRequestTokens = new Mongo.Collection( // 23
"meteor_oauth_pendingRequestTokens", { // 24
_preventAutopublish: true // 25
}); // 26
// 27
OAuth._pendingRequestTokens._ensureIndex('key', {unique: 1}); // 28
OAuth._pendingRequestTokens._ensureIndex('createdAt'); // 29
// 30
// 31
// 32
// Periodically clear old entries that never got completed // 33
var _cleanStaleResults = function() { // 34
// Remove request tokens older than 5 minute // 35
var timeCutoff = new Date(); // 36
timeCutoff.setMinutes(timeCutoff.getMinutes() - 5); // 37
OAuth._pendingRequestTokens.remove({ createdAt: { $lt: timeCutoff } }); // 38
}; // 39
var _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000); // 40
// 41
// 42
// Stores the key and request token in the _pendingRequestTokens collection. // 43
// Will throw an exception if `key` is not a string. // 44
// // 45
// @param key {string} // 46
// @param requestToken {string} // 47
// @param requestTokenSecret {string} // 48
// // 49
OAuth._storeRequestToken = function (key, requestToken, requestTokenSecret) { // 50
check(key, String); // 51
// 52
// We do an upsert here instead of an insert in case the user happens // 53
// to somehow send the same `state` parameter twice during an OAuth // 54
// login; we don't want a duplicate key error. // 55
OAuth._pendingRequestTokens.upsert({ // 56
key: key // 57
}, { // 58
key: key, // 59
requestToken: OAuth.sealSecret(requestToken), // 60
requestTokenSecret: OAuth.sealSecret(requestTokenSecret), // 61
createdAt: new Date() // 62
}); // 63
}; // 64
// 65
// 66
// Retrieves and removes a request token from the _pendingRequestTokens collection // 67
// Returns an object containing requestToken and requestTokenSecret properties // 68
// // 69
// @param key {string} // 70
// // 71
OAuth._retrieveRequestToken = function (key) { // 72
check(key, String); // 73
// 74
var pendingRequestToken = OAuth._pendingRequestTokens.findOne({ key: key }); // 75
if (pendingRequestToken) { // 76
OAuth._pendingRequestTokens.remove({ _id: pendingRequestToken._id }); // 77
return { // 78
requestToken: OAuth.openSecret(pendingRequestToken.requestToken), // 79
requestTokenSecret: OAuth.openSecret( // 80
pendingRequestToken.requestTokenSecret) // 81
}; // 82
} else { // 83
return undefined; // 84
} // 85
}; // 86
// 87
/////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package.oauth1 = {
OAuth1Binding: OAuth1Binding,
OAuth1Test: OAuth1Test
};
})();
//# sourceMappingURL=oauth1.js.map