From d6a611f8d52f10a2b3c26d64d4cd20f9f8bc484b Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Thu, 3 Apr 2014 13:21:11 -0700 Subject: [PATCH 1/4] Oauth Pending Credentials - even cleaner reconstruction in recreateError --- packages/oauth/oauth_tests.js | 1 + packages/oauth/pending_credentials.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/oauth/oauth_tests.js b/packages/oauth/oauth_tests.js index 5059ff9327..7ef523d281 100644 --- a/packages/oauth/oauth_tests.js +++ b/packages/oauth/oauth_tests.js @@ -26,4 +26,5 @@ Tinytest.add("oauth - pendingCredential handles Meteor.Errors", function (test) test.equal(result.message, testError.message); test.equal(result.reason, testError.reason); test.equal(result.stack, testError.stack); + test.isUndefined(result.meteorError); }); diff --git a/packages/oauth/pending_credentials.js b/packages/oauth/pending_credentials.js index 8112245049..e729826cd6 100644 --- a/packages/oauth/pending_credentials.js +++ b/packages/oauth/pending_credentials.js @@ -93,7 +93,7 @@ var recreateError = function(errorDoc) { if (errorDoc.meteorError) { error = new Meteor.Error(); - delete errorDoc.meteorErrror; + delete errorDoc.meteorError; } else { error = new Error(); } From 25ce6bc4f89074067e47b48f6b6bfbc516434156 Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Fri, 4 Apr 2014 15:26:33 -0700 Subject: [PATCH 2/4] Move pending oauth1 request tokens from ram into the DB. --- .../oauth1/oauth1_pending_request_tokens.js | 79 +++++++++++++++++++ packages/oauth1/oauth1_server.js | 21 ++--- packages/oauth1/oauth1_tests.js | 4 +- packages/oauth1/package.js | 1 + 4 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 packages/oauth1/oauth1_pending_request_tokens.js diff --git a/packages/oauth1/oauth1_pending_request_tokens.js b/packages/oauth1/oauth1_pending_request_tokens.js new file mode 100644 index 0000000000..1388cf3792 --- /dev/null +++ b/packages/oauth1/oauth1_pending_request_tokens.js @@ -0,0 +1,79 @@ +// +// _pendingRequestTokens are request tokens that have been received +// but not yet fully authorized (processed). +// +// During the oauth1 authorization process, the Meteor App opens +// a pop-up, requests a request token from the oauth1 service, and +// redirects the browser to the oauth1 service for the user +// to grant authorization. The user is then returned to the +// Meteor Apps' callback url and the request token is verified. +// +// When Meteor Apps run on multiple servers, it's possible that +// 2 different servers may be used to generate the request token +// and to verify it in the callback once the user has authorized. +// +// For this reason, the _pendingRequestTokens are stored in the database +// so they can be shared across Meteor App servers. +// + + +// Collection containing pending request tokens +// Has key, requestToken, requestTokenSecret, and createdAt fields. +Oauth._pendingRequestTokens = new Meteor.Collection( + "meteor_oauth_pendingRequestTokens", { + _preventAutopublish: true + }); + +Oauth._pendingRequestTokens._ensureIndex('key', {unique: 1}); +Oauth._pendingRequestTokens._ensureIndex('createdAt'); + + + +// Periodically clear old entries that never got completed +var _cleanStaleResults = function() { + // Remove request tokens older than 5 minute + var timeCutoff = new Date(); + timeCutoff.setMinutes(timeCutoff.getMinutes() - 5); + Oauth._pendingRequestTokens.remove({ createdAt: { $lt: timeCutoff } }); +}; +var _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000); + + +// Stores the key and request token in the _pendingRequestTokens collection +// XXX After oauth token encryption is added to Meteor, apply it here too +// +// @param key {string} +// @param requestToken {string} +// @param requestTokenSecret {string} +// +Oauth._storeRequestToken = function (key, requestToken, requestTokenSecret) { + Oauth._pendingRequestTokens.insert({ + key: key, + requestToken: requestToken, + requestTokenSecret: requestTokenSecret, + createdAt: new Date() + }); +}; + + +// Retrieves and removes a request token from the _pendingRequestTokens collection +// Returns an object containing requestToken and requestTokenSecret properties +// +// XXX After oauth token encryption is added to Meteor, apply it here too +// +// @param key {string} +// +Oauth._retrieveRequestToken = function (key) { + check(key, String); + + var pendingRequestToken = Oauth._pendingRequestTokens.findOne({ key:key }); + if (pendingRequestToken) { + Oauth._pendingRequestTokens.remove({ _id: pendingRequestToken._id }); + return { + requestToken: pendingRequestToken.requestToken, + requestTokenSecret: pendingRequestToken.requestTokenSecret + } ; + } else { + return undefined; + } +}; \ No newline at end of file diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 642ed62c72..36d6e96bc6 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -1,8 +1,3 @@ -// A place to store request tokens pending verification -var requestTokens = {}; - -OAuth1Test = {requestTokens: requestTokens}; - // connect middleware Oauth._requestHandlers['1'] = function (service, query, res) { @@ -21,10 +16,10 @@ 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] = { - requestToken: oauthBinding.requestToken, - requestTokenSecret: oauthBinding.requestTokenSecret - }; + Oauth._storeRequestToken(query.state, + oauthBinding.requestToken, + oauthBinding.requestTokenSecret + ); // support for scope/name parameters var redirectUrl = undefined; @@ -42,19 +37,17 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // and close the window to allow the login handler to proceed // Get the user's request token so we can verify it and clear it - var requestToken = requestTokens[query.state].requestToken; - var requestTokenSecret = requestTokens[query.state].requestTokenSecret; - delete requestTokens[query.state]; + var requestTokenHolder = Oauth._retrieveRequestToken(query.state); // Verify user authorized access and the oauth_token matches // the requestToken from previous step - if (query.oauth_token && query.oauth_token === requestToken) { + if (query.oauth_token && query.oauth_token === requestTokenHolder.requestToken) { // Prepare the login results before returning. This way the // subsequent call to the `login` method will be immediate. // Get the access token for signing requests - oauthBinding.prepareAccessToken(query, requestTokenSecret); + oauthBinding.prepareAccessToken(query, requestTokenHolder.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 cd4bb28b97..9c1ac07689 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -40,9 +40,7 @@ Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved", functi }); // simulate logging in using twitterfoo - OAuth1Test.requestTokens[credentialToken] = { - requestToken: twitterfooAccessToken - }; + Oauth._storeRequestToken(credentialToken, twitterfooAccessToken); var req = { method: "POST", diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index c57d98f515..4fc67d8435 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -15,6 +15,7 @@ Package.on_use(function (api) { api.add_files('oauth1_binding.js', 'server'); api.add_files('oauth1_server.js', 'server'); + api.add_files('oauth1_pending_request_tokens.js', 'server'); }); Package.on_test(function (api) { From 75cf184a76dd67d6e9828a1418d7a5a671cf1e3b Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Fri, 4 Apr 2014 15:33:47 -0700 Subject: [PATCH 3/4] OAuth: rename token to key in pendingCredentials to reduce confusion --- packages/oauth/pending_credentials.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/oauth/pending_credentials.js b/packages/oauth/pending_credentials.js index e729826cd6..c896bf88a0 100644 --- a/packages/oauth/pending_credentials.js +++ b/packages/oauth/pending_credentials.js @@ -10,13 +10,13 @@ // Collection containing pending credentials of oauth credential requests -// Has token, credential, and createdAt fields. +// Has key, credential, and createdAt fields. Oauth._pendingCredentials = new Meteor.Collection( "meteor_oauth_pendingCredentials", { _preventAutopublish: true }); -Oauth._pendingCredentials._ensureIndex('token', {unique: 1}); +Oauth._pendingCredentials._ensureIndex('key', {unique: 1}); Oauth._pendingCredentials._ensureIndex('createdAt'); @@ -31,18 +31,18 @@ var _cleanStaleResults = function() { var _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000); -// Stores the token and credential in the _pendingCredentials collection +// Stores the key and credential in the _pendingCredentials collection // XXX After oauth token encryption is added to Meteor, apply it here too // -// @param credentialToken {string} +// @param key {string} // @param credential {string} The credential to store // -Oauth._storePendingCredential = function (credentialToken, credential) { +Oauth._storePendingCredential = function (key, credential) { if (credential instanceof Error) credential = storableError(credential); Oauth._pendingCredentials.insert({ - token: credentialToken, + key: key, credential: credential, createdAt: new Date() }); @@ -52,12 +52,12 @@ Oauth._storePendingCredential = function (credentialToken, credential) { // Retrieves and removes a credential from the _pendingCredentials collection // XXX After oauth token encryption is added to Meteor, apply it here too // -// @param credentialToken {string} +// @param key {string} // -Oauth._retrievePendingCredential = function (credentialToken) { - check(credentialToken, String); +Oauth._retrievePendingCredential = function (key) { + check(key, String); - var pendingCredential = Oauth._pendingCredentials.findOne({ token:credentialToken }); + var pendingCredential = Oauth._pendingCredentials.findOne({ key:key }); if (pendingCredential) { Oauth._pendingCredentials.remove({ _id: pendingCredential._id }); if (pendingCredential.credential.error) From 7168410d5458dcafaaf930a61491ba8edb5b37bb Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Fri, 18 Apr 2014 15:53:31 -0700 Subject: [PATCH 4/4] Pending Request Tokens - Whitespace and var name fixes --- packages/oauth1/oauth1_pending_request_tokens.js | 4 ++-- packages/oauth1/oauth1_server.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/oauth1/oauth1_pending_request_tokens.js b/packages/oauth1/oauth1_pending_request_tokens.js index 1388cf3792..3d7fd3be33 100644 --- a/packages/oauth1/oauth1_pending_request_tokens.js +++ b/packages/oauth1/oauth1_pending_request_tokens.js @@ -66,13 +66,13 @@ Oauth._storeRequestToken = function (key, requestToken, requestTokenSecret) { Oauth._retrieveRequestToken = function (key) { check(key, String); - var pendingRequestToken = Oauth._pendingRequestTokens.findOne({ key:key }); + var pendingRequestToken = Oauth._pendingRequestTokens.findOne({ key: key }); if (pendingRequestToken) { Oauth._pendingRequestTokens.remove({ _id: pendingRequestToken._id }); return { requestToken: pendingRequestToken.requestToken, requestTokenSecret: pendingRequestToken.requestTokenSecret - } ; + }; } else { return undefined; } diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 36d6e96bc6..a75d88bb86 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -37,17 +37,17 @@ Oauth._requestHandlers['1'] = function (service, query, res) { // and close the window to allow the login handler to proceed // Get the user's request token so we can verify it and clear it - var requestTokenHolder = Oauth._retrieveRequestToken(query.state); + var requestTokenInfo = Oauth._retrieveRequestToken(query.state); // Verify user authorized access and the oauth_token matches // the requestToken from previous step - if (query.oauth_token && query.oauth_token === requestTokenHolder.requestToken) { + if (query.oauth_token && query.oauth_token === requestTokenInfo.requestToken) { // Prepare the login results before returning. This way the // subsequent call to the `login` method will be immediate. // Get the access token for signing requests - oauthBinding.prepareAccessToken(query, requestTokenHolder.requestTokenSecret); + oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret); // Run service-specific handler. var oauthResult = service.handleOauthRequest(oauthBinding);