From d6ee11b643c17861998ad1ca900dc40dd81e1cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Finn=20Gl=C3=B6e?= Date: Wed, 25 Oct 2017 22:34:38 +0200 Subject: [PATCH] added a CredentialTokens model to store user info in mongodb instead of process to fix CAS when operating multiple server instances --- packages/rocketchat-cas/package.js | 1 + packages/rocketchat-cas/server/cas_server.js | 26 ++++------------- .../server/models/CredentialTokens.js | 28 +++++++++++++++++++ 3 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 packages/rocketchat-cas/server/models/CredentialTokens.js diff --git a/packages/rocketchat-cas/package.js b/packages/rocketchat-cas/package.js index 8520a0b09280..c6b3c7e156d0 100644 --- a/packages/rocketchat-cas/package.js +++ b/packages/rocketchat-cas/package.js @@ -19,6 +19,7 @@ Package.onUse(function(api) { // Server files api.add_files('server/cas_rocketchat.js', 'server'); api.add_files('server/cas_server.js', 'server'); + api.add_files('server/models/CredentialTokens.js', 'server'); // Client files api.add_files('client/cas_client.js', 'client'); diff --git a/packages/rocketchat-cas/server/cas_server.js b/packages/rocketchat-cas/server/cas_server.js index 9100fb47d837..aee57fe75eb0 100644 --- a/packages/rocketchat-cas/server/cas_server.js +++ b/packages/rocketchat-cas/server/cas_server.js @@ -6,8 +6,6 @@ const fiber = Npm.require('fibers'); const url = Npm.require('url'); const CAS = Npm.require('cas'); -const _casCredentialTokens = {}; - RoutePolicy.declare('/_cas/', 'network'); const closePopup = function(res) { @@ -38,7 +36,7 @@ const casTicket = function(req, token, callback) { service: `${ appUrl }/_cas/${ token }` }); - cas.validate(ticketId, function(err, status, username, details) { + cas.validate(ticketId, Meteor.bindEnvironment(function(err, status, username, details) { if (err) { logger.error(`error when trying to validate: ${ err.message }`); } else if (status) { @@ -49,14 +47,14 @@ const casTicket = function(req, token, callback) { if (details && details.attributes) { _.extend(user_info, { attributes: details.attributes }); } - _casCredentialTokens[token] = user_info; + RocketChat.models.CredentialTokens.create(token, user_info); } else { logger.error(`Unable to validate ticket: ${ ticketId }`); } //logger.debug("Receveied response: " + JSON.stringify(details, null , 4)); callback(); - }); + })); return; }; @@ -102,19 +100,6 @@ WebApp.connectHandlers.use(function(req, res, next) { }).run(); }); -const _hasCredential = function(credentialToken) { - return _.has(_casCredentialTokens, credentialToken); -}; - -/* - * Retrieve token and delete it to avoid replaying it. - */ -const _retrieveCredential = function(credentialToken) { - const result = _casCredentialTokens[credentialToken]; - delete _casCredentialTokens[credentialToken]; - return result; -}; - /* * Register a server-side login handle. * It is call after Accounts.callLoginMethod() is call from client. @@ -126,12 +111,13 @@ Accounts.registerLoginHandler(function(options) { return undefined; } - if (!_hasCredential(options.cas.credentialToken)) { + const credentials = RocketChat.models.CredentialTokens.findOneById(options.cas.credentialToken); + if (credentials === undefined) { throw new Meteor.Error(Accounts.LoginCancelledError.numericError, 'no matching login attempt found'); } - const result = _retrieveCredential(options.cas.credentialToken); + const result = credentials.userInfo; const syncUserDataFieldMap = RocketChat.settings.get('CAS_Sync_User_Data_FieldMap').trim(); const cas_version = parseFloat(RocketChat.settings.get('CAS_version')); const sync_enabled = RocketChat.settings.get('CAS_Sync_User_Data_Enabled'); diff --git a/packages/rocketchat-cas/server/models/CredentialTokens.js b/packages/rocketchat-cas/server/models/CredentialTokens.js new file mode 100644 index 000000000000..499725f2357d --- /dev/null +++ b/packages/rocketchat-cas/server/models/CredentialTokens.js @@ -0,0 +1,28 @@ +RocketChat.models.CredentialTokens = new class extends RocketChat.models._Base { + constructor() { + super('credential_tokens'); + + this.tryEnsureIndex({ 'expireAt': 1 }, { sparse: 1, expireAfterSeconds: 0 }); + } + + create(_id, userInfo) { + const validForMilliseconds = 60000; // Valid for 60 seconds + const token = { + _id, + userInfo, + expireAt: new Date(Date.now() + validForMilliseconds) + }; + + this.insert(token); + return token; + } + + findOneById(_id) { + const query = { + _id, + expireAt: { $gt: new Date() } + }; + + return this.findOne(query); + } +};