diff --git a/packages/rocketchat-2fa/server/lib/totp.js b/packages/rocketchat-2fa/server/lib/totp.js index bf81dd043cd04..54b4f579f2de8 100644 --- a/packages/rocketchat-2fa/server/lib/totp.js +++ b/packages/rocketchat-2fa/server/lib/totp.js @@ -13,30 +13,39 @@ RocketChat.TOTP = { }, verify({ secret, token, backupTokens, userId }) { - let verified; - // validates a backup code if (token.length === 8 && backupTokens) { const hashedCode = SHA256(token); const usedCode = backupTokens.indexOf(hashedCode); if (usedCode !== -1) { - verified = true; - backupTokens.splice(usedCode, 1); // mark the code as used (remove it from the list) RocketChat.models.Users.update2FABackupCodesByUserId(userId, backupTokens); + return true; } - } else { - verified = speakeasy.totp.verify({ + + return false; + } + + const maxDelta = RocketChat.settings.get('Accounts_TwoFactorAuthentication_MaxDelta'); + if (maxDelta) { + const verifiedDelta = speakeasy.totp.verifyDelta({ secret, encoding: 'base32', - token + token, + window: maxDelta }); + + return verifiedDelta !== undefined; } - return verified; + return speakeasy.totp.verify({ + secret, + encoding: 'base32', + token + }); }, generateCodes() { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 9b728820ef71d..c06b3559065ea 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -137,6 +137,8 @@ "Accounts_SetDefaultAvatar": "Set Default Avatar", "Accounts_SetDefaultAvatar_Description": "Tries to determine default avatar based on OAuth Account or Gravatar", "Accounts_ShowFormLogin": "Show Form-Based Login", + "Accounts_TwoFactorAuthentication_MaxDelta": "Maximum Delta", + "Accounts_TwoFactorAuthentication_MaxDelta_Description": "The Maximum Delta determines how many tokens are valid at any given time. Tokens are generated every 30 seconds, and are valid for (30 * Maximum Delta) seconds.
Example: With a Maximum Delta set to 10, each token can be used up to 300 seconds before or after it's timestamp. This is useful when the client's clock is not properly synced with the server.", "Accounts_UseDefaultBlockedDomainsList": "Use Default Blocked Domains List", "Accounts_UseDNSDomainCheck": "Use DNS Domain Check", "Accounts_UserAddedEmail_Default": "

Welcome to

[Site_Name]

Go to [Site_URL] and try the best open source chat solution available today!

You may login using your email: [email] and password: [password]. You may be required to change it after your first login.", diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index 249784a07a30c..643610aeb7375 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -83,6 +83,14 @@ RocketChat.settings.addGroup('Accounts', function() { public: true }); + this.section('Two Factor Authentication', function() { + this.add('Accounts_TwoFactorAuthentication_MaxDelta', 1, { + type: 'int', + public: true, + i18nLabel: 'Accounts_TwoFactorAuthentication_MaxDelta' + }); + }); + this.section('Registration', function() { this.add('Accounts_DefaultUsernamePrefixSuggestion', 'user', { type: 'string'