From 41ba623ff006b9ded7c93d2e9861e0618e68f827 Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 16:44:47 +0000 Subject: [PATCH 1/8] issue 4251 password policy --- .../compose-modules/compose-err-module.ts | 7 +++++++ .../compose-pwd-or-pubkey-container-module.ts | 20 +++++++++++++++++++ extension/chrome/elements/compose.htm | 2 +- extension/js/common/lang.ts | 2 ++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/extension/chrome/elements/compose-modules/compose-err-module.ts b/extension/chrome/elements/compose-modules/compose-err-module.ts index 6ee5695046d..99d0c775be3 100644 --- a/extension/chrome/elements/compose-modules/compose-err-module.ts +++ b/extension/chrome/elements/compose-modules/compose-err-module.ts @@ -15,6 +15,8 @@ import { Xss } from '../../../js/common/platform/xss.js'; import { ViewModule } from '../../../js/common/view-module.js'; import { ComposeView } from '../compose.js'; import { AjaxErrMsgs } from '../../../js/common/api/shared/api-error.js'; +import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; +import { Lang } from '../../../js/common/lang.js'; export class ComposerUserError extends Error { } class ComposerNotReadyError extends ComposerUserError { } @@ -147,6 +149,11 @@ export class ComposeErrModule extends ViewModule { `Sharing password over email undermines password based encryption.\n\n` + `You can ask the recipient to also install FlowCrypt, messages between FlowCrypt users don't need a password.`); } + const { fesUrl } = await AcctStore.get(this.view.acctEmail, ['fesUrl']); + if (!this.view.pwdOrPubkeyContainerModule.isPasswordStrong(pwd, !!fesUrl)) { + const pwdErrText = fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; + throw new ComposerUserError(pwdErrText.split('\n').join('
')); + } } else { this.view.S.cached('input_password').focus(); throw new ComposerUserError('Some recipients don\'t have encryption set up. Please add a password.'); diff --git a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts index 1774737c512..6c289e924b0 100644 --- a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts +++ b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts @@ -11,6 +11,8 @@ import { ApiErr } from '../../../js/common/api/shared/api-error.js'; import { ViewModule } from '../../../js/common/view-module.js'; import { ComposeView } from '../compose.js'; import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; +import { Lang } from '../../../js/common/lang.js'; +import { Xss } from '../../../js/common/platform/xss.js'; export class ComposePwdOrPubkeyContainerModule extends ViewModule { @@ -84,10 +86,28 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { return !this.view.S.cached('password_or_pubkey').is(':hidden'); }; + public isPasswordStrong = (pwd: string, isFesUsed: boolean): boolean => { + const isLengthValid = pwd.length >= 8; + if (isFesUsed) { // enterprise FES - use common corporate password rules + const isContentValid = /[0-9]/.test(pwd) && /[A-Z]/.test(pwd) && /[a-z]/.test(pwd) && /[^A-Za-z0-9]/.test(pwd); + if (!isContentValid || !isLengthValid) { + return false; + } + } else { // consumers - just 8 chars requirement + if (!isLengthValid) { + return false; + } + } + return true; + }; + private showMsgPwdUiAndColorBtn = async (anyNopgp: boolean, anyRevoked: boolean) => { if (!this.isVisible()) { const authInfo = await AcctStore.authInfo(this.view.acctEmail); const expirationTextEl = this.view.S.cached('expiration_note').find('#expiration_note_message_expire'); + const { fesUrl } = await AcctStore.get(this.view.acctEmail, ['fesUrl']); + const pwdPolicy = fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; + $('#password-policy-container').html(Xss.htmlSanitize(pwdPolicy.split('\n').join('
'))); if (!authInfo) { expirationTextEl.text(Str.pluralize(this.MSG_EXPIRE_DAYS_DEFAULT, 'day')); } else { diff --git a/extension/chrome/elements/compose.htm b/extension/chrome/elements/compose.htm index c16864bcda4..c6c043ece48 100644 --- a/extension/chrome/elements/compose.htm +++ b/extension/chrome/elements/compose.htm @@ -122,7 +122,7 @@

New Secure Message

Recipient will have access to this message for (loading). You are responsible for sharing this password with them (use other medium to share the password - not email). -

+



(Messages sent to FlowCrypt users don't expire this way and don't need password exchange)
diff --git a/extension/js/common/lang.ts b/extension/js/common/lang.ts index 5c5b77aa8bb..301206e1bf1 100644 --- a/extension/js/common/lang.ts +++ b/extension/js/common/lang.ts @@ -79,6 +79,8 @@ export const Lang = { // tslint:disable-line:variable-name pubkeyExpiredConfirmCompose: 'The public key of one of your recipients is expired.\n\nThe right thing to do is to ask the recipient to send you an updated Public Key.\n\nAre you sure you want to encrypt this message for an expired public key? (NOT RECOMMENDED)', needReadAccessToReply: 'FlowCrypt has limited functionality. Your browser needs to access this conversation to reply.', addMissingPermission: 'Add missing permission', + enterprisePasswordPolicy: 'Please use password with the following properties:\n - one uppercase\n - one lowercase\n - one number\n - one special character eg &\"#-\'_%-@,;:!*()\n - 8 characters length', + consumerPasswordPolicy: 'Please use a password at least 8 characters long', }, general: { somethingWentWrongTryAgain: 'Something went wrong, please try again. If this happens again, please write us at human@flowcrypt.com to fix it. ', From ddeeaa5f7b48fe0996d93593a8b38d7b71df215f Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 17:18:36 +0000 Subject: [PATCH 2/8] color input based on pass/fail of password strength --- .../compose-modules/compose-err-module.ts | 2 +- .../compose-pwd-or-pubkey-container-module.ts | 15 +++------------ extension/chrome/elements/compose.ts | 4 +++- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-err-module.ts b/extension/chrome/elements/compose-modules/compose-err-module.ts index 99d0c775be3..94ff2dc33f4 100644 --- a/extension/chrome/elements/compose-modules/compose-err-module.ts +++ b/extension/chrome/elements/compose-modules/compose-err-module.ts @@ -150,7 +150,7 @@ export class ComposeErrModule extends ViewModule { `You can ask the recipient to also install FlowCrypt, messages between FlowCrypt users don't need a password.`); } const { fesUrl } = await AcctStore.get(this.view.acctEmail, ['fesUrl']); - if (!this.view.pwdOrPubkeyContainerModule.isPasswordStrong(pwd, !!fesUrl)) { + if (!this.view.pwdOrPubkeyContainerModule.isMessagePasswordStrong(pwd, !!fesUrl)) { const pwdErrText = fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; throw new ComposerUserError(pwdErrText.split('\n').join('
')); } diff --git a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts index 6c289e924b0..509936b21cb 100644 --- a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts +++ b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts @@ -4,7 +4,6 @@ import { RecipientStatus, SendBtnTexts } from './compose-types.js'; -import { KeyImportUi } from '../../../js/common/ui/key-import-ui.js'; import { Catch } from '../../../js/common/platform/catch.js'; import { Str } from '../../../js/common/core/common.js'; import { ApiErr } from '../../../js/common/api/shared/api-error.js'; @@ -17,8 +16,6 @@ import { Xss } from '../../../js/common/platform/xss.js'; export class ComposePwdOrPubkeyContainerModule extends ViewModule { private MSG_EXPIRE_DAYS_DEFAULT = 3; // todo - update to 7 (needs backend work) - private keyImportUI = new KeyImportUi({}); - private rmPwdStrengthValidationElements: (() => void) | undefined; constructor(view: ComposeView, hideMsgPwd: boolean | undefined) { super(view); @@ -80,13 +77,15 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { } } this.view.sizeModule.setInputTextHeightManuallyIfNeeded(); + const pwdOk = this.isMessagePasswordStrong(String(this.view.S.cached('input_password').val()), !!this.view.fesUrl); + this.view.S.cached('input_password').css('color', pwdOk ? '#31a217' : '#d14836'); // green : red }; public isVisible = () => { return !this.view.S.cached('password_or_pubkey').is(':hidden'); }; - public isPasswordStrong = (pwd: string, isFesUsed: boolean): boolean => { + public isMessagePasswordStrong = (pwd: string, isFesUsed: boolean): boolean => { const isLengthValid = pwd.length >= 8; if (isFesUsed) { // enterprise FES - use common corporate password rules const isContentValid = /[0-9]/.test(pwd) && /[A-Z]/.test(pwd) && /[a-z]/.test(pwd) && /[^A-Za-z0-9]/.test(pwd); @@ -136,10 +135,6 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { this.view.S.cached('warning_nopgp').css('display', anyNopgp ? 'inline-block' : 'none'); this.view.S.cached('warning_revoked').css('display', anyRevoked ? 'inline-block' : 'none'); this.view.sizeModule.setInputTextHeightManuallyIfNeeded(); - if (!this.rmPwdStrengthValidationElements) { - const { removeValidationElements } = this.keyImportUI.renderPassPhraseStrengthValidationInput($("#input_password"), undefined, 'pwd'); - this.rmPwdStrengthValidationElements = removeValidationElements; - } }; private hideMsgPwdUi = () => { @@ -148,10 +143,6 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { this.view.S.cached('add_intro').css('display', 'none'); this.view.S.cached('input_intro').text(''); this.view.S.cached('intro_container').css('display', 'none'); - if (this.rmPwdStrengthValidationElements) { - this.rmPwdStrengthValidationElements(); - this.rmPwdStrengthValidationElements = undefined; - } this.view.sizeModule.setInputTextHeightManuallyIfNeeded(); }; diff --git a/extension/chrome/elements/compose.ts b/extension/chrome/elements/compose.ts index 412a59c9c5b..b0402fc8b93 100644 --- a/extension/chrome/elements/compose.ts +++ b/extension/chrome/elements/compose.ts @@ -43,6 +43,7 @@ export class ComposeView extends View { public readonly isReplyBox: boolean; public readonly replyMsgId: string; public readonly replyPubkeyMismatch: boolean; + public fesUrl?: string; public skipClickPrompt: boolean; public draftId: string; public threadId: string = ''; @@ -144,7 +145,7 @@ export class ComposeView extends View { } public render = async () => { - const storage = await AcctStore.get(this.acctEmail, ['sendAs', 'hide_message_password']); + const storage = await AcctStore.get(this.acctEmail, ['sendAs', 'hide_message_password', 'fesUrl']); this.orgRules = await OrgRules.newInstance(this.acctEmail); if (this.orgRules.shouldHideArmorMeta()) { opgp.config.show_comment = false; @@ -160,6 +161,7 @@ export class ComposeView extends View { this.recipientsModule = new ComposeRecipientsModule(this); this.sendBtnModule = new ComposeSendBtnModule(this); this.pwdOrPubkeyContainerModule = new ComposePwdOrPubkeyContainerModule(this, storage.hide_message_password); + this.fesUrl = storage.fesUrl; this.sizeModule = new ComposeSizeModule(this); this.senderModule = new ComposeSenderModule(this); this.footerModule = new ComposeFooterModule(this); From 84888af08eff8aedac50d9babeab33770fda5262 Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 17:24:38 +0000 Subject: [PATCH 3/8] simplify --- .../compose-modules/compose-pwd-or-pubkey-container-module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts index 509936b21cb..48d2e355003 100644 --- a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts +++ b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts @@ -104,8 +104,7 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { if (!this.isVisible()) { const authInfo = await AcctStore.authInfo(this.view.acctEmail); const expirationTextEl = this.view.S.cached('expiration_note').find('#expiration_note_message_expire'); - const { fesUrl } = await AcctStore.get(this.view.acctEmail, ['fesUrl']); - const pwdPolicy = fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; + const pwdPolicy = this.view.fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; $('#password-policy-container').html(Xss.htmlSanitize(pwdPolicy.split('\n').join('
'))); if (!authInfo) { expirationTextEl.text(Str.pluralize(this.MSG_EXPIRE_DAYS_DEFAULT, 'day')); From 54ac02a104512f98d417b9565399819e04ba3f4d Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 17:25:16 +0000 Subject: [PATCH 4/8] simplify --- .../chrome/elements/compose-modules/compose-err-module.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-err-module.ts b/extension/chrome/elements/compose-modules/compose-err-module.ts index 94ff2dc33f4..a8457572f89 100644 --- a/extension/chrome/elements/compose-modules/compose-err-module.ts +++ b/extension/chrome/elements/compose-modules/compose-err-module.ts @@ -149,9 +149,8 @@ export class ComposeErrModule extends ViewModule { `Sharing password over email undermines password based encryption.\n\n` + `You can ask the recipient to also install FlowCrypt, messages between FlowCrypt users don't need a password.`); } - const { fesUrl } = await AcctStore.get(this.view.acctEmail, ['fesUrl']); - if (!this.view.pwdOrPubkeyContainerModule.isMessagePasswordStrong(pwd, !!fesUrl)) { - const pwdErrText = fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; + if (!this.view.pwdOrPubkeyContainerModule.isMessagePasswordStrong(pwd, !!this.view.fesUrl)) { + const pwdErrText = this.view.fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; throw new ComposerUserError(pwdErrText.split('\n').join('
')); } } else { From 62a6d386d369edd42b666c24820520229ace02a7 Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 17:26:25 +0000 Subject: [PATCH 5/8] simplify --- .../chrome/elements/compose-modules/compose-err-module.ts | 2 +- .../compose-pwd-or-pubkey-container-module.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-err-module.ts b/extension/chrome/elements/compose-modules/compose-err-module.ts index a8457572f89..90ed9054a60 100644 --- a/extension/chrome/elements/compose-modules/compose-err-module.ts +++ b/extension/chrome/elements/compose-modules/compose-err-module.ts @@ -149,7 +149,7 @@ export class ComposeErrModule extends ViewModule { `Sharing password over email undermines password based encryption.\n\n` + `You can ask the recipient to also install FlowCrypt, messages between FlowCrypt users don't need a password.`); } - if (!this.view.pwdOrPubkeyContainerModule.isMessagePasswordStrong(pwd, !!this.view.fesUrl)) { + if (!this.view.pwdOrPubkeyContainerModule.isMessagePasswordStrong(pwd)) { const pwdErrText = this.view.fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; throw new ComposerUserError(pwdErrText.split('\n').join('
')); } diff --git a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts index 48d2e355003..61f44d34a89 100644 --- a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts +++ b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts @@ -77,7 +77,7 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { } } this.view.sizeModule.setInputTextHeightManuallyIfNeeded(); - const pwdOk = this.isMessagePasswordStrong(String(this.view.S.cached('input_password').val()), !!this.view.fesUrl); + const pwdOk = this.isMessagePasswordStrong(String(this.view.S.cached('input_password').val())); this.view.S.cached('input_password').css('color', pwdOk ? '#31a217' : '#d14836'); // green : red }; @@ -85,9 +85,9 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { return !this.view.S.cached('password_or_pubkey').is(':hidden'); }; - public isMessagePasswordStrong = (pwd: string, isFesUsed: boolean): boolean => { + public isMessagePasswordStrong = (pwd: string): boolean => { const isLengthValid = pwd.length >= 8; - if (isFesUsed) { // enterprise FES - use common corporate password rules + if (this.view.fesUrl) { // enterprise FES - use common corporate password rules const isContentValid = /[0-9]/.test(pwd) && /[A-Z]/.test(pwd) && /[a-z]/.test(pwd) && /[^A-Za-z0-9]/.test(pwd); if (!isContentValid || !isLengthValid) { return false; From eccf630f3ebf1f767bc83a6771e5382b34328e64 Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 17:33:24 +0000 Subject: [PATCH 6/8] fix build --- extension/chrome/elements/compose-modules/compose-err-module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extension/chrome/elements/compose-modules/compose-err-module.ts b/extension/chrome/elements/compose-modules/compose-err-module.ts index 90ed9054a60..87ecc8532f3 100644 --- a/extension/chrome/elements/compose-modules/compose-err-module.ts +++ b/extension/chrome/elements/compose-modules/compose-err-module.ts @@ -15,7 +15,6 @@ import { Xss } from '../../../js/common/platform/xss.js'; import { ViewModule } from '../../../js/common/view-module.js'; import { ComposeView } from '../compose.js'; import { AjaxErrMsgs } from '../../../js/common/api/shared/api-error.js'; -import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; import { Lang } from '../../../js/common/lang.js'; export class ComposerUserError extends Error { } From c3c6b3e0502e274784676e17cd9f0442e63c8d0d Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 17:51:18 +0000 Subject: [PATCH 7/8] fix code style --- .../compose-modules/compose-pwd-or-pubkey-container-module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts index 61f44d34a89..0eba94dec03 100644 --- a/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts +++ b/extension/chrome/elements/compose-modules/compose-pwd-or-pubkey-container-module.ts @@ -105,7 +105,7 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule { const authInfo = await AcctStore.authInfo(this.view.acctEmail); const expirationTextEl = this.view.S.cached('expiration_note').find('#expiration_note_message_expire'); const pwdPolicy = this.view.fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy; - $('#password-policy-container').html(Xss.htmlSanitize(pwdPolicy.split('\n').join('
'))); + $('#password-policy-container').html(Xss.htmlSanitize(pwdPolicy.split('\n').join('
'))); // xss-sanitized if (!authInfo) { expirationTextEl.text(Str.pluralize(this.MSG_EXPIRE_DAYS_DEFAULT, 'day')); } else { From c30a89d8606314c633a7097ae59e363dfa7dd194 Mon Sep 17 00:00:00 2001 From: Tom J Date: Thu, 13 Jan 2022 19:07:53 +0000 Subject: [PATCH 8/8] test --- test/source/tests/compose.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/source/tests/compose.ts b/test/source/tests/compose.ts index bb328a47f37..797d070b45a 100644 --- a/test/source/tests/compose.ts +++ b/test/source/tests/compose.ts @@ -1535,13 +1535,19 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); await SetupPageRecipe.manualEnter(settingsPage, 'flowcrypt.test.key.used.pgp', { submitPubkey: false, usedPgpBefore: false }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false }); - const msgPwd = 'super hard password for the message'; const subject = 'PWD encrypted message with FES - ID TOKEN'; const composePage = await ComposePageRecipe.openStandalone(t, browser, 'user@standardsubdomainfes.test:8001'); await ComposePageRecipe.fillMsg(composePage, { to: 'to@example.com', bcc: 'bcc@example.com' }, subject); const fileInput = await composePage.target.$('input[type=file]'); await fileInput!.uploadFile('test/samples/small.txt'); - await ComposePageRecipe.sendAndClose(composePage, { password: msgPwd }); + // lousy pwd + await composePage.waitAndType('@input-password', 'lousy pwd'); + await composePage.waitAndClick('@action-send', { delay: 1 }); + await composePage.waitAndRespondToModal('error', 'confirm', 'Please use password with the following properties'); + // good pwd + await composePage.waitAndType('@input-password', 'gO0d-pwd'); + await composePage.waitAndClick('@action-send', { delay: 1 }); + await ComposePageRecipe.closed(composePage); // this test is using PwdEncryptedMessageWithFesIdTokenTestStrategy to check sent result based on subject "PWD encrypted message with flowcrypt.com/api" // also see '/api/v1/message' in fes-endpoints.ts mock }));