Added password dialog view.

pull/77/head
Mikunj 6 years ago
parent 26ba553e6a
commit 0b87f13699

@ -1698,11 +1698,56 @@
"description": "Button action that the user can click to set a password"
},
"changePassword": {
"message": "Set Password",
"message": "Change Password",
"description": "Button action that the user can click to change a password"
},
"removePassword": {
"message": "Set Password",
"message": "Remove Password",
"description": "Button action that the user can click to remove a password"
},
"typeInOldPassword": {
"message": "Please type in your old password"
},
"invalidPassword": {
"message": "Invalid password"
},
"passwordsDoNotMatch": {
"message": "Passwords do not match"
},
"setPasswordFail": {
"message": "Failed to set password"
},
"removePasswordFail": {
"message": "Failed to remove password"
},
"changePasswordFail": {
"message": "Failed to change password"
},
"setPasswordSuccess": {
"message": "Password set"
},
"removePasswordSuccess": {
"message": "Password removed"
},
"changePasswordSuccess": {
"message": "Password changed"
},
"passwordLengthError": {
"message": "Password must be atleast 6 characters long",
"description": "Error string shown to the user when password doesn't meet length criteria"
},
"passwordTypeError": {
"message": "Password must be a string",
"description": "Error string shown to the user when password is not a string"
},
"change": {
"message": "Change"
},
"set": {
"message": "Set"
},
"remove": {
"message": "Remove"
}
}

@ -3,13 +3,13 @@ const { sha512 } = require('js-sha512');
const generateHash = (phrase) => phrase && sha512(phrase.trim());
const matchesHash = (phrase, hash) => phrase && sha512(phrase.trim()) === hash.trim();
const validatePassword = (phrase) => {
const validatePassword = (phrase, i18n) => {
if (typeof phrase !== 'string') {
return 'Password must be a string'
return i18n ? i18n('passwordTypeError') : 'Password must be a string'
}
if (phrase && phrase.trim().length < 6) {
return 'Password must be atleast 6 characters long';
return i18n ? i18n('passwordLengthError') : 'Password must be atleast 6 characters long';
}
// An empty password is still valid :P

@ -165,6 +165,35 @@
<span class='time'>0:00</span>
<button class='close'><span class='icon'></span></button>
</script>
<script type='text/x-tmpl-mustache' id='password-dialog'>
<div class="content">
{{ #title }}
<h4>{{ title }}</h4>
{{ /title }}
<input type='password' id='password' placeholder='Password' autofocus>
<input type='password' id='password-confirmation' placeholder='Type in your password again' autofocus>
<div class='error'></div>
<div class='buttons'>
<button class='cancel' tabindex='2'>{{ cancel }}</button>
<button class='ok' tabindex='1'>{{ ok }}</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='password-change-dialog'>
<div class="content">
{{ #title }}
<h4>{{ title }}</h4>
{{ /title }}
<input type='password' id='old-password' placeholder='Old password' autofocus>
<input type='password' id='new-password' placeholder='New password' autofocus>
<input type='password' id='new-password-confirmation' placeholder='Type in your new password again' autofocus>
<div class='error'></div>
<div class='buttons'>
<button class='cancel' tabindex='2'>{{ cancel }}</button>
<button class='ok' tabindex='1'>{{ ok }}</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='nickname-dialog'>
<div class="content">
{{ #title }}
@ -706,6 +735,7 @@
<script type='text/javascript' src='js/views/network_status_view.js'></script>
<script type='text/javascript' src='js/views/confirmation_dialog_view.js'></script>
<script type='text/javascript' src='js/views/nickname_dialog_view.js'></script>
<script type='text/javascript' src='js/views/password_dialog_view.js'></script>
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/install_view.js'></script>
<script type='text/javascript' src='js/views/banner_view.js'></script>

@ -602,6 +602,12 @@
}
});
Whisper.events.on('showPasswordDialog', options => {
if (appView) {
appView.showPasswordDialog(options);
}
});
Whisper.events.on('calculatingPoW', ({ pubKey, timestamp }) => {
try {
const conversation = ConversationController.get(pubKey);

@ -189,5 +189,9 @@
});
this.el.append(dialog.el);
},
showPasswordDialog({ type, resolve, reject }) {
const dialog = Whisper.getPasswordDialogView(type, resolve, reject);
this.el.append(dialog.el);
},
});
})();

@ -334,29 +334,37 @@
const ourNumber = textsecure.storage.user.getNumber();
clipboard.writeText(ourNumber);
const toast = new Whisper.MessageToastView({
message: i18n('copiedPublicKey'),
});
toast.$el.appendTo(this.$('.gutter'));
toast.render();
this.showToastMessageInGutter(i18n('copiedPublicKey'));
}),
this._mainHeaderItem('editDisplayName', () => {
window.Whisper.events.trigger('onEditProfile');
}),
...this.passwordHeaderItems || [],
];
},
async onPasswordUpdated() {
const hasPassword = await Signal.Data.getPasswordHash();
const items = this.getMainHeaderItems();
const showPasswordDialog = (type, resolve) => Whisper.events.trigger('showPasswordDialog', {
type,
resolve,
});
const passwordItem = (textKey, type) => this._mainHeaderItem(
textKey,
() => showPasswordDialog(type, () => {
this.showToastMessageInGutter(i18n(`${textKey}Success`));
})
);
if (hasPassword) {
items.push(
this._mainHeaderItem('changePassword'),
this._mainHeaderItem('removePassword')
passwordItem('changePassword', 'change'),
passwordItem('removePassword', 'remove')
);
} else {
items.push(
this._mainHeaderItem('setPassword')
passwordItem('setPassword', 'set')
);
}
@ -369,6 +377,13 @@
onClick,
};
},
showToastMessageInGutter(message) {
const toast = new Whisper.MessageToastView({
message,
});
toast.$el.appendTo(this.$('.gutter'));
toast.render();
},
});
Whisper.ExpiredAlertBanner = Whisper.View.extend({

@ -7,7 +7,7 @@
window.Whisper = window.Whisper || {};
Whisper.NicknameDialogView = Whisper.View.extend({
className: 'nickname-dialog modal',
className: 'loki-dialog nickname-dialog modal',
templateName: 'nickname-dialog',
initialize(options) {
this.message = options.message;

@ -0,0 +1,214 @@
/* 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,
});
};
})();

@ -193,12 +193,14 @@
const input = this.trim(this.$passwordInput.val());
const confirmationInput = this.trim(this.$passwordConfirmationInput.val());
const error = passwordUtil.validatePassword(input);
if (error)
const error = passwordUtil.validatePassword(input, i18n);
if (error) {
return error;
}
if (input !== confirmationInput)
if (input !== confirmationInput) {
return 'Password don\'t match';
}
return null;
},
@ -207,13 +209,30 @@
if (passwordValidation) {
this.$passwordInput.addClass('error-input');
this.$passwordConfirmationInput.addClass('error-input');
this.$passwordInput.removeClass('match-input');
this.$passwordConfirmationInput.removeClass('match-input');
this.$passwordInputError.text(passwordValidation);
this.$passwordInputError.show();
} else {
this.$passwordInput.removeClass('error-input');
this.$passwordConfirmationInput.removeClass('error-input');
this.$passwordInputError.text('');
this.$passwordInputError.hide();
// Show green box around inputs that match
const input = this.trim(this.$passwordInput.val());
const confirmationInput = this.trim(this.$passwordConfirmationInput.val());
if (input && input === confirmationInput) {
this.$passwordInput.addClass('match-input');
this.$passwordConfirmationInput.addClass('match-input');
} else {
this.$passwordInput.removeClass('match-input');
this.$passwordConfirmationInput.removeClass('match-input');
}
}
},
trim(value) {

@ -24,6 +24,8 @@ window.getEnvironment = () => config.environment;
window.getVersion = () => config.version;
window.getAppInstance = () => config.appInstance;
window.passwordUtil = require('./app/password_util');
window.onLogin = (passPhrase) => new Promise((resolve, reject) => {
ipcRenderer.once('password-window-login-response', (event, error) => {
if (error) {

@ -353,7 +353,7 @@
}
}
.nickname-dialog {
.loki-dialog {
display: flex;
align-items: center;
justify-content: center;
@ -366,55 +366,62 @@
border-radius: $border-radius;
overflow: auto;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.3);
}
.buttons {
button {
float: right;
margin-left: 10px;
background-color: $color-loki-green;
border-radius: 100px;
padding: 5px 15px;
border: 1px solid $color-loki-green;
color: white;
outline: none;
button {
float: right;
margin-left: 10px;
background-color: $color-loki-green;
border-radius: 100px;
padding: 5px 15px;
border: 1px solid $color-loki-green;
color: white;
outline: none;
&:hover, &:disabled {
background-color: $color-loki-green-dark;
border-color: $color-loki-green-dark;
}
&:hover {
background-color: $color-loki-green-dark;
border-color: $color-loki-green-dark;
}
}
&:disabled {
cursor: not-allowed;
}
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 0;
outline: none;
border-radius: 4px;
background-color: $color-loki-light-gray;
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 0;
&:focus {
outline: none;
border-radius: 4px;
background-color: $color-loki-light-gray;
}
}
h4 {
margin-top: 8px;
margin-bottom: 16px;
white-space: -moz-pre-wrap; /* Mozilla */
white-space: -hp-pre-wrap; /* HP printers */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: pre-wrap; /* CSS 2.1 */
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
word-wrap: break-word; /* IE */
word-break: break-all;
}
h4 {
margin-top: 8px;
margin-bottom: 16px;
white-space: -moz-pre-wrap; /* Mozilla */
white-space: -hp-pre-wrap; /* HP printers */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: pre-wrap; /* CSS 2.1 */
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
word-wrap: break-word; /* IE */
word-break: break-all;
}
}
.message {
font-style: italic;
color: $grey;
font-size: 12px;
margin-bottom: 16px;
}
.nickname-dialog {
.message {
font-style: italic;
color: $grey;
font-size: 12px;
margin-bottom: 16px;
}
}

@ -993,6 +993,14 @@ textarea {
outline: none;
}
}
.match-input {
border: 3px solid $color-loki-green;
&:focus {
outline: none;
}
}
}
@media (min-height: 750px) and (min-width: 700px) {

@ -93,34 +93,34 @@ body.dark-theme {
}
}
.nickname-dialog {
.loki-dialog {
.content {
background: $color-black;
color: $color-dark-05;
}
.buttons {
button {
background-color: $color-dark-85;
border-radius: $border-radius;
border: 1px solid $color-dark-60;
color: $color-dark-05;
&:hover {
background-color: $color-dark-70;
border-color: $color-dark-55;
}
}
}
button {
background-color: $color-dark-85;
border-radius: $border-radius;
border: 1px solid $color-dark-60;
color: $color-dark-05;
input {
color: $color-dark-05;
&:hover {
background-color: $color-dark-70;
border-color: $color-dark-55;
}
}
.message {
color: $color-light-35;
}
input {
color: $color-dark-05;
background-color: $color-dark-70;
border-color: $color-dark-55;
}
}
.nickname-dialog {
.message {
color: $color-light-35;
}
}

@ -403,6 +403,7 @@
<script type='text/javascript' src='../js/views/network_status_view.js'></script>
<script type='text/javascript' src='../js/views/confirmation_dialog_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/nickname_dialog_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/password_dialog_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/identicon_svg_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/last_seen_indicator_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/scroll_down_button_view.js' data-cover></script>

Loading…
Cancel
Save