implement wallet password change.
implement wallet delete (though actual wallet file delete is left out still)
This commit is contained in:
@@ -6,7 +6,7 @@ import QtQml 2.6
|
|||||||
Item {
|
Item {
|
||||||
id: rootItem
|
id: rootItem
|
||||||
|
|
||||||
property string title: Daemon.currentWallet.name
|
property string title: Daemon.currentWallet ? Daemon.currentWallet.name : ''
|
||||||
|
|
||||||
property QtObject menu: Menu {
|
property QtObject menu: Menu {
|
||||||
id: menu
|
id: menu
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ Pane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changePassword() {
|
function changePassword() {
|
||||||
// TODO: show set password dialog
|
// trigger dialog via wallet (auth then signal)
|
||||||
|
Daemon.currentWallet.start_change_password()
|
||||||
}
|
}
|
||||||
|
|
||||||
property QtObject menu: Menu {
|
property QtObject menu: Menu {
|
||||||
@@ -58,7 +59,7 @@ Pane {
|
|||||||
id: changePasswordComp
|
id: changePasswordComp
|
||||||
MenuItem {
|
MenuItem {
|
||||||
icon.color: 'transparent'
|
icon.color: 'transparent'
|
||||||
enabled: false
|
enabled: Daemon.currentWallet // != null
|
||||||
action: Action {
|
action: Action {
|
||||||
text: qsTr('Change Password');
|
text: qsTr('Change Password');
|
||||||
onTriggered: rootItem.changePassword()
|
onTriggered: rootItem.changePassword()
|
||||||
@@ -308,6 +309,23 @@ Pane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Daemon.currentWallet
|
||||||
|
function onRequestNewPassword() { // new wallet password
|
||||||
|
var dialog = app.passwordDialog.createObject(app,
|
||||||
|
{
|
||||||
|
'confirmPassword': true,
|
||||||
|
'title': qsTr('Enter new password'),
|
||||||
|
'infotext': qsTr('If you forget your password, you\'ll need to\
|
||||||
|
restore from seed. Please make sure you have your seed stored safely')
|
||||||
|
} )
|
||||||
|
dialog.accepted.connect(function() {
|
||||||
|
Daemon.currentWallet.set_password(dialog.password)
|
||||||
|
})
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: share
|
id: share
|
||||||
GenericShareDialog {
|
GenericShareDialog {
|
||||||
|
|||||||
115
electrum/gui/qml/components/controls/PasswordDialog.qml
Normal file
115
electrum/gui/qml/components/controls/PasswordDialog.qml
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import QtQuick 2.6
|
||||||
|
import QtQuick.Layouts 1.0
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Controls.Material 2.0
|
||||||
|
|
||||||
|
import org.electrum 1.0
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: passworddialog
|
||||||
|
|
||||||
|
title: qsTr("Enter Password")
|
||||||
|
|
||||||
|
property bool confirmPassword: false
|
||||||
|
property string password
|
||||||
|
property string infotext
|
||||||
|
|
||||||
|
parent: Overlay.overlay
|
||||||
|
modal: true
|
||||||
|
x: (parent.width - width) / 2
|
||||||
|
y: (parent.height - height) / 2
|
||||||
|
Overlay.modal: Rectangle {
|
||||||
|
color: "#aa000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
header: GridLayout {
|
||||||
|
columns: 2
|
||||||
|
rowSpacing: 0
|
||||||
|
|
||||||
|
Image {
|
||||||
|
source: "../../../icons/lock.png"
|
||||||
|
Layout.preferredWidth: constants.iconSizeXLarge
|
||||||
|
Layout.preferredHeight: constants.iconSizeXLarge
|
||||||
|
Layout.leftMargin: constants.paddingMedium
|
||||||
|
Layout.topMargin: constants.paddingMedium
|
||||||
|
Layout.bottomMargin: constants.paddingMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: title
|
||||||
|
elide: Label.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
topPadding: constants.paddingXLarge
|
||||||
|
bottomPadding: constants.paddingXLarge
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: constants.fontSizeMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: constants.paddingXXSmall
|
||||||
|
Layout.rightMargin: constants.paddingXXSmall
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(0,0,0,0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
InfoTextArea {
|
||||||
|
visible: infotext
|
||||||
|
text: infotext
|
||||||
|
Layout.preferredWidth: password_layout.width
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: password_layout
|
||||||
|
columns: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: constants.paddingXXLarge
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Password')
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: pw_1
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Password (again)')
|
||||||
|
visible: confirmPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: pw_2
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
visible: confirmPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: constants.paddingXXLarge
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Ok")
|
||||||
|
enabled: confirmPassword ? pw_1.text == pw_2.text : true
|
||||||
|
onClicked: {
|
||||||
|
password = pw_1.text
|
||||||
|
passworddialog.accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onClicked: {
|
||||||
|
passworddialog.reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -171,6 +171,14 @@ ApplicationWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property alias passwordDialog: _passwordDialog
|
||||||
|
Component {
|
||||||
|
id: _passwordDialog
|
||||||
|
PasswordDialog {
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NotificationPopup {
|
NotificationPopup {
|
||||||
id: notificationPopup
|
id: notificationPopup
|
||||||
}
|
}
|
||||||
@@ -225,14 +233,23 @@ ApplicationWindow
|
|||||||
Connections {
|
Connections {
|
||||||
target: Daemon.currentWallet
|
target: Daemon.currentWallet
|
||||||
function onAuthRequired() {
|
function onAuthRequired() {
|
||||||
var dialog = app.messageDialog.createObject(app, {'text': 'Auth placeholder', 'yesno': true})
|
if (Daemon.currentWallet.verify_password('')) {
|
||||||
dialog.yesClicked.connect(function() {
|
// wallet has no password
|
||||||
Daemon.currentWallet.authProceed()
|
Daemon.currentWallet.authProceed()
|
||||||
})
|
} else {
|
||||||
dialog.noClicked.connect(function() {
|
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')})
|
||||||
Daemon.currentWallet.authCancel()
|
dialog.accepted.connect(function() {
|
||||||
})
|
if (Daemon.currentWallet.verify_password(dialog.password)) {
|
||||||
dialog.open()
|
Daemon.currentWallet.authProceed()
|
||||||
|
} else {
|
||||||
|
Daemon.currentWallet.authCancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.rejected.connect(function() {
|
||||||
|
Daemon.currentWallet.authCancel()
|
||||||
|
})
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class QEAppController(QObject):
|
|||||||
|
|
||||||
def on_wallet_loaded(self):
|
def on_wallet_loaded(self):
|
||||||
qewallet = self._qedaemon.currentWallet
|
qewallet = self._qedaemon.currentWallet
|
||||||
|
if not qewallet:
|
||||||
|
return
|
||||||
# attach to the wallet user notification events
|
# attach to the wallet user notification events
|
||||||
# connect only once
|
# connect only once
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -152,7 +152,12 @@ class QEDaemon(AuthMixin, QObject):
|
|||||||
path = wallet.wallet.storage.path
|
path = wallet.wallet.storage.path
|
||||||
self._logger.debug('Ok to delete wallet with path %s' % path)
|
self._logger.debug('Ok to delete wallet with path %s' % path)
|
||||||
# TODO checks, e.g. existing LN channels, unpaid requests, etc
|
# TODO checks, e.g. existing LN channels, unpaid requests, etc
|
||||||
|
self._logger.debug('Not deleting yet, just unloading for now')
|
||||||
|
# TODO actually delete
|
||||||
|
# TODO walletLoaded signal is confusing
|
||||||
self.daemon.stop_wallet(path)
|
self.daemon.stop_wallet(path)
|
||||||
|
self._current_wallet = None
|
||||||
|
self.walletLoaded.emit()
|
||||||
|
|
||||||
@pyqtProperty('QString')
|
@pyqtProperty('QString')
|
||||||
def path(self):
|
def path(self):
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import threading
|
|||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QTimer
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QTimer
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import register_callback, Satoshis, format_time, parse_max_spend
|
from electrum.util import register_callback, Satoshis, format_time, parse_max_spend, InvalidPassword
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
from electrum.wallet import Wallet, Abstract_Wallet
|
from electrum.wallet import Wallet, Abstract_Wallet
|
||||||
|
from electrum.storage import StorageEncryptionVersion
|
||||||
from electrum import bitcoin
|
from electrum import bitcoin
|
||||||
from electrum.transaction import PartialTxOutput
|
from electrum.transaction import PartialTxOutput
|
||||||
from electrum.invoices import (Invoice, InvoiceError,
|
from electrum.invoices import (Invoice, InvoiceError,
|
||||||
@@ -331,7 +332,7 @@ class QEWallet(AuthMixin, QObject):
|
|||||||
self.wallet.init_lightning(password=None) # TODO pass password if needed
|
self.wallet.init_lightning(password=None) # TODO pass password if needed
|
||||||
self.isLightningChanged.emit()
|
self.isLightningChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot('QString', int, int, bool)
|
@pyqtSlot(str, int, int, bool)
|
||||||
def send_onchain(self, address, amount, fee=None, rbf=False):
|
def send_onchain(self, address, amount, fee=None, rbf=False):
|
||||||
self._logger.info('send_onchain: %s %d' % (address,amount))
|
self._logger.info('send_onchain: %s %d' % (address,amount))
|
||||||
coins = self.wallet.get_spendable_coins(None)
|
coins = self.wallet.get_spendable_coins(None)
|
||||||
@@ -437,9 +438,9 @@ class QEWallet(AuthMixin, QObject):
|
|||||||
|
|
||||||
return req_key, addr
|
return req_key, addr
|
||||||
|
|
||||||
@pyqtSlot(QEAmount, 'QString', int)
|
@pyqtSlot(QEAmount, str, int)
|
||||||
@pyqtSlot(QEAmount, 'QString', int, bool)
|
@pyqtSlot(QEAmount, str, int, bool)
|
||||||
@pyqtSlot(QEAmount, 'QString', int, bool, bool)
|
@pyqtSlot(QEAmount, str, int, bool, bool)
|
||||||
def create_request(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False):
|
def create_request(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False):
|
||||||
try:
|
try:
|
||||||
if is_lightning:
|
if is_lightning:
|
||||||
@@ -463,29 +464,52 @@ class QEWallet(AuthMixin, QObject):
|
|||||||
self._requestModel.add_invoice(self.wallet.get_request(key))
|
self._requestModel.add_invoice(self.wallet.get_request(key))
|
||||||
self.requestCreateSuccess.emit()
|
self.requestCreateSuccess.emit()
|
||||||
|
|
||||||
@pyqtSlot('QString')
|
@pyqtSlot(str)
|
||||||
def delete_request(self, key: str):
|
def delete_request(self, key: str):
|
||||||
self._logger.debug('delete req %s' % key)
|
self._logger.debug('delete req %s' % key)
|
||||||
self.wallet.delete_request(key)
|
self.wallet.delete_request(key)
|
||||||
self._requestModel.delete_invoice(key)
|
self._requestModel.delete_invoice(key)
|
||||||
|
|
||||||
@pyqtSlot('QString', result='QVariant')
|
@pyqtSlot(str, result='QVariant')
|
||||||
def get_request(self, key: str):
|
def get_request(self, key: str):
|
||||||
return self._requestModel.get_model_invoice(key)
|
return self._requestModel.get_model_invoice(key)
|
||||||
|
|
||||||
@pyqtSlot('QString')
|
@pyqtSlot(str)
|
||||||
def delete_invoice(self, key: str):
|
def delete_invoice(self, key: str):
|
||||||
self._logger.debug('delete inv %s' % key)
|
self._logger.debug('delete inv %s' % key)
|
||||||
self.wallet.delete_invoice(key)
|
self.wallet.delete_invoice(key)
|
||||||
self._invoiceModel.delete_invoice(key)
|
self._invoiceModel.delete_invoice(key)
|
||||||
|
|
||||||
@pyqtSlot('QString', result='QVariant')
|
@pyqtSlot(str, result='QVariant')
|
||||||
def get_invoice(self, key: str):
|
def get_invoice(self, key: str):
|
||||||
return self._invoiceModel.get_model_invoice(key)
|
return self._invoiceModel.get_model_invoice(key)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str, result=bool)
|
||||||
|
def verify_password(self, password):
|
||||||
|
try:
|
||||||
|
self.wallet.storage.check_password(password)
|
||||||
|
return True
|
||||||
|
except InvalidPassword as e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
requestNewPassword = pyqtSignal()
|
||||||
|
@pyqtSlot()
|
||||||
@auth_protect
|
@auth_protect
|
||||||
|
def start_change_password(self):
|
||||||
|
self.requestNewPassword.emit()
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
storage = self.wallet.storage
|
storage = self.wallet.storage
|
||||||
|
|
||||||
|
# HW wallet not supported yet
|
||||||
|
if storage.is_encrypted_with_hw_device():
|
||||||
|
return
|
||||||
|
|
||||||
self._logger.debug('Ok to set password for wallet with path %s' % storage.path)
|
self._logger.debug('Ok to set password for wallet with path %s' % storage.path)
|
||||||
# TODO
|
if password:
|
||||||
|
enc_version = StorageEncryptionVersion.USER_PASSWORD
|
||||||
|
else:
|
||||||
|
enc_version = StorageEncryptionVersion.PLAINTEXT
|
||||||
|
storage.set_password(password, enc_version=enc_version)
|
||||||
|
self.wallet.save_db()
|
||||||
|
|||||||
Reference in New Issue
Block a user