Skip to content

Commit 78f0122

Browse files
committed
qml: rework auth
now handles keystore-only password and defines a new set of auth methods
1 parent 56fa832 commit 78f0122

File tree

7 files changed

+203
-61
lines changed

7 files changed

+203
-61
lines changed

electrum/gui/qml/auth.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ class AuthMixin:
2525
authRequired = pyqtSignal([str, str], arguments=['method', 'authMessage'])
2626

2727
@pyqtSlot()
28-
def authProceed(self):
28+
@pyqtSlot(str)
29+
def authProceed(self, password=None):
2930
self._auth_logger.debug('Proceeding with authed fn()')
3031
try:
3132
self._auth_logger.debug(str(getattr(self, '__auth_fcall')))
3233
(func,args,kwargs,reject) = getattr(self, '__auth_fcall')
33-
r = func(self, *args, **kwargs)
34+
if password:
35+
r = func(self, *args, **dict(kwargs, password=password))
36+
else:
37+
r = func(self, *args, **kwargs)
3438
return r
3539
except Exception as e:
3640
self._auth_logger.error(f'Error executing wrapped fn(): {repr(e)}')

electrum/gui/qml/components/main.qml

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -550,51 +550,104 @@ ApplicationWindow
550550
}
551551
}
552552

553+
// handle auth_protect decorator events
554+
//
555+
// 'wallet_password': User must supply a password
556+
// that matches the storage password (if set)
557+
// or the keystore password. This forces password
558+
// verification in all cases, even for wallets using
559+
// keystore-only passwords (unless the storage and
560+
// keystore are both unencrypted).
561+
// It's primary use is password knowledge verification
562+
// before presenting a secret (e.g. seed) or doing
563+
// something irreversible (e.g. delete wallet)
564+
//
565+
// 'keystore': User must supply a password
566+
// that matches the keystore password (if set).
567+
//
568+
// 'keystore_else_pin': User must supply a password
569+
// that matches the keystore password (if set), unless
570+
// the keystore is 'unlocked' which means the wallet password
571+
// has been given when opening the wallet, and is the same as
572+
// the keystore password (should always be the case). In that
573+
// case a PIN is asked.
574+
// This is mainly used when signing a transaction.
575+
//
576+
// 'pin': User must supply the configured PIN code
577+
//
578+
553579
function handleAuthRequired(qtobject, method, authMessage) {
554580
console.log('auth using method ' + method)
555-
if (method == 'wallet') {
556-
if (Daemon.currentWallet.verifyPassword('')) {
581+
if (method == 'wallet_password') {
582+
if (!Daemon.currentWallet.isEncrypted
583+
&& Daemon.currentWallet.verifyKeystorePassword('')) {
557584
// wallet has no password
558585
qtobject.authProceed()
559586
} else {
560-
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')})
561-
dialog.accepted.connect(function() {
562-
if (Daemon.currentWallet.verifyPassword(dialog.password)) {
563-
qtobject.authProceed()
564-
} else {
565-
qtobject.authCancel()
566-
}
587+
if (!Daemon.currentWallet.isEncrypted) {
588+
handleAuthVerifyPassword(qtobject, authMessage, function(password) {
589+
return Daemon.currentWallet.verifyKeystorePassword(password)
590+
})
591+
} else {
592+
handleAuthVerifyPassword(qtobject, authMessage, function(password) {
593+
return Daemon.currentWallet.verifyPassword(password)
594+
})
595+
}
596+
}
597+
} else if (method == 'keystore_else_pin') {
598+
if (!Daemon.currentWallet.canHaveKeystoreEncryption()
599+
|| Daemon.currentWallet.verifyKeystorePassword('')) {
600+
handleAuthRequired(qtobject, 'pin', authMessage)
601+
} else if (Daemon.currentWallet.isKeystorePasswordWalletPassword()) {
602+
handleAuthRequired(qtobject, 'pin', authMessage)
603+
} else {
604+
handleAuthVerifyPassword(qtobject, authMessage, function(password) {
605+
return Daemon.currentWallet.verifyKeystorePassword(password)
567606
})
568-
dialog.rejected.connect(function() {
569-
qtobject.authCancel()
607+
}
608+
} else if (method == 'keystore') {
609+
if (!Daemon.currentWallet.canHaveKeystoreEncryption()
610+
|| Daemon.currentWallet.verifyKeystorePassword('')) {
611+
qtobject.authProceed()
612+
} else {
613+
handleAuthVerifyPassword(qtobject, authMessage, function(password) {
614+
return Daemon.currentWallet.verifyKeystorePassword(password)
570615
})
571-
dialog.open()
572616
}
573617
} else if (method == 'pin') {
574618
if (Config.pinCode == '') {
575619
// no PIN configured
576620
handleAuthConfirmationOnly(qtobject, authMessage)
577621
} else {
578-
var dialog = app.pinDialog.createObject(app, {
579-
mode: 'check',
580-
pincode: Config.pinCode,
581-
authMessage: authMessage
582-
})
583-
dialog.accepted.connect(function() {
584-
qtobject.authProceed()
585-
dialog.close()
586-
})
587-
dialog.rejected.connect(function() {
588-
qtobject.authCancel()
589-
})
590-
dialog.open()
622+
handleAuthVerifyPin(qtobject, authMessage)
591623
}
592624
} else {
593625
console.log('unknown auth method ' + method)
594626
qtobject.authCancel()
595627
}
596628
}
597629

630+
function handleAuthVerifyPassword(qtobject, authMessage, validator) {
631+
var dialog = app.passwordDialog.createObject(app, {
632+
title: authMessage ? authMessage : qsTr('Enter current password')
633+
})
634+
dialog.accepted.connect(function() {
635+
if (validator(dialog.password)) {
636+
qtobject.authProceed(dialog.password)
637+
} else {
638+
qtobject.authCancel()
639+
var fdialog = app.messageDialog.createObject(app, {
640+
title: qsTr('Password incorrect')
641+
})
642+
fdialog.open()
643+
}
644+
})
645+
dialog.rejected.connect(function() {
646+
qtobject.authCancel()
647+
})
648+
dialog.open()
649+
}
650+
598651
function handleAuthConfirmationOnly(qtobject, authMessage) {
599652
if (!authMessage) {
600653
qtobject.authProceed()
@@ -610,6 +663,22 @@ ApplicationWindow
610663
dialog.open()
611664
}
612665

666+
function handleAuthVerifyPin(qtobject, authMessage) {
667+
var dialog = app.pinDialog.createObject(app, {
668+
mode: 'check',
669+
pincode: Config.pinCode,
670+
authMessage: authMessage
671+
})
672+
dialog.accepted.connect(function() {
673+
qtobject.authProceed()
674+
dialog.close()
675+
})
676+
dialog.rejected.connect(function() {
677+
qtobject.authCancel()
678+
})
679+
dialog.open()
680+
}
681+
613682
function startSwap() {
614683
var swapdialog = swapDialog.createObject(app)
615684
swapdialog.open()

electrum/gui/qml/qechannelopener.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,16 @@ def openChannel(self, confirm_backup_conflict=False):
165165
node_id=self._node_pubkey,
166166
fee_est=None)
167167

168-
acpt = lambda tx: self.do_open_channel(tx, self._connect_str_resolved, self._wallet.password)
168+
acpt = lambda tx: self.do_open_channel(tx, self._connect_str_resolved)
169169

170170
self._finalizer = QETxFinalizer(self, make_tx=mktx, accept=acpt)
171171
self._finalizer.canRbf = False
172172
self._finalizer.amount = self._amount
173173
self._finalizer.wallet = self._wallet
174174
self.finalizerChanged.emit()
175175

176-
@auth_protect(message=_('Open Lightning channel?'))
177-
def do_open_channel(self, funding_tx, conn_str, password):
176+
@auth_protect(method='keystore_else_pin', message=_('Open Lightning channel?'))
177+
def do_open_channel(self, funding_tx, conn_str, password=None):
178178
"""
179179
conn_str: a connection string that extract_nodeid can parse, i.e. cannot be a trampoline name
180180
"""
@@ -183,6 +183,9 @@ def do_open_channel(self, funding_tx, conn_str, password):
183183
funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
184184
lnworker = self._wallet.wallet.lnworker
185185

186+
if password is None:
187+
password = self._wallet.password
188+
186189
def open_thread():
187190
error = None
188191
try:

electrum/gui/qml/qeconfig.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ def pinCode(self, pin_code):
126126
self.config.set_key('pin_code', pin_code, True)
127127
self.pinCodeChanged.emit()
128128

129-
@auth_protect(method='wallet')
130-
def pinCodeRemoveAuth(self):
129+
# TODO: this allows disabling PIN unconditionally if wallet has no password
130+
# (which should never be the case however)
131+
@auth_protect(method='wallet_password')
132+
def pinCodeRemoveAuth(self, password=None):
131133
self.config.set_key('pin_code', '', True)
132134
self.pinCodeChanged.emit()
133135

electrum/gui/qml/qedaemon.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def checkThenDeleteWallet(self, wallet, confirm_requests=False, confirm_balance=
264264

265265
self.delete_wallet(wallet)
266266

267-
@auth_protect(message=_('Really delete this wallet?'))
267+
@auth_protect(method='wallet_password', message=_('Really delete this wallet?'))
268268
def delete_wallet(self, wallet):
269269
path = standardize_path(wallet.wallet.storage.path)
270270
self._logger.debug('deleting wallet with path %s' % path)
@@ -315,12 +315,15 @@ def suggestWalletName(self):
315315
return f'wallet_{i}'
316316

317317
@pyqtSlot()
318-
@auth_protect(method='wallet')
319318
def startChangePassword(self):
320319
if self._use_single_password:
321-
self.requestNewPassword.emit()
320+
self._do_start_change_all_passwords()
322321
else:
323-
self.currentWallet.requestNewPassword.emit()
322+
self.currentWallet.startChangePassword()
323+
324+
@auth_protect(method='wallet_password')
325+
def _do_start_change_all_passwords(self):
326+
self.requestNewPassword.emit()
324327

325328
@pyqtSlot(str)
326329
def setPassword(self, password):

electrum/gui/qml/qeswaphelper.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,19 @@ def fwd_swap_updatetx(self):
341341
self.miningfee = QEAmount(amount_sat=self._tx.get_fee()) if self._tx else QEAmount()
342342
self.check_valid(pay_amount, self._receive_amount)
343343

344-
def do_normal_swap(self, lightning_amount, onchain_amount):
344+
def do_normal_swap(self, lightning_amount, onchain_amount, password):
345345
assert self._tx
346346
if lightning_amount is None or onchain_amount is None:
347347
return
348+
349+
if password is None:
350+
password = self._wallet.password
351+
348352
loop = get_asyncio_loop()
349353
coro = self._wallet.wallet.lnworker.swap_manager.normal_swap(
350354
lightning_amount_sat=lightning_amount,
351355
expected_onchain_amount_sat=onchain_amount,
352-
password=self._wallet.password,
356+
password=password,
353357
tx=self._tx,
354358
)
355359

@@ -424,15 +428,19 @@ def executeSwap(self):
424428
if not self._wallet.wallet.network:
425429
self.error.emit(_("You are offline."))
426430
return
427-
self._do_execute_swap()
428-
429-
@auth_protect(message=_('Confirm Lightning swap?'))
430-
def _do_execute_swap(self):
431431
if self.isReverse:
432-
lightning_amount = self._send_amount
433-
onchain_amount = self._receive_amount
434-
self.do_reverse_swap(lightning_amount, onchain_amount)
432+
self._do_execute_reverse_swap()
435433
else:
436-
lightning_amount = self._receive_amount
437-
onchain_amount = self._send_amount
438-
self.do_normal_swap(lightning_amount, onchain_amount)
434+
self._do_execute_forward_swap()
435+
436+
@auth_protect(method='pin', message=_('Confirm Lightning swap?'))
437+
def _do_execute_reverse_swap(self):
438+
lightning_amount = self._send_amount
439+
onchain_amount = self._receive_amount
440+
self.do_reverse_swap(lightning_amount, onchain_amount)
441+
442+
@auth_protect(method='keystore_or_pin', message=_('Confirm Lightning swap?'))
443+
def _do_execute_forward_swap(self, password=None):
444+
lightning_amount = self._receive_amount
445+
onchain_amount = self._send_amount
446+
self.do_normal_swap(lightning_amount, onchain_amount, password)

0 commit comments

Comments
 (0)