Merge pull request #2016 from timhaines/multi-server-oauth

OAuth 1 - Move pending request tokens from RAM to mongo.
This commit is contained in:
Emily Stark
2014-04-21 10:56:33 -07:00
5 changed files with 98 additions and 27 deletions

View File

@@ -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)

View File

@@ -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;
}
};

View File

@@ -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 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 === 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, requestTokenSecret);
oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
// Run service-specific handler.
var oauthResult = service.handleOauthRequest(oauthBinding);

View File

@@ -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",

View File

@@ -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) {