|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
/* global Whisper */
|
|
|
|
|
/* global Whisper, i18n, _, Signal, passwordUtil */
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line func-names
|
|
|
|
|
(function() {
|
|
|
|
@ -6,28 +6,223 @@
|
|
|
|
|
|
|
|
|
|
window.Whisper = window.Whisper || {};
|
|
|
|
|
|
|
|
|
|
Whisper.PasswordDialogView = Whisper.View.extend({
|
|
|
|
|
const PasswordDialogView = Whisper.View.extend({
|
|
|
|
|
className: 'loki-dialog password-dialog modal',
|
|
|
|
|
initialize() {
|
|
|
|
|
this.close = this.close.bind(this);
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
this.dialogView = new Whisper.ReactWrapperView({
|
|
|
|
|
className: 'password-dialog-wrapper',
|
|
|
|
|
Component: window.Signal.Components.SessionPasswordChangeModal,
|
|
|
|
|
props: {
|
|
|
|
|
onClose: this.close,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const pairValidation = this.validatePasswordPair(
|
|
|
|
|
password,
|
|
|
|
|
passwordConfirmation
|
|
|
|
|
);
|
|
|
|
|
const hashValidation = await this.validatePasswordHash(password);
|
|
|
|
|
|
|
|
|
|
this.$el.append(this.dialogView.el);
|
|
|
|
|
return this;
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
close() {
|
|
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|