diff --git a/.eslintrc b/.eslintrc
index 3c9e29446b60..bbae28a07d60 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -72,7 +72,7 @@
"curly": [2, "all"],
"eqeqeq": [2, "allow-null"],
"new-cap": [2, {
- "capIsNewExceptions": ["Match.Optional", "Match.Maybe", "Match.ObjectIncluding", "Push.Configure"]
+ "capIsNewExceptions": ["Match.Optional", "Match.Maybe", "Match.ObjectIncluding", "Push.Configure", "SHA256"]
}],
"use-isnan": 2,
"valid-typeof": 2,
diff --git a/.meteor/packages b/.meteor/packages
index 4863b12e029e..905b829d46f2 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -38,6 +38,7 @@ standard-minifier-css@1.3.3
standard-minifier-js@1.2.2
tracker@1.1.2
+rocketchat:2fa
rocketchat:action-links
rocketchat:analytics
rocketchat:api
diff --git a/.meteor/versions b/.meteor/versions
index da42836cd55d..d6517147238f 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -119,6 +119,7 @@ reactive-dict@1.1.8
reactive-var@1.0.11
reload@1.1.11
retry@1.0.9
+rocketchat:2fa@0.0.1
rocketchat:action-links@0.0.1
rocketchat:analytics@0.0.2
rocketchat:api@0.0.1
diff --git a/HISTORY.md b/HISTORY.md
index d1dbaa0b2f32..f9d83f5e4040 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -4,6 +4,7 @@
- [NEW] Permission `join-without-join-code` assigned to admins and bots by default (#6139)
- [NEW] Integrations, both incoming and outgoing, now have access to the models. Example: `Users.findOneById(id)` (#6336)
+- [NEW] Option to enable `Two Factor Authentication` in user's account preference
- [FIX] Incoming integrations would break when trying to use the `Store` feature.
## 0.54.2 - 2017-Mar-24
diff --git a/packages/rocketchat-2fa/.npm/package/.gitignore b/packages/rocketchat-2fa/.npm/package/.gitignore
new file mode 100644
index 000000000000..3c3629e647f5
--- /dev/null
+++ b/packages/rocketchat-2fa/.npm/package/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/packages/rocketchat-2fa/.npm/package/README b/packages/rocketchat-2fa/.npm/package/README
new file mode 100644
index 000000000000..3d492553a438
--- /dev/null
+++ b/packages/rocketchat-2fa/.npm/package/README
@@ -0,0 +1,7 @@
+This directory and the files immediately inside it are automatically generated
+when you change this package's NPM dependencies. Commit the files in this
+directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
+so that others run the same versions of sub-dependencies.
+
+You should NOT check in the node_modules directory that Meteor automatically
+creates; if you are using git, the .gitignore file tells git to ignore it.
diff --git a/packages/rocketchat-2fa/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-2fa/.npm/package/npm-shrinkwrap.json
new file mode 100644
index 000000000000..df69ce869e8b
--- /dev/null
+++ b/packages/rocketchat-2fa/.npm/package/npm-shrinkwrap.json
@@ -0,0 +1,19 @@
+{
+ "dependencies": {
+ "base32.js": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.0.1.tgz",
+ "from": "base32.js@0.0.1"
+ },
+ "speakeasy": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz",
+ "from": "speakeasy@2.0.0"
+ },
+ "yaqrcode": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/yaqrcode/-/yaqrcode-0.2.1.tgz",
+ "from": "yaqrcode@0.2.1"
+ }
+ }
+}
diff --git a/packages/rocketchat-2fa/README.md b/packages/rocketchat-2fa/README.md
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/rocketchat-2fa/client/TOTPPassword.js b/packages/rocketchat-2fa/client/TOTPPassword.js
new file mode 100644
index 000000000000..2fb6fa0e171e
--- /dev/null
+++ b/packages/rocketchat-2fa/client/TOTPPassword.js
@@ -0,0 +1,72 @@
+import toastr from 'toastr';
+
+function reportError(error, callback) {
+ if (callback) {
+ callback(error);
+ } else {
+ throw error;
+ }
+}
+
+Meteor.loginWithPasswordAndTOTP = function(selector, password, code, callback) {
+ if (typeof selector === 'string') {
+ if (selector.indexOf('@') === -1) {
+ selector = {username: selector};
+ } else {
+ selector = {email: selector};
+ }
+ }
+
+ Accounts.callLoginMethod({
+ methodArguments: [{
+ totp: {
+ login: {
+ user: selector,
+ password: Accounts._hashPassword(password)
+ },
+ code
+ }
+ }],
+ userCallback(error) {
+ if (error) {
+ reportError(error, callback);
+ } else {
+ callback && callback();
+ }
+ }
+ });
+};
+
+const loginWithPassword = Meteor.loginWithPassword;
+
+Meteor.loginWithPassword = function(email, password, cb) {
+ loginWithPassword(email, password, (error) => {
+ if (!error || error.error !== 'totp-required') {
+ return cb(error);
+ }
+
+ swal({
+ title: t('Two-factor_authentication'),
+ text: t('Open_your_authentication_app_and_enter_the_code'),
+ type: 'input',
+ inputType: 'text',
+ showCancelButton: true,
+ closeOnConfirm: true,
+ confirmButtonText: t('Verify'),
+ cancelButtonText: t('Cancel')
+ }, (code) => {
+ if (code === false) {
+ return cb();
+ }
+
+ Meteor.loginWithPasswordAndTOTP(email, password, code, (error) => {
+ if (error && error.error === 'totp-invalid') {
+ toastr.error(t('Invalid_two_factor_code'));
+ cb();
+ } else {
+ cb(error);
+ }
+ });
+ });
+ });
+};
diff --git a/packages/rocketchat-2fa/client/accountSecurity.html b/packages/rocketchat-2fa/client/accountSecurity.html
new file mode 100644
index 000000000000..9ed51e09d061
--- /dev/null
+++ b/packages/rocketchat-2fa/client/accountSecurity.html
@@ -0,0 +1,57 @@
+
+
+ {{_ "Security"}}
+
+ ${ backupCodes }
`;
+ swal({
+ title: t('Backup_codes'),
+ text: `${ t('Make_sure_you_have_a_copy_of_your_codes', { codes }) }`,
+ html: true
+ });
+ };
+
+ this.autorun(() => {
+ const user = Meteor.user();
+ if (user && user.services && user.services.totp && user.services.totp.enabled) {
+ Meteor.call('2fa:checkCodesRemaining', (error, result) => {
+ if (result) {
+ this.codesRemaining.set(result.remaining);
+ }
+ });
+ }
+ });
+});
diff --git a/packages/rocketchat-2fa/package.js b/packages/rocketchat-2fa/package.js
new file mode 100644
index 000000000000..3a8bdc696b82
--- /dev/null
+++ b/packages/rocketchat-2fa/package.js
@@ -0,0 +1,39 @@
+Package.describe({
+ name: 'rocketchat:2fa',
+ version: '0.0.1',
+ summary: '',
+ git: '',
+ documentation: 'README.md'
+});
+
+Npm.depends({
+ speakeasy: '2.0.0',
+ yaqrcode: '0.2.1'
+});
+
+Package.onUse(function(api) {
+ api.use([
+ 'accounts-base',
+ 'ecmascript',
+ 'templating',
+ 'rocketchat:lib',
+ 'sha',
+ 'random'
+ ]);
+
+ api.addFiles('client/accountSecurity.html', 'client');
+ api.addFiles('client/accountSecurity.js', 'client');
+ api.addFiles('client/TOTPPassword.js', 'client');
+
+ api.addFiles('server/lib/totp.js', 'server');
+
+ api.addFiles('server/methods/checkCodesRemaining.js', 'server');
+ api.addFiles('server/methods/disable.js', 'server');
+ api.addFiles('server/methods/enable.js', 'server');
+ api.addFiles('server/methods/regenerateCodes.js', 'server');
+ api.addFiles('server/methods/validateTempToken.js', 'server');
+
+ api.addFiles('server/models/users.js', 'server');
+
+ api.addFiles('server/loginHandler.js', 'server');
+});
diff --git a/packages/rocketchat-2fa/server/lib/totp.js b/packages/rocketchat-2fa/server/lib/totp.js
new file mode 100644
index 000000000000..bf81dd043cd0
--- /dev/null
+++ b/packages/rocketchat-2fa/server/lib/totp.js
@@ -0,0 +1,54 @@
+import speakeasy from 'speakeasy';
+
+RocketChat.TOTP = {
+ generateSecret() {
+ return speakeasy.generateSecret();
+ },
+
+ generateOtpauthURL(secret, username) {
+ return speakeasy.otpauthURL({
+ secret: secret.ascii,
+ label: `Rocket.Chat:${ username }`
+ });
+ },
+
+ 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);
+ }
+ } else {
+ verified = speakeasy.totp.verify({
+ secret,
+ encoding: 'base32',
+ token
+ });
+ }
+
+ return verified;
+ },
+
+ generateCodes() {
+ // generate 12 backup codes
+ const codes = [];
+ const hashedCodes = [];
+ for (let i = 0; i < 12; i++) {
+ const code = Random.id(8);
+ codes.push(code);
+ hashedCodes.push(SHA256(code));
+ }
+
+ return { codes, hashedCodes };
+ }
+};
diff --git a/packages/rocketchat-2fa/server/loginHandler.js b/packages/rocketchat-2fa/server/loginHandler.js
new file mode 100644
index 000000000000..650b1f9d6df5
--- /dev/null
+++ b/packages/rocketchat-2fa/server/loginHandler.js
@@ -0,0 +1,28 @@
+Accounts.registerLoginHandler('totp', function(options) {
+ if (!options.totp || !options.totp.code) {
+ return;
+ }
+
+ return Accounts._runLoginHandlers(this, options.totp.login);
+});
+
+RocketChat.callbacks.add('onValidateLogin', (login) => {
+ if (login.type === 'password' && login.user.services && login.user.services.totp && login.user.services.totp.enabled === true) {
+ const { totp } = login.methodArguments[0];
+
+ if (!totp || !totp.code) {
+ throw new Meteor.Error('totp-required', 'TOTP Required');
+ }
+
+ const verified = RocketChat.TOTP.verify({
+ secret: login.user.services.totp.secret,
+ token: totp.code,
+ userId: login.user._id,
+ backupTokens: login.user.services.totp.hashedBackup
+ });
+
+ if (verified !== true) {
+ throw new Meteor.Error('totp-invalid', 'TOTP Invalid');
+ }
+ }
+});
diff --git a/packages/rocketchat-2fa/server/methods/checkCodesRemaining.js b/packages/rocketchat-2fa/server/methods/checkCodesRemaining.js
new file mode 100644
index 000000000000..8d2224d61726
--- /dev/null
+++ b/packages/rocketchat-2fa/server/methods/checkCodesRemaining.js
@@ -0,0 +1,17 @@
+Meteor.methods({
+ '2fa:checkCodesRemaining'() {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ const user = Meteor.user();
+
+ if (!user.services || !user.services.totp || !user.services.totp.enabled) {
+ throw new Meteor.Error('invalid-totp');
+ }
+
+ return {
+ remaining: user.services.totp.hashedBackup.length
+ };
+ }
+});
diff --git a/packages/rocketchat-2fa/server/methods/disable.js b/packages/rocketchat-2fa/server/methods/disable.js
new file mode 100644
index 000000000000..f8cb63d3dbef
--- /dev/null
+++ b/packages/rocketchat-2fa/server/methods/disable.js
@@ -0,0 +1,22 @@
+Meteor.methods({
+ '2fa:disable'(code) {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ const user = Meteor.user();
+
+ const verified = RocketChat.TOTP.verify({
+ secret: user.services.totp.secret,
+ token: code,
+ userId: Meteor.userId(),
+ backupTokens: user.services.totp.hashedBackup
+ });
+
+ if (!verified) {
+ return false;
+ }
+
+ return RocketChat.models.Users.disable2FAByUserId(Meteor.userId());
+ }
+});
diff --git a/packages/rocketchat-2fa/server/methods/enable.js b/packages/rocketchat-2fa/server/methods/enable.js
new file mode 100644
index 000000000000..3c690089b7cf
--- /dev/null
+++ b/packages/rocketchat-2fa/server/methods/enable.js
@@ -0,0 +1,17 @@
+Meteor.methods({
+ '2fa:enable'() {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ const user = Meteor.user();
+
+ const secret = RocketChat.TOTP.generateSecret();
+
+ RocketChat.models.Users.disable2FAAndSetTempSecretByUserId(Meteor.userId(), secret.base32);
+
+ return {
+ url: RocketChat.TOTP.generateOtpauthURL(secret, user.username)
+ };
+ }
+});
diff --git a/packages/rocketchat-2fa/server/methods/regenerateCodes.js b/packages/rocketchat-2fa/server/methods/regenerateCodes.js
new file mode 100644
index 000000000000..24f9a4e5973f
--- /dev/null
+++ b/packages/rocketchat-2fa/server/methods/regenerateCodes.js
@@ -0,0 +1,27 @@
+Meteor.methods({
+ '2fa:regenerateCodes'(userToken) {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ const user = Meteor.user();
+
+ if (!user.services || !user.services.totp || !user.services.totp.enabled) {
+ throw new Meteor.Error('invalid-totp');
+ }
+
+ const verified = RocketChat.TOTP.verify({
+ secret: user.services.totp.secret,
+ token: userToken,
+ userId: Meteor.userId(),
+ backupTokens: user.services.totp.hashedBackup
+ });
+
+ if (verified) {
+ const { codes, hashedCodes } = RocketChat.TOTP.generateCodes();
+
+ RocketChat.models.Users.update2FABackupCodesByUserId(Meteor.userId(), hashedCodes);
+ return { codes };
+ }
+ }
+});
diff --git a/packages/rocketchat-2fa/server/methods/validateTempToken.js b/packages/rocketchat-2fa/server/methods/validateTempToken.js
new file mode 100644
index 000000000000..482ccfde0098
--- /dev/null
+++ b/packages/rocketchat-2fa/server/methods/validateTempToken.js
@@ -0,0 +1,25 @@
+Meteor.methods({
+ '2fa:validateTempToken'(userToken) {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ const user = Meteor.user();
+
+ if (!user.services || !user.services.totp || !user.services.totp.tempSecret) {
+ throw new Meteor.Error('invalid-totp');
+ }
+
+ const verified = RocketChat.TOTP.verify({
+ secret: user.services.totp.tempSecret,
+ token: userToken
+ });
+
+ if (verified) {
+ const { codes, hashedCodes } = RocketChat.TOTP.generateCodes();
+
+ RocketChat.models.Users.enable2FAAndSetSecretAndCodesByUserId(Meteor.userId(), user.services.totp.tempSecret, hashedCodes);
+ return { codes };
+ }
+ }
+});
diff --git a/packages/rocketchat-2fa/server/models/users.js b/packages/rocketchat-2fa/server/models/users.js
new file mode 100644
index 000000000000..ba90d2673861
--- /dev/null
+++ b/packages/rocketchat-2fa/server/models/users.js
@@ -0,0 +1,49 @@
+RocketChat.models.Users.disable2FAAndSetTempSecretByUserId = function(userId, tempToken) {
+ return this.update({
+ _id: userId
+ }, {
+ $set: {
+ 'services.totp': {
+ enabled: false,
+ tempSecret: tempToken
+ }
+ }
+ });
+};
+
+RocketChat.models.Users.enable2FAAndSetSecretAndCodesByUserId = function(userId, secret, backupCodes) {
+ return this.update({
+ _id: userId
+ }, {
+ $set: {
+ 'services.totp.enabled': true,
+ 'services.totp.secret': secret,
+ 'services.totp.hashedBackup': backupCodes
+ },
+ $unset: {
+ 'services.totp.tempSecret': 1
+ }
+ });
+};
+
+RocketChat.models.Users.disable2FAByUserId = function(userId) {
+ return this.update({
+ _id: userId
+ }, {
+ $set: {
+ 'services.totp': {
+ enabled: false
+ }
+ }
+ });
+};
+
+RocketChat.models.Users.update2FABackupCodesByUserId = function(userId, backupCodes) {
+ return this.update({
+ _id: userId
+ }, {
+ $set: {
+ 'services.totp.hashedBackup': backupCodes
+ }
+ });
+};
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 12cba57bcbdc..6057e02758c1 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -245,6 +245,7 @@
"Back_to_integration_detail": "Back to the integration detail",
"Back_to_login": "Back to login",
"Back_to_permissions": "Back to permissions",
+ "Backup_codes": "Backup codes",
"Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta feature. Depends on Video Conference to be enabled.",
"Block_User": "Block User",
"Body": "Body",
@@ -405,6 +406,7 @@
"Desktop_Notifications_Enabled": "Desktop Notifications are Enabled",
"Direct_message_someone": "Direct message someone",
"Direct_Messages": "Direct Messages",
+ "Disable_two-factor_authentication": "Disable two-factor authentication",
"Display_offline_form": "Display offline form",
"Displays_action_text": "Displays action text",
"Do_you_want_to_change_to_s_question": "Do you want to change to %s?",
@@ -447,10 +449,12 @@
"Empty_title": "Empty title",
"Enable": "Enable",
"Enable_Desktop_Notifications": "Enable Desktop Notifications",
+ "Enable_two-factor_authentication": "Enable two-factor authentication",
"Enabled": "Enabled",
"Enable_Svg_Favicon": "Enable SVG favicon",
"Encrypted_message": "Encrypted message",
"End_OTR": "End OTR",
+ "Enter_authentication_code": "Enter authentication code",
"Enter_Alternative": "Alternative mode (send with Enter + Ctrl/Alt/Shift/CMD)",
"Enter_a_regex": "Enter a regex",
"Enter_a_room_name": "Enter a room name",
@@ -740,6 +744,7 @@
"Invalid_room_name": "%s is not a valid room name,
use only letters, numbers, hyphens and underscores",
"Invalid_secret_URL_message": "The URL provided is invalid.",
"Invalid_setting_s": "Invalid setting: %s",
+ "Invalid_two_factor_code": "Invalid two factor code",
"invisible": "invisible",
"Invisible": "Invisible",
"Invitation": "Invitation",
@@ -924,6 +929,7 @@
"Mailer_body_tags": "You must use [unsubscribe] for the unsubscription link.
You may use [name], [fname], [lname] for the user's full name, first name or last name, respectively.
You may use [email] for the user's email.",
"Mailing": "Mailing",
"Make_Admin": "Make Admin",
+ "Make_sure_you_have_a_copy_of_your_codes": "Make sure you have a copy of your codes: __codes__ If you lose access to your authenticator app, you can use one of these codes to log in.",
"Manager_added": "Manager added",
"Manager_removed": "Manager removed",
"Managing_assets": "Managing assets",
@@ -1095,6 +1101,7 @@
"optional": "optional",
"Use_minor_colors": "Use minor color palette (defaults inherit major colors)",
"or": "or",
+ "Open_your_authentication_app_and_enter_the_code": "Open your authentication app and enter the code. You can also use one of your backup codes.",
"Order": "Order",
"OS_Arch": "OS Arch",
"OS_Cpus": "OS CPU Count",
@@ -1196,6 +1203,7 @@
"Refresh_oauth_services": "Refresh OAuth Services",
"Refresh_keys": "Refresh keys",
"Refresh_your_page_after_install_to_enable_screen_sharing": "Refresh your page after install to enable screen sharing",
+ "Regenerate_codes": "Regenerate codes",
"Register": "Register a new account",
"Registration": "Registration",
"Registration_Succeeded": "Registration Succeeded",
@@ -1276,6 +1284,7 @@
"Save_to_enable_this_action": "Save to enable this action",
"Saved": "Saved",
"Saving": "Saving",
+ "Scan_QR_code": "Using an authenticator app like Google Authenticator, Authy or Duo, scan the QR code. It will display a 6 digit code which you need to enter below.",
"Scope": "Scope",
"Screen_Share": "Screen Share",
"Script_Enabled": "Script Enabled",
@@ -1285,6 +1294,7 @@
"Search_Private_Groups": "Search Private Groups",
"seconds": "seconds",
"Secret_token": "Secret token",
+ "Security": "Security",
"Select_a_department": "Select a department",
"Select_a_user": "Select a user",
"Select_an_avatar": "Select an avatar",
@@ -1478,6 +1488,10 @@
"This_is_a_push_test_messsage": "This is a push test message",
"This_room_has_been_archived_by__username_": "This room has been archived by __username__",
"This_room_has_been_unarchived_by__username_": "This room has been unarchived by __username__",
+ "Two-factor_authentication": "Two-factor authentication",
+ "Two-factor_authentication_disabled": "Two-factor authentication disabled",
+ "Two-factor_authentication_enabled": "Two-factor authentication enabled",
+ "Two-factor_authentication_is_currently_disabled": "Two-factor authentication is currently disabled",
"Thursday": "Thursday",
"Time_in_seconds": "Time in seconds",
"Title": "Title",
@@ -1599,6 +1613,7 @@
"Verification_Email_Subject": "[Site_Name] - Verify your account",
"Verification_Email": "Click here to verify your account.",
"Verified": "Verified",
+ "Verify": "Verify",
"Version": "Version",
"Video_Chat_Window": "Video Chat",
"Video_Conference": "Video Conference",
@@ -1653,6 +1668,7 @@
"You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "You can use webhooks to easily integrate livechat with your CRM.",
"You_cant_leave_a_livechat_room_Please_use_the_close_button": "You can't leave a livechat room. Please, use the close button.",
"You_have_been_muted": "You have been muted and cannot speak in this room",
+ "You_have_n_codes_remaining": "You have __number__ codes remaining.",
"You_have_not_verified_your_email": "You have not verified your email.",
"You_have_successfully_unsubscribed": "You have successfully unsubscribed from our Mailling List.",
"You_must_join_to_view_messages_in_this_channel": "You must join to view messages in this channel",
diff --git a/packages/rocketchat-theme/client/imports/base.less b/packages/rocketchat-theme/client/imports/base.less
index b5a45929b789..334cd74107da 100644
--- a/packages/rocketchat-theme/client/imports/base.less
+++ b/packages/rocketchat-theme/client/imports/base.less
@@ -4803,8 +4803,12 @@ body:not(.is-cordova) {
}
}
-.sweet-alert .sa-input-error {
- top: 19px;
+.sweet-alert {
+ margin-left: -239px !important;
+
+ .sa-input-error {
+ top: 19px;
+ }
}
.one-passsword {
diff --git a/packages/rocketchat-ui-account/client/accountFlex.html b/packages/rocketchat-ui-account/client/accountFlex.html
index 6c3e46ddcd6f..e8f433806c48 100644
--- a/packages/rocketchat-ui-account/client/accountFlex.html
+++ b/packages/rocketchat-ui-account/client/accountFlex.html
@@ -10,15 +10,18 @@