diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2e1fb9c90..fc07c6722 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2181,6 +2181,12 @@ "description": "Description for remove account password setting view" }, + "enterPassword": { + "message": "Please enter your password" + }, + "confirmPassword": { + "message": "Confirm password" + }, "showSeedPasswordRequest": { "message": "Please enter your password", "description": "Request for user to enter password to show seed." @@ -2290,6 +2296,33 @@ "passwordsDoNotMatch": { "message": "Passwords do not match" }, + "setPasswordInvalid": { + "message": "Passwords do not match" + }, + "changePasswordInvalid": { + "message": "The old password you entered is incorrect" + }, + "removePasswordInvalid": { + "message": "Incorrect password" + }, + "setPasswordTitle": { + "message": "Set Password" + }, + "changePasswordTitle": { + "message": "Changed Password" + }, + "removePasswordTitle": { + "message": "Removed Password" + }, + "setPasswordToastDescription": { + "message": "Your password has been set. Please keep it safe." + }, + "changePasswordToastDescription": { + "message": "Your password has been changed. Please keep it safe." + }, + "removePasswordToastDescription": { + "message": "You have removed your password." + }, "publicChatExists": { "message": "You are already connected to this public channel" }, diff --git a/js/background.js b/js/background.js index aaec0e28a..8105b2ff4 100644 --- a/js/background.js +++ b/js/background.js @@ -821,6 +821,7 @@ }; window.showSeedDialog = window.owsDesktopApp.appView.showSeedDialog; + window.showPasswordDialog = window.owsDesktopApp.appView.showPasswordDialog; window.generateID = () => Math.random() @@ -838,6 +839,7 @@ id: options.id || window.generateID(), description: options.description || '', type: options.type || '', + icon: options.icon || '', shouldFade: options.shouldFade, }; @@ -1183,12 +1185,6 @@ } }); - Whisper.events.on('showPasswordDialog', options => { - if (appView) { - appView.showPasswordDialog(options); - } - }); - Whisper.events.on('showSeedDialog', async () => { if (appView) { appView.showSeedDialog(); diff --git a/js/modules/signal.js b/js/modules/signal.js index 8554f1c32..7733af81a 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -70,6 +70,10 @@ const { const { SessionSeedModal, } = require('../../ts/components/session/SessionSeedModal'); + +const { SessionPasswordModal, +} = require('../../ts/components/session/SessionPasswordModal'); + const { SessionConfirm, } = require('../../ts/components/session/SessionConfirm'); @@ -284,6 +288,7 @@ exports.setup = (options = {}) => { SessionModal, SessionQRModal, SessionSeedModal, + SessionPasswordModal, SessionDropdown, MediaGallery, Message, diff --git a/js/views/app_view.js b/js/views/app_view.js index 636740f0a..7c1b59d52 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -17,6 +17,7 @@ this.applyHideMenu(); this.showSeedDialog = this.showSeedDialog.bind(this); + this.showPasswordDialog = this.showPasswordDialog.bind(this); }, events: { 'click .openInstaller': 'openInstaller', // NetworkStatusView has this button @@ -198,8 +199,8 @@ this.el.append(dialog.el); dialog.focusInput(); }, - showPasswordDialog() { - const dialog = Whisper.PasswordDialogView(); + showPasswordDialog(options) { + const dialog = new Whisper.PasswordDialogView(options); this.el.append(dialog.el); }, showSeedDialog() { diff --git a/js/views/password_dialog_view copy.js b/js/views/password_dialog_view copy.js new file mode 100644 index 000000000..57d2afa00 --- /dev/null +++ b/js/views/password_dialog_view copy.js @@ -0,0 +1,228 @@ +/* global Whisper, i18n, _, Signal, passwordUtil */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + const PasswordDialogView = Whisper.View.extend({ + className: 'loki-dialog password-dialog modal', + templateName: 'password-dialog', + initialize(options) { + this.type = options.type; + this.resolve = options.resolve; + this.okText = options.okText || i18n('ok'); + + this.reject = options.reject; + this.cancelText = options.cancelText || i18n('cancel'); + + this.title = options.title; + + this.render(); + this.updateUI(); + }, + events: { + keyup: 'onKeyup', + 'click .ok': 'ok', + 'click .cancel': 'cancel', + }, + render_attributes() { + return { + showCancel: !this.hideCancel, + cancel: this.cancelText, + ok: this.okText, + title: this.title, + }; + }, + async updateUI() { + if (this.disableOkButton()) { + this.$('.ok').prop('disabled', true); + } else { + this.$('.ok').prop('disabled', false); + } + }, + disableOkButton() { + const password = this.$('#password').val(); + return _.isEmpty(password); + }, + async validate() { + const password = this.$('#password').val(); + const passwordConfirmation = this.$('#password-confirmation').val(); + + const pairValidation = this.validatePasswordPair( + password, + passwordConfirmation + ); + const hashValidation = await this.validatePasswordHash(password); + + return pairValidation || hashValidation; + }, + async validatePasswordHash(password) { + // Check if the password matches the hash we have stored + const hash = await Signal.Data.getPasswordHash(); + if (hash && !passwordUtil.matchesHash(password, hash)) { + return i18n('invalidPassword'); + } + return null; + }, + validatePasswordPair(password, passwordConfirmation) { + if (!_.isEmpty(password)) { + // Check if the password is first valid + const passwordValidation = passwordUtil.validatePassword( + password, + i18n + ); + if (passwordValidation) { + return passwordValidation; + } + + // Check if the confirmation password is the same + if ( + !passwordConfirmation || + password.trim() !== passwordConfirmation.trim() + ) { + return i18n('passwordsDoNotMatch'); + } + } + return null; + }, + okPressed() { + const password = this.$('#password').val(); + if (this.type === 'set') { + window.setPassword(password.trim()); + } else if (this.type === 'remove') { + window.setPassword(null, password.trim()); + } + }, + okErrored() { + if (this.type === 'set') { + this.showError(i18n('setPasswordFail')); + } else if (this.type === 'remove') { + this.showError(i18n('removePasswordFail')); + } + }, + async ok() { + const error = await this.validate(); + if (error) { + this.showError(error); + return; + } + + // Clear any errors + this.showError(null); + + try { + this.okPressed(); + + this.remove(); + if (this.resolve) { + this.resolve(); + } + } catch (e) { + this.okErrored(); + } + }, + cancel() { + this.remove(); + if (this.reject) { + this.reject(); + } + }, + onKeyup(event) { + this.updateUI(); + switch (event.key) { + case 'Enter': + this.ok(); + break; + case 'Escape': + case 'Esc': + this.cancel(); + break; + default: + return; + } + event.preventDefault(); + }, + focusCancel() { + this.$('.cancel').focus(); + }, + showError(message) { + if (_.isEmpty(message)) { + this.$('.error').text(''); + this.$('.error').hide(); + } else { + this.$('.error').text(`Error: ${message}`); + this.$('.error').show(); + } + }, + }); + + const ChangePasswordDialogView = PasswordDialogView.extend({ + templateName: 'password-change-dialog', + disableOkButton() { + const oldPassword = this.$('#old-password').val(); + const newPassword = this.$('#new-password').val(); + return _.isEmpty(oldPassword) || _.isEmpty(newPassword); + }, + async validate() { + const oldPassword = this.$('#old-password').val(); + + // Validate the old password + if (!_.isEmpty(oldPassword)) { + const oldPasswordValidation = passwordUtil.validatePassword( + oldPassword, + i18n + ); + if (oldPasswordValidation) { + return oldPasswordValidation; + } + } else { + return i18n('typeInOldPassword'); + } + + const password = this.$('#new-password').val(); + const passwordConfirmation = this.$('#new-password-confirmation').val(); + + const pairValidation = this.validatePasswordPair( + password, + passwordConfirmation + ); + const hashValidation = await this.validatePasswordHash(oldPassword); + + return pairValidation || hashValidation; + }, + okPressed() { + const oldPassword = this.$('#old-password').val(); + const newPassword = this.$('#new-password').val(); + window.setPassword(newPassword.trim(), oldPassword.trim()); + }, + okErrored() { + this.showError(i18n('changePasswordFail')); + }, + }); + + Whisper.getPasswordDialogView = (type, resolve, reject) => { + // This is a differently styled dialog + if (type === 'change') { + return new ChangePasswordDialogView({ + title: i18n('changePassword'), + okTitle: i18n('change'), + resolve, + reject, + }); + } + + // Set and Remove is basically the same UI + const title = + type === 'remove' ? i18n('removePassword') : i18n('setPassword'); + const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); + return new PasswordDialogView({ + title, + okTitle, + type, + resolve, + reject, + }); + }; +})(); diff --git a/js/views/password_dialog_view.js b/js/views/password_dialog_view.js index 57d2afa00..646e617ee 100644 --- a/js/views/password_dialog_view.js +++ b/js/views/password_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, _, Signal, passwordUtil */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -6,223 +6,43 @@ window.Whisper = window.Whisper || {}; - const PasswordDialogView = Whisper.View.extend({ + Whisper.PasswordDialogView = Whisper.View.extend({ className: 'loki-dialog password-dialog modal', - templateName: 'password-dialog', initialize(options) { - this.type = options.type; - this.resolve = options.resolve; - this.okText = options.okText || i18n('ok'); - - this.reject = options.reject; - this.cancelText = options.cancelText || i18n('cancel'); - - this.title = options.title; + this.close = this.close.bind(this); + this.onOk = this.onOk.bind(this); + this.props = options; this.render(); - this.updateUI(); - }, - events: { - keyup: 'onKeyup', - 'click .ok': 'ok', - 'click .cancel': 'cancel', - }, - render_attributes() { - return { - showCancel: !this.hideCancel, - cancel: this.cancelText, - ok: this.okText, - title: this.title, - }; - }, - async updateUI() { - if (this.disableOkButton()) { - this.$('.ok').prop('disabled', true); - } else { - this.$('.ok').prop('disabled', false); - } - }, - disableOkButton() { - const password = this.$('#password').val(); - return _.isEmpty(password); - }, - async validate() { - const password = this.$('#password').val(); - const passwordConfirmation = this.$('#password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(password); - - return pairValidation || hashValidation; }, - async validatePasswordHash(password) { - // Check if the password matches the hash we have stored - const hash = await Signal.Data.getPasswordHash(); - if (hash && !passwordUtil.matchesHash(password, hash)) { - return i18n('invalidPassword'); - } - return null; - }, - validatePasswordPair(password, passwordConfirmation) { - if (!_.isEmpty(password)) { - // Check if the password is first valid - const passwordValidation = passwordUtil.validatePassword( - password, - i18n - ); - if (passwordValidation) { - return passwordValidation; - } - // Check if the confirmation password is the same - if ( - !passwordConfirmation || - password.trim() !== passwordConfirmation.trim() - ) { - return i18n('passwordsDoNotMatch'); - } - } - return null; - }, - okPressed() { - const password = this.$('#password').val(); - if (this.type === 'set') { - window.setPassword(password.trim()); - } else if (this.type === 'remove') { - window.setPassword(null, password.trim()); - } - }, - okErrored() { - if (this.type === 'set') { - this.showError(i18n('setPasswordFail')); - } else if (this.type === 'remove') { - this.showError(i18n('removePasswordFail')); - } - }, - async ok() { - const error = await this.validate(); - if (error) { - this.showError(error); - return; - } + render() { - // Clear any errors - this.showError(null); + this.dialogView = new Whisper.ReactWrapperView({ + className: 'password-dialog-wrapper', + Component: window.Signal.Components.SessionPasswordModal, + props: { + onClose: this.close, + onOk: this.onOk, + ...this.props, + }, + }); - try { - this.okPressed(); - this.remove(); - if (this.resolve) { - this.resolve(); - } - } catch (e) { - this.okErrored(); - } + this.$el.append(this.dialogView.el); + return this; }, - cancel() { - this.remove(); - if (this.reject) { - this.reject(); - } - }, - onKeyup(event) { - this.updateUI(); - switch (event.key) { - case 'Enter': - this.ok(); - break; - case 'Escape': - case 'Esc': - this.cancel(); - break; - default: - return; - } - event.preventDefault(); - }, - focusCancel() { - this.$('.cancel').focus(); - }, - showError(message) { - if (_.isEmpty(message)) { - this.$('.error').text(''); - this.$('.error').hide(); - } else { - this.$('.error').text(`Error: ${message}`); - this.$('.error').show(); + + onOk(action) { + if (this.props.onSuccess) { + this.props.onSuccess(action); } }, - }); - const ChangePasswordDialogView = PasswordDialogView.extend({ - templateName: 'password-change-dialog', - disableOkButton() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - return _.isEmpty(oldPassword) || _.isEmpty(newPassword); + close() { + this.remove(); }, - async validate() { - const oldPassword = this.$('#old-password').val(); - // Validate the old password - if (!_.isEmpty(oldPassword)) { - const oldPasswordValidation = passwordUtil.validatePassword( - oldPassword, - i18n - ); - if (oldPasswordValidation) { - return oldPasswordValidation; - } - } else { - return i18n('typeInOldPassword'); - } - - const password = this.$('#new-password').val(); - const passwordConfirmation = this.$('#new-password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(oldPassword); - - return pairValidation || hashValidation; - }, - okPressed() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - window.setPassword(newPassword.trim(), oldPassword.trim()); - }, - okErrored() { - this.showError(i18n('changePasswordFail')); - }, }); - - Whisper.getPasswordDialogView = (type, resolve, reject) => { - // This is a differently styled dialog - if (type === 'change') { - return new ChangePasswordDialogView({ - title: i18n('changePassword'), - okTitle: i18n('change'), - resolve, - reject, - }); - } - - // Set and Remove is basically the same UI - const title = - type === 'remove' ? i18n('removePassword') : i18n('setPassword'); - const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); - return new PasswordDialogView({ - title, - okTitle, - type, - resolve, - reject, - }); - }; })(); + diff --git a/js/views/session_toast_view.js b/js/views/session_toast_view.js index 460862717..bbcb8b67d 100644 --- a/js/views/session_toast_view.js +++ b/js/views/session_toast_view.js @@ -12,6 +12,7 @@ title: options.title, id: options.id, description: options.description, + icon: options.icon, fadeToast: this.fadeToast.bind(this), closeToast: this.closeToast.bind(this), }; @@ -32,6 +33,7 @@ this.props.id = options.id; this.props.description = options.description || ''; this.props.type = options.type || ''; + this.props.icon = options.icon || ''; this.props.shouldFade = options.shouldFade !== false; this.toastView.update(this.props); diff --git a/main.js b/main.js index 490b3eccb..3cb82f9c4 100644 --- a/main.js +++ b/main.js @@ -53,6 +53,7 @@ const config = require('./app/config'); const userConfig = require('./app/user_config'); const passwordUtil = require('./app/password_util'); + const importMode = process.argv.some(arg => arg === '--import') || config.get('import'); diff --git a/preload.js b/preload.js index 528871baf..858c5442a 100644 --- a/preload.js +++ b/preload.js @@ -95,6 +95,8 @@ window.setPassword = (passPhrase, oldPhrase) => ipc.send('set-password', passPhrase, oldPhrase); }); +window.passwordUtil = require('./app/password_util'); + // We never do these in our code, so we'll prevent it everywhere window.open = () => null; // eslint-disable-next-line no-eval, no-multi-assign diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 68d37d285..7ed4e579c 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -75,6 +75,7 @@ $session-color-info: $session-shade-11; $session-color-success: #35d388; $session-color-error: #edd422; $session-color-warning: $session-shade-17; +$session-color-warning-alt: #ff9d00; $session-color-light-grey: #a0a0a0; @@ -227,6 +228,7 @@ $session_message-container-border-radius: 5px; justify-content: center; font-weight: 700; user-select: none; + white-space: nowrap; cursor: pointer; transition: $session-transition-duration; background-color: rgba(0, 0, 0, 0); @@ -280,9 +282,9 @@ $session_message-container-border-radius: 5px; } } &.warning { - background-color: $session-color-warning; + background-color: $session-color-warning-alt; &.disabled { - background-color: rgba($session-color-warning, 0.6); + background-color: rgba($session-color-warning-alt, 0.6); } } } @@ -363,7 +365,7 @@ $session_message-container-border-radius: 5px; background-color: $session-color-danger; } &.warning { - background-color: $session-color-warning; + background-color: $session-color-warning-alt; } } @@ -604,7 +606,7 @@ label { @include set-toast-theme($session-color-success); } &.warning { - @include set-toast-theme($session-color-warning); + @include set-toast-theme($session-color-warning-alt); } &.error { @include set-toast-theme($session-color-error); diff --git a/ts/components/session/SessionPasswordModal.tsx b/ts/components/session/SessionPasswordModal.tsx new file mode 100644 index 000000000..293287d92 --- /dev/null +++ b/ts/components/session/SessionPasswordModal.tsx @@ -0,0 +1,183 @@ +import React from 'react'; + +import { SessionModal } from './SessionModal'; +import { SessionButton, SessionButtonType, SessionButtonColor } from './SessionButton'; + +export enum PasswordAction { + Set = 'set', + Change = 'change', + Remove = 'remove' +} + +interface Props { + action: PasswordAction; + onOk: any; + onClose: any; +} + +interface State { + error: string | null; +} + +export class SessionPasswordModal extends React.Component { + constructor(props: any) { + super(props); + + this.state = { + error: null, + } + + this.showError = this.showError.bind(this); + + this.setPassword = this.setPassword.bind(this); + this.closeDialog = this.closeDialog.bind(this); + this.onEnter = this.onEnter.bind(this); + } + + public render() { + const { action, onOk } = this.props; + const placeholders = this.props.action === PasswordAction.Change + ? [window.i18n('typeInOldPassword'), window.i18n('enterPassword')] + : [window.i18n('enterPassword'), window.i18n('confirmPassword')] + + const confirmButtonColor = this.props.action === PasswordAction.Remove + ? SessionButtonColor.Danger + : SessionButtonColor.Primary + + return ( + null} + onClose={this.closeDialog} + > +
+ +
+ + { action !== PasswordAction.Remove && ( + + )} +
+ +
+ {this.showError()} + +
+ this.setPassword(onOk)} + /> + + +
+ + + ); + + } + + private async setPassword(onSuccess: any) { + const enteredPassword = String($('#password-modal-input').val()); + const enteredPasswordConfirm = String($('#password-modal-input-confirm').val()); + + // Check passwords enntered + if ((enteredPassword.length === 0) || + ((this.props.action === PasswordAction.Change) && + (enteredPasswordConfirm.length === 0))){ + this.setState({ + error: window.i18n('noGivenPassword'), + }); + + return; + } + + // Passwords match or remove password successful + const newPassword = this.props.action === PasswordAction.Remove + ? null + : enteredPasswordConfirm; + const oldPassword = this.props.action === PasswordAction.Set + ? null + : enteredPassword; + + + // Check if password match, when setting, changing or removing + const valid = this.props.action !== PasswordAction.Set + ? !! await this.validatePasswordHash(oldPassword) + : (enteredPassword === enteredPasswordConfirm); + + if (!valid){ + this.setState({ + error: window.i18n(`${this.props.action}PasswordInvalid`), + }); + + return; + } + + await window.setPassword(newPassword, oldPassword); + + + const toastParams = { + title: window.i18n(`${this.props.action}PasswordTitle`), + description: window.i18n(`${this.props.action}PasswordToastDescription`), + type: this.props.action !== PasswordAction.Remove ? 'success' : 'warning', + icon: this.props.action !== PasswordAction.Remove ? 'lock' : undefined, + } + + window.pushToast({ + id: "set-password-success-toast", + ...toastParams + }); + + onSuccess(this.props.action); + this.closeDialog(); + + } + + public async validatePasswordHash(password: string | null) { + // Check if the password matches the hash we have stored + const hash = await window.Signal.Data.getPasswordHash(); + if (hash && !window.passwordUtil.matchesHash(password, hash)) { + return false; + } + + return true; + } + + private showError() { + const message = this.state.error; + + return ( + <> + {message && ( + <> +
{message}
+
+ + )} + + ); + } + + private onEnter(event: any) { + if (event.key === 'Enter') { + //if ($('#server-url').is(':focus')) { + //this.showView('connecting'); + //} + } + } + + private closeDialog() { + window.removeEventListener('keyup', this.onEnter); + this.props.onClose && this.props.onClose(); + } + +} diff --git a/ts/components/session/SessionToast.tsx b/ts/components/session/SessionToast.tsx index f33b5f8f6..531f93451 100644 --- a/ts/components/session/SessionToast.tsx +++ b/ts/components/session/SessionToast.tsx @@ -19,6 +19,7 @@ interface Props { title: string; id?: string; type?: SessionToastType; + icon?: SessionIconType; description?: string; closeToast: any; } @@ -29,7 +30,7 @@ export class SessionToast extends React.PureComponent { } public render() { - const { title, description, type } = this.props; + const { title, description, type, icon } = this.props; const toastType = type ? type : SessionToastType.Info; const toastDesc = description ? description : ''; @@ -37,23 +38,27 @@ export class SessionToast extends React.PureComponent { ? SessionIconSize.Large : SessionIconSize.Medium; - let toastIcon; - switch (type) { - case SessionToastType.Info: - toastIcon = SessionIconType.Info; - break; - case SessionToastType.Success: - toastIcon = SessionIconType.Check; - break; - case SessionToastType.Error: - toastIcon = SessionIconType.Error; - break; - case SessionToastType.Warning: - toastIcon = SessionIconType.Warning; - break; - default: - toastIcon = SessionIconType.Info; - } + + // Set a custom icon or allow the theme to define the icon + let toastIcon = icon || undefined; + if (! toastIcon ){ + switch (type) { + case SessionToastType.Info: + toastIcon = SessionIconType.Info; + break; + case SessionToastType.Success: + toastIcon = SessionIconType.Check; + break; + case SessionToastType.Error: + toastIcon = SessionIconType.Error; + break; + case SessionToastType.Warning: + toastIcon = SessionIconType.Warning; + break; + default: + toastIcon = SessionIconType.Info; + } + } return (
diff --git a/ts/components/session/icon/Icons.tsx b/ts/components/session/icon/Icons.tsx index 1a033a875..8a7ac456f 100644 --- a/ts/components/session/icon/Icons.tsx +++ b/ts/components/session/icon/Icons.tsx @@ -19,6 +19,7 @@ export enum SessionIconType { Gear = 'gear', Globe = 'globe', Info = 'info', + Lock = 'lock', Microphone = 'microphone', Moon = 'moon', Reply = 'reply', @@ -137,6 +138,11 @@ export const icons = { 'M17.5,2.4c-1.82-1.5-4.21-2.1-6.57-1.64c-3.09,0.6-5.57,3.09-6.15,6.19c-0.4,2.1,0.04,4.21,1.22,5.95 C7.23,14.7,8,16.41,8.36,18.12c0.17,0.81,0.89,1.41,1.72,1.41h4.85c0.83,0,1.55-0.59,1.72-1.42c0.37-1.82,1.13-3.55,2.19-4.99 c1-1.36,1.53-2.96,1.53-4.65C20.37,6.11,19.32,3.9,17.5,2.4z M17.47,12.11c-1.21,1.64-2.07,3.6-2.55,5.72l-4.91-0.05 c-0.4-1.93-1.25-3.84-2.62-5.84c-0.93-1.36-1.27-3.02-0.95-4.67c0.46-2.42,2.39-4.37,4.81-4.83c0.41-0.08,0.82-0.12,1.23-0.12 c1.44,0,2.82,0.49,3.94,1.4c1.43,1.18,2.25,2.91,2.25,4.76C18.67,9.79,18.25,11.04,17.47,12.11z M15.94,20.27H9.61c-0.47,0-0.85,0.38-0.85,0.85s0.38,0.85,0.85,0.85h6.33c0.47,0,0.85-0.38,0.85-0.85 S16.41,20.27,15.94,20.27z M15.94,22.7H9.61c-0.47,0-0.85,0.38-0.85,0.85s0.38,0.85,0.85,0.85h6.33c0.47,0,0.85-0.38,0.85-0.85 S16.41,22.7,15.94,22.7z M12.5,3.28c-2.89,0-5.23,2.35-5.23,5.23c0,0.47,0.38,0.85,0.85,0.85s0.85-0.38,0.85-0.85 c0-1.95,1.59-3.53,3.54-3.53c0.47,0,0.85-0.38,0.85-0.85S12.97,3.28,12.5,3.28z', viewBox: '0 0 25 25', }, + [SessionIconType.Lock]: { + path: + 'M417.684,188.632H94.316c-9.923,0-17.965,8.042-17.965,17.965v239.532c0,7.952,5.234,14.965,12.863,17.222l161.684,47.906 c1.665,0.497,3.383,0.743,5.102,0.743c1.719,0,3.437-0.246,5.108-0.743l161.684-47.906c7.623-2.258,12.857-9.27,12.857-17.222 V206.596C435.649,196.674,427.607,188.632,417.684,188.632z M399.719,432.715L256,475.298l-143.719-42.583V224.561h287.439 V432.715z M256,0c-69.345,0-125.754,56.949-125.754,126.952v76.052h35.93v-76.052c0-50.188,40.295-91.022,89.825-91.022 s89.825,40.834,89.825,91.022v76.65h35.93v-76.65C381.754,56.949,325.339,0,256,0z M256,308.398c-9.923,0-17.965,8.042-17.965,17.965v47.906c0,9.923,8.042,17.965,17.965,17.965 c9.923,0,17.965-8.042,17.965-17.965v-47.906C273.965,316.44,265.923,308.398,256,308.398z', + viewBox: '0 0 512 512', + }, [SessionIconType.Microphone]: { path: 'M43.362728,18.444286 C46.0752408,18.444286 48.2861946,16.2442453 48.2861946,13.5451212 L48.2861946,6.8991648 C48.2861946,4.20004074 46.0752408,2 43.362728,2 C40.6502153,2 38.4392615,4.20004074 38.4392615,6.8991648 L38.4392615,13.5451212 C38.4392615,16.249338 40.6502153,18.444286 43.362728,18.444286 Z M51.0908304,12.9238134 C51.4388509,12.9238134 51.7203381,13.2039112 51.7203381,13.5502139 C51.7203381,17.9248319 48.3066664,21.5202689 43.9871178,21.8411082 L43.9871178,21.8411082 L43.9871178,25.747199 L47.2574869,25.747199 C47.6055074,25.747199 47.8869946,26.0272968 47.8869946,26.3735995 C47.8869946,26.7199022 47.6055074,27 47.2574869,27 L47.2574869,27 L39.4628512,27 C39.1148307,27 38.8333435,26.7199022 38.8333435,26.3735995 C38.8333435,26.0272968 39.1148307,25.747199 39.4628512,25.747199 L39.4628512,25.747199 L42.7332204,25.747199 L42.7332204,21.8411082 C38.4136717,21.5253616 35,17.9248319 35,13.5502139 C35,13.2039112 35.2814872,12.9238134 35.6295077,12.9238134 C35.9775282,12.9238134 36.2538974,13.2039112 36.2436615,13.5502139 C36.2436615,17.4512121 39.4321435,20.623956 43.3524921,20.623956 C47.2728408,20.623956 50.4613228,17.4512121 50.4613228,13.5502139 C50.4613228,13.2039112 50.7428099,12.9238134 51.0908304,12.9238134 Z M43.362728,3.24770829 C45.3843177,3.24770829 47.0322972,4.88755347 47.0322972,6.8991648 L47.0322972,13.5451212 C47.0322972,15.5567325 45.3843177,17.1965777 43.362728,17.1965777 C41.3411383,17.1965777 39.6931589,15.5567325 39.6931589,13.5451212 L39.6931589,6.8991648 C39.6931589,4.88755347 41.3411383,3.24770829 43.362728,3.24770829 lZ', diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 27b9e35f2..70c71e711 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -94,7 +94,7 @@ export class SessionSettingListItem extends React.Component { step={6} min={12} max={96} - defaultValue={value} + defaultValue={currentSliderValue} onAfterChange={value => this.handleSlider(value)} />
diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 1747ed392..e29917429 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -24,20 +24,31 @@ export interface SettingsViewProps { category: SessionSettingCategory; } -export class SettingsView extends React.Component { +interface State { + hasPassword: boolean | null; + changedPassword: boolean | null; + removedPassword: +} + +export class SettingsView extends React.Component { public settingsViewRef: React.RefObject; public constructor(props: any) { super(props); + + this.state = { + hasPassword: null, + } + this.settingsViewRef = React.createRef(); + this.onPasswordUpdated = this.onPasswordUpdated.bind(this); + + this.hasPassword(); } public renderSettingInCategory() { const { Settings } = window.Signal.Types; - //const hasPassword = window.userConfig.get('dbHasPassword'); - //console.log(`User has password: ${hasPassword}`); - // Grab initial values from database on startup // ID corresponds to instalGetter parameters in preload.js // They are NOT arbitrary; add with caution @@ -135,13 +146,15 @@ export class SettingsView extends React.Component { type: SessionSettingType.Slider, category: SessionSettingCategory.Privacy, setFn: undefined, - content: {}, + content: { + defaultValue: 24 + }, }, { id: 'set-password', title: window.i18n('setAccountPasswordTitle'), description: window.i18n('setAccountPasswordDescription'), - hidden: false, + hidden: this.state.hasPassword, type: SessionSettingType.Button, category: SessionSettingCategory.Privacy, setFn: undefined, @@ -149,25 +162,33 @@ export class SettingsView extends React.Component { buttonText: window.i18n('setPassword'), buttonColor: SessionButtonColor.Primary, }, + onClick: () => window.showPasswordDialog({ + action: 'set', + onSuccess: this.onPasswordUpdated, + }), }, { id: 'change-password', title: window.i18n('changeAccountPasswordTitle'), description: window.i18n('changeAccountPasswordDescription'), - hidden: false, + hidden: !this.state.hasPassword, type: SessionSettingType.Button, category: SessionSettingCategory.Privacy, setFn: undefined, content: { - buttonText: window.i18n('changePassword'), + buttonText: window.i18n('changePassword'), buttonColor: SessionButtonColor.Primary, }, + onClick: () => window.showPasswordDialog({ + action: 'change', + onSuccess: this.onPasswordUpdated, + }), }, { id: 'remove-password', title: window.i18n('removeAccountPasswordTitle'), description: window.i18n('removeAccountPasswordDescription'), - hidden: false, + hidden: !this.state.hasPassword, type: SessionSettingType.Button, category: SessionSettingCategory.Privacy, setFn: undefined, @@ -175,22 +196,30 @@ export class SettingsView extends React.Component { buttonText: window.i18n('removePassword'), buttonColor: SessionButtonColor.Danger, }, + onClick: () => window.showPasswordDialog({ + action: 'remove', + onSuccess: this.onPasswordUpdated, + }), }, ]; return ( <> - {localSettings.map(setting => { + {(this.state.hasPassword !== null) && localSettings.map(setting => { const { category } = this.props; const shouldRenderSettings = setting.category === category; const description = setting.description || ''; + const comparisonValue = setting.comparisonValue || null; + const value = window.getSettingValue(setting.id, comparisonValue) || setting.content.defaultValue; const sliderFn = setting.type === SessionSettingType.Slider ? (value: any) => window.setSettingValue(setting.id, value) : () => null; + const onClickFn = setting.onClick || (() => this.updateSetting(setting)); + return (
{shouldRenderSettings && @@ -199,10 +228,8 @@ export class SettingsView extends React.Component { title={setting.title} description={description} type={setting.type} - value={window.getSettingValue(setting.id, comparisonValue)} - onClick={() => { - this.updateSetting(setting); - }} + value={value} + onClick={onClickFn} onSliderChange={sliderFn} content={setting.content || undefined} /> @@ -214,6 +241,16 @@ export class SettingsView extends React.Component { ); } + public hasPassword() { + const hashPromise = window.Signal.Data.getPasswordHash(); + + hashPromise.then((hash: any) => { + this.setState({ + hasPassword: !!hash, + }); + }); + } + public render() { const { category } = this.props; @@ -246,4 +283,19 @@ export class SettingsView extends React.Component { const selectedValue = $(`#${settingID} .session-radio input:checked`).val(); window.setSettingValue(settingID, selectedValue); } + + public onPasswordUpdated(action: string) { + + if (action === 'set'){ + this.setState({ + hasPassword: true, + }); + } + + if (action === 'remove'){ + this.setState({ + hasPassword: false, + }); + } + } } diff --git a/ts/global.d.ts b/ts/global.d.ts index 37341f671..0e8f0f2fb 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -4,15 +4,19 @@ interface Window { getAccountManager: any; mnemonic: any; clipboard: any; + passwordUtil: any; userConfig: any; + dcodeIO: any; libsignal: any; libloki: any; displayNameRegex: any; + Signal: any; Whisper: any; ConversationController: any; + setPassword: any; textsecure: any; Session: any; @@ -22,14 +26,19 @@ interface Window { generateID: any; storage: any; pushToast: any; + confirmationDialog: any; showSeedDialog: any; + showPasswordDialog: any; + deleteAccount: any; + toggleTheme: any; toggleMenuBar: any; toggleSpellCheck: any; toggleLinkPreview: any; toggleMediaPermissions: any; + getSettingValue: any; setSettingValue: any; }