Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into release-candidate
Browse files Browse the repository at this point in the history
  • Loading branch information
sampaiodiego committed Nov 28, 2020
2 parents 443dd87 + 43cb21c commit 188ccde
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 61 deletions.
80 changes: 73 additions & 7 deletions app/2fa/client/TOTPOAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@ import { Twitter } from 'meteor/twitter-oauth';
import { MeteorDeveloperAccounts } from 'meteor/meteor-developer-oauth';
import { Linkedin } from 'meteor/pauli:linkedin-oauth';
import { OAuth } from 'meteor/oauth';
import s from 'underscore.string';

import { Utils2fa } from './lib/2fa';
import { process2faReturn } from './callWithTwoFactorRequired';
import { CustomOAuth } from '../../custom-oauth';

Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback, totpCode) {
const credentialSecret = OAuth._retrieveCredentialSecret(credentialToken) || null;
let lastCredentialToken = null;
let lastCredentialSecret = null;

Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback, totpCode, credentialSecret = null) {
credentialSecret = credentialSecret || OAuth._retrieveCredentialSecret(credentialToken) || null;
const methodArgument = {
oauth: {
credentialToken,
credentialSecret,
},
};

lastCredentialToken = credentialToken;
lastCredentialSecret = credentialSecret;

if (totpCode && typeof totpCode === 'string') {
methodArgument.totp = {
code: totpCode,
Expand All @@ -41,32 +50,89 @@ Accounts.oauth.credentialRequestCompleteHandler = function(callback, totpCode) {
};
};

const loginWithFacebookAndTOTP = Utils2fa.createOAuthTotpLoginMethod(() => Facebook);
const createOAuthTotpLoginMethod = (credentialProvider) => (options, code, callback) => {
// support a callback without options
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}

if (lastCredentialToken && lastCredentialSecret) {
Accounts.oauth.tryLoginAfterPopupClosed(lastCredentialToken, callback, code, lastCredentialSecret);
} else {
const provider = (credentialProvider && credentialProvider()) || this;
const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback, code);
provider.requestCredential(options, credentialRequestCompleteCallback);
}

lastCredentialToken = null;
lastCredentialSecret = null;
};

const loginWithOAuthTokenAndTOTP = createOAuthTotpLoginMethod();

const loginWithFacebookAndTOTP = createOAuthTotpLoginMethod(() => Facebook);
const { loginWithFacebook } = Meteor;
Meteor.loginWithFacebook = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithFacebook, [options], cb, loginWithFacebookAndTOTP);
};

const loginWithGithubAndTOTP = Utils2fa.createOAuthTotpLoginMethod(() => Github);
const loginWithGithubAndTOTP = createOAuthTotpLoginMethod(() => Github);
const { loginWithGithub } = Meteor;
Meteor.loginWithGithub = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithGithub, [options], cb, loginWithGithubAndTOTP);
};

const loginWithMeteorDeveloperAccountAndTOTP = Utils2fa.createOAuthTotpLoginMethod(() => MeteorDeveloperAccounts);
const loginWithMeteorDeveloperAccountAndTOTP = createOAuthTotpLoginMethod(() => MeteorDeveloperAccounts);
const { loginWithMeteorDeveloperAccount } = Meteor;
Meteor.loginWithMeteorDeveloperAccount = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithMeteorDeveloperAccount, [options], cb, loginWithMeteorDeveloperAccountAndTOTP);
};

const loginWithTwitterAndTOTP = Utils2fa.createOAuthTotpLoginMethod(() => Twitter);
const loginWithTwitterAndTOTP = createOAuthTotpLoginMethod(() => Twitter);
const { loginWithTwitter } = Meteor;
Meteor.loginWithTwitter = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithTwitter, [options], cb, loginWithTwitterAndTOTP);
};

const loginWithLinkedinAndTOTP = Utils2fa.createOAuthTotpLoginMethod(() => Linkedin);
const loginWithLinkedinAndTOTP = createOAuthTotpLoginMethod(() => Linkedin);
const { loginWithLinkedin } = Meteor;
Meteor.loginWithLinkedin = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithLinkedin, [options], cb, loginWithLinkedinAndTOTP);
};

Accounts.onPageLoadLogin((loginAttempt) => {
if (loginAttempt?.error?.error !== 'totp-required') {
return;
}

const { methodArguments } = loginAttempt;
if (!methodArguments?.length) {
return;
}

const oAuthArgs = methodArguments.find((arg) => arg.oauth);
const { credentialToken, credentialSecret } = oAuthArgs.oauth;
const cb = loginAttempt.userCallback;

process2faReturn({
error: loginAttempt.error,
originalCallback: cb,
onCode: (code) => {
Accounts.oauth.tryLoginAfterPopupClosed(credentialToken, cb, code, credentialSecret);
},
});
});

const oldConfigureLogin = CustomOAuth.prototype.configureLogin;
CustomOAuth.prototype.configureLogin = function(...args) {
const loginWithService = `loginWith${ s.capitalize(this.name) }`;

oldConfigureLogin.apply(this, args);

const oldMethod = Meteor[loginWithService];

Meteor[loginWithService] = function(options, cb) {
Utils2fa.overrideLoginMethod(oldMethod, [options], cb, loginWithOAuthTokenAndTOTP);
};
};
45 changes: 3 additions & 42 deletions app/2fa/client/lib/2fa.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Meteor } from 'meteor/meteor';
import toastr from 'toastr';
import s from 'underscore.string';
import { Accounts } from 'meteor/accounts-base';

import { CustomOAuth } from '../../../custom-oauth';
import { t } from '../../../utils/client';
import { process2faReturn } from '../callWithTwoFactorRequired';

Expand Down Expand Up @@ -36,8 +34,9 @@ export class Utils2fa {
originalCallback: cb,
onCode: (code) => {
loginMethodTOTP && loginMethodTOTP.apply(this, loginArgs.concat([code, (error) => {
console.log('failed');
console.log(error);
if (error) {
console.log(error);
}
if (error && error.error === 'totp-invalid') {
toastr.error(t('Invalid_two_factor_code'));
cb();
Expand All @@ -49,42 +48,4 @@ export class Utils2fa {
});
}]));
}

static createOAuthTotpLoginMethod(credentialProvider) {
return function(options, code, callback) {
// support a callback without options
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}

const provider = credentialProvider();

const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback, code);
provider.requestCredential(options, credentialRequestCompleteCallback);
};
}
}

const oldConfigureLogin = CustomOAuth.prototype.configureLogin;
CustomOAuth.prototype.configureLogin = function(...args) {
const loginWithService = `loginWith${ s.capitalize(this.name) }`;

oldConfigureLogin.apply(this, args);

const oldMethod = Meteor[loginWithService];
const newMethod = (options, code, callback) => {
// support a callback without options
if (!callback && typeof options === 'function') {
callback = options;
options = null;
}

const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback, code);
this.requestCredential(options, credentialRequestCompleteCallback);
};

Meteor[loginWithService] = function(options, cb) {
Utils2fa.overrideLoginMethod(oldMethod, [options], cb, newMethod);
};
};
54 changes: 54 additions & 0 deletions app/2fa/server/loginHandler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { OAuth } from 'meteor/oauth';
import { check } from 'meteor/check';

import { callbacks } from '../../callbacks';
import { checkCodeForUser } from './code/index';
Expand All @@ -22,3 +25,54 @@ callbacks.add('onValidateLogin', (login) => {

return login;
}, callbacks.priority.MEDIUM, '2fa');

const recreateError = (errorDoc) => {
let error;

if (errorDoc.meteorError) {
error = new Meteor.Error();
delete errorDoc.meteorError;
} else {
error = new Error();
}

Object.getOwnPropertyNames(errorDoc).forEach((key) => {
error[key] = errorDoc[key];
});
return error;
};

OAuth._retrievePendingCredential = function(key, ...args) {
const credentialSecret = args.length > 0 && args[0] !== undefined ? args[0] : null;
check(key, String);

const pendingCredential = OAuth._pendingCredentials.findOne({
key,
credentialSecret,
});

if (!pendingCredential) {
return;
}

if (pendingCredential.credential.error) {
OAuth._pendingCredentials.remove({
_id: pendingCredential._id,
});
return recreateError(pendingCredential.credential.error);
}

// Work-around to make the credentials reusable for 2FA
const future = new Date();
future.setMinutes(future.getMinutes() + 2);

OAuth._pendingCredentials.update({
_id: pendingCredential._id,
}, {
$set: {
createdAt: future,
},
});

return OAuth.openSecret(pendingCredential.credential);
};
1 change: 1 addition & 0 deletions app/models/server/models/Sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ export class Sessions extends Base {
this.tryEnsureIndex({ instanceId: 1, sessionId: 1, userId: 1 });
this.tryEnsureIndex({ instanceId: 1, sessionId: 1 });
this.tryEnsureIndex({ sessionId: 1 });
this.tryEnsureIndex({ userId: 1 });
this.tryEnsureIndex({ year: 1, month: 1, day: 1, type: 1 });
this.tryEnsureIndex({ type: 1 });
this.tryEnsureIndex({ ip: 1, loginAt: 1 });
Expand Down
13 changes: 3 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"@nivo/heatmap": "^0.61.0",
"@nivo/line": "^0.61.1",
"@nivo/pie": "^0.61.1",
"@rocket.chat/apps-engine": "1.20.0-alpha.4149",
"@rocket.chat/apps-engine": "1.20.0",
"@rocket.chat/css-in-js": "^0.6.3-dev.136",
"@rocket.chat/emitter": "^0.6.3-dev.136",
"@rocket.chat/fuselage": "^0.6.3-dev.138",
Expand Down
15 changes: 14 additions & 1 deletion server/startup/migrations/v211.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,21 @@ async function migrateSessions() {
$match: { 'user.roles.0': { $exists: 1 } },
}]);

let actions = [];
for await (const session of cursor) {
await Sessions.col.updateMany({ userId: session._id }, { $set: { mostImportantRole: getMostImportantRole(session.user.roles) } });
actions.push({
updateMany: {
filter: { userId: session._id },
update: { $set: { mostImportantRole: getMostImportantRole(session.user.roles) } },
},
});
if (actions.length === 100) {
await Sessions.col.bulkWrite(actions, { ordered: false });
actions = [];
}
}
if (actions.length) {
await Sessions.col.bulkWrite(actions, { ordered: false });
}
}

Expand Down

0 comments on commit 188ccde

Please sign in to comment.