From cff4ada189aec068f82f17c20d98fa8300e9f40f Mon Sep 17 00:00:00 2001 From: Julian Adams Date: Thu, 6 Sep 2018 16:48:18 -0500 Subject: [PATCH 1/2] Check username before enter password on Login --- email_login/src/components/Login.js | 29 ++++--- email_login/src/components/LoginWrapper.js | 76 ++++++++++++------- email_login/src/components/login.scss | 69 +++++++++-------- .../src/components/lostAllDevices.scss | 2 +- email_login/src/utils/electronInterface.js | 5 +- 5 files changed, 103 insertions(+), 78 deletions(-) diff --git a/email_login/src/components/Login.js b/email_login/src/components/Login.js index c247d296f..ad6a3eb8b 100644 --- a/email_login/src/components/Login.js +++ b/email_login/src/components/Login.js @@ -27,21 +27,18 @@ const renderForm = props => (
-
); -export const EmptyRecoveryEmail = () => ( -
-

- You did not set a Recovery Email so account recovery is - impossible if you forget your password. -

-

Proceed without recovery email?

-
-); +export const EmptyRecoveryEmail = () => { + return ( +
+

+ You did not set a Recovery Email so account recovery is + impossible if you forget your password. +

+
+ ); +}; -export const ForgotPasswordSentLink = () => ( -
-

A reset link will be sent to d******@o****l.com

-

The link will be valid for 1 hour

-
-); +export const ForgotPasswordSentLink = customText => { + const content = + customText || + `A reset link was sent to your Recovery email\nThe link will be valid for 30 min`; + return ( +
+

{content}

+
+ ); +}; -export const ForgotPasswordEmptyEmail = () => ( +export const ForgotPasswordEmptyEmail = customText => (
-

You need to set a Recovery Email to reset your password.

+

+ {customText || `You need to set a Recovery Email to reset your password`} +

); diff --git a/email_login/src/components/LostAllDevices.js b/email_login/src/components/LostAllDevices.js index ac8fc1ba1..373069ea3 100644 --- a/email_login/src/components/LostAllDevices.js +++ b/email_login/src/components/LostAllDevices.js @@ -49,7 +49,6 @@ const renderForm = props => ( placeholder="Password" value={props.values.password} onChange={props.onChangeField} - onKeyUp={props.validator} disabled={props.isLoading} autoFocus={true} /> diff --git a/email_login/src/components/LostAllDevicesWrapper.js b/email_login/src/components/LostAllDevicesWrapper.js index 7ba27517e..b68dc6d4a 100644 --- a/email_login/src/components/LostAllDevicesWrapper.js +++ b/email_login/src/components/LostAllDevicesWrapper.js @@ -11,10 +11,12 @@ import { login, openMailbox, throwError, - errors + errors, + resetPassword } from './../utils/electronInterface'; import signal from './../libs/signal'; import { hashPassword } from '../utils/HashUtils'; +import { censureEmailAddress } from '../utils/StringUtils'; class LostDevicesWrapper extends Component { constructor(props) { @@ -25,7 +27,6 @@ class LostDevicesWrapper extends Component { password: '' }, disabled: true, - hasRecoveryEmail: true, isLoading: false }; } @@ -100,17 +101,20 @@ class LostDevicesWrapper extends Component { } }; - handleForgot = event => { - event.preventDefault(); - event.stopPropagation(); - if (this.state.hasRecoveryEmail === true) { - confirmForgotPasswordSentLink(response => { + handleForgot = async ev => { + ev.preventDefault(); + ev.stopPropagation(); + const recipientId = this.state.values.username; + const { status, text } = await resetPassword(recipientId); + const customText = this.getForgotPasswordMessage(status, text); + if (status === 200) { + confirmForgotPasswordSentLink(customText, response => { if (response) { closeDialog(); } }); } else { - confirmForgotPasswordEmptyEmail(response => { + confirmForgotPasswordEmptyEmail(customText, response => { if (response) { closeDialog(); } @@ -118,6 +122,16 @@ class LostDevicesWrapper extends Component { } }; + getForgotPasswordMessage = (status, text) => { + if (status === 200) { + const { address } = JSON.parse(text); + return `A reset link was sent to ${censureEmailAddress( + address + )}\nThe link will be valid for 30 minutes`; + } + return text; + }; + createAccountWithNewDevice = async ({ recipientId, deviceId, name }) => { try { await signal.createAccountWithNewDevice({ diff --git a/email_login/src/utils/StringUtils.js b/email_login/src/utils/StringUtils.js new file mode 100644 index 000000000..70126c2f0 --- /dev/null +++ b/email_login/src/utils/StringUtils.js @@ -0,0 +1,36 @@ +export const censureEmailAddress = address => { + const [user, domain] = address.split('@'); + const domainLevels = domain.split('.'); + const lastLevel = domainLevels.pop(); + const character = '*'; + const censoredUser = replaceCharacters(user, [0], character); + const censoredDomain = `${replaceCharacters( + domainLevels.join('.'), + [0, -1], + character + )}.${lastLevel}`; + return `${censoredUser}@${censoredDomain}`; +}; + +export const replaceCharacters = (string, positionsToExclude, character) => { + if (typeof positionsToExclude === 'string') { + return positionsToExclude === 'all' + ? string + : positionsToExclude === 'none' + ? character.repeat(string.length) + : string; + } + if (Array.isArray(positionsToExclude)) { + const sanitizedPositions = positionsToExclude.map(position => { + return position < 0 ? string.length + position : position; + }); + let replacedString = ''; + for (let i = 0; i < string.length; i++) { + const newCharacter = sanitizedPositions.includes(i) + ? string.charAt(i) + : character; + replacedString += newCharacter; + } + return replacedString; + } +}; diff --git a/email_login/src/utils/__tests__/StringUtils.js b/email_login/src/utils/__tests__/StringUtils.js new file mode 100644 index 000000000..286e8df10 --- /dev/null +++ b/email_login/src/utils/__tests__/StringUtils.js @@ -0,0 +1,41 @@ +/* eslint-env node, jest */ + +import { censureEmailAddress, replaceCharacters } from './../StringUtils'; + +describe('Censure email address: ', () => { + it('Should censure all email address', () => { + const email = 'testemail@another.domain.level.com'; + const expected = 't********@a******************l.com'; + const censoredEmail = censureEmailAddress(email); + expect(censoredEmail).toEqual(expected); + }); +}); + +describe('Replace characters in string ', () => { + it('Should replace all string. Param type: String', () => { + const initialString = 'thisisarandomstringtoreplace'; + const expectedString = '****************************'; + const exclude = 'none'; + const character = '*'; + const replacedString = replaceCharacters(initialString, exclude, character); + expect(replacedString).toEqual(expectedString); + }); + + it('Should not replace string. Param type: String', () => { + const initialString = 'thisisarandomstringtoreplace'; + const expectedString = 'thisisarandomstringtoreplace'; + const exclude = 'all'; + const character = '*'; + const replacedString = replaceCharacters(initialString, exclude, character); + expect(replacedString).toEqual(expectedString); + }); + + it('Should replace some positions in string. Param type: Array', () => { + const initialString = 'thisisarandomstringtoreplace'; + const expectedString = 't**************************e'; + const exclude = [0, -1]; + const character = '*'; + const replacedString = replaceCharacters(initialString, exclude, character); + expect(replacedString).toEqual(expectedString); + }); +}); diff --git a/email_login/src/utils/electronInterface.js b/email_login/src/utils/electronInterface.js index 813f71f77..925034b2b 100644 --- a/email_login/src/utils/electronInterface.js +++ b/email_login/src/utils/electronInterface.js @@ -13,7 +13,7 @@ webFrame.setVisualZoomLevelLimits(1, 1); webFrame.setLayoutZoomLevelLimits(0, 0); /* Window events - ----------------------------- */ + ----------------------------- */ export const closeDialog = () => { ipcRenderer.send('close-modal'); }; @@ -58,10 +58,11 @@ export const confirmLostDevices = callback => { }); }; -export const confirmForgotPasswordEmptyEmail = callback => { +export const confirmForgotPasswordEmptyEmail = (customText, callback) => { const dataForModal = { title: 'Alert!', contentType: 'FORGOT_PASSWORD_EMPTY_EMAIL', + customTextToReplace: customText, options: { cancelLabel: 'Cancel', acceptLabel: 'Ok' @@ -74,12 +75,12 @@ export const confirmForgotPasswordEmptyEmail = callback => { }); }; -export const confirmForgotPasswordSentLink = callback => { +export const confirmForgotPasswordSentLink = (customText, callback) => { const dataForModal = { - title: 'Forgot Password?', - contentType: 'FORGOT_PASSWORD_SENT_LINK', + title: 'Forgot Password', + contentType: 'FORGOT_PASSWORD_SEND_LINK', + customTextToReplace: customText, options: { - cancelLabel: 'Cancel', acceptLabel: 'Ok' }, sendTo: 'login' @@ -111,7 +112,7 @@ export const throwError = error => { }; /* Criptext Client - ----------------------------- */ + ----------------------------- */ export const checkAvailableUsername = username => { return clientManager.checkAvailableUsername(username); }; @@ -128,8 +129,12 @@ export const postUser = params => { return clientManager.postUser(params); }; +export const resetPassword = recipientId => { + return clientManager.resetPassword(recipientId); +}; + /* DataBase - ----------------------------- */ + ----------------------------- */ export const cleanDataBase = params => { return dbManager.cleanDataBase(params); };