Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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 { Lang } from '../../../js/common/lang.js';

export class ComposerUserError extends Error { }
class ComposerNotReadyError extends ComposerUserError { }
Expand Down Expand Up @@ -147,6 +148,10 @@ export class ComposeErrModule extends ViewModule<ComposeView> {
`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)) {
const pwdErrText = this.view.fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy;
throw new ComposerUserError(pwdErrText.split('\n').join('<br />'));
}
} else {
this.view.S.cached('input_password').focus();
throw new ComposerUserError('Some recipients don\'t have encryption set up. Please add a password.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@


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';
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<ComposeView> {

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);
Expand Down Expand Up @@ -78,16 +77,35 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule<ComposeView> {
}
}
this.view.sizeModule.setInputTextHeightManuallyIfNeeded();
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
};

public isVisible = () => {
return !this.view.S.cached('password_or_pubkey').is(':hidden');
};

public isMessagePasswordStrong = (pwd: string): boolean => {
const isLengthValid = pwd.length >= 8;
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;
}
} 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 pwdPolicy = this.view.fesUrl ? Lang.compose.enterprisePasswordPolicy : Lang.compose.consumerPasswordPolicy;
$('#password-policy-container').html(Xss.htmlSanitize(pwdPolicy.split('\n').join('<br />'))); // xss-sanitized
if (!authInfo) {
expirationTextEl.text(Str.pluralize(this.MSG_EXPIRE_DAYS_DEFAULT, 'day'));
} else {
Expand Down Expand Up @@ -116,10 +134,6 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule<ComposeView> {
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 = () => {
Expand All @@ -128,10 +142,6 @@ export class ComposePwdOrPubkeyContainerModule extends ViewModule<ComposeView> {
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();
};

Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/elements/compose.htm
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ <h1 id="header_title" data-test="header-title">New Secure Message</h1>
<td>
<div id="expiration_note">
Recipient will have access to this message <a href="#" id="expiration_note_settings_link">for <span id="expiration_note_message_expire">(loading)</span></a>. You are responsible for sharing this password with them (use other medium to share the password - not email).
<br /> <br />
<br /> <br /><span id="password-policy-container"></span><br /><br />
(Messages sent to FlowCrypt users don't expire this way and don't need password exchange)
</div>
<div>
Expand Down
4 changes: 3 additions & 1 deletion extension/chrome/elements/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions extension/js/common/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. ',
Expand Down
10 changes: 8 additions & 2 deletions test/source/tests/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}));
Expand Down