diff --git a/electrum/gui/qml/auth.py b/electrum/gui/qml/auth.py index 3a7500815..05209e2c9 100644 --- a/electrum/gui/qml/auth.py +++ b/electrum/gui/qml/auth.py @@ -5,7 +5,18 @@ from PyQt6.QtCore import pyqtSignal, pyqtSlot from electrum.logging import get_logger -def auth_protect(func=None, reject=None, method='pin', message=''): +def auth_protect(func=None, reject=None, method='payment_auth', message=''): + """ + Supported methods: + * payment_auth: If the user has enabled the 'Payment authentication' config + they need to authenticate to continue. If biometrics are enabled they + can authenticate using the Android system dialog, else they will see the + wallet password dialog. + If the option is disabled they will have to confirm a dialog. + * wallet: Same as payment_auth, but not dependent on user configuration, + always requires authentication. + * wallet_password_only: No biometric/system authentication, user has to enter wallet password. + """ if func is None: return partial(auth_protect, reject=reject, method=method, message=message) diff --git a/electrum/gui/qml/components/Pin.qml b/electrum/gui/qml/components/Pin.qml deleted file mode 100644 index 1f222c0a8..000000000 --- a/electrum/gui/qml/components/Pin.qml +++ /dev/null @@ -1,114 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.Material - -import org.electrum 1.0 - -import "controls" - -ElDialog { - id: root - - property bool canCancel: true - property string mode // [check, enter, change] - property string pincode // old one passed in when change, new one passed out - property bool checkError: false - property string authMessage - property int _phase: mode == 'enter' ? 1 : 0 // 0 = existing pin, 1 = new pin, 2 = re-enter new pin - property string _pin - - title: authMessage ? authMessage : qsTr('PIN') - iconSource: Qt.resolvedUrl('../../icons/lock.png') - width: parent.width * 3/4 - z: 1000 - focus: true - closePolicy: canCancel ? Popup.CloseOnEscape | Popup.CloseOnPressOutside : Popup.NoAutoClose - allowClose: canCancel - needsSystemBarPadding: false - - anchors.centerIn: parent - - Overlay.modal: Rectangle { - color: canCancel ? "#aa000000" : "#ff000000" - } - - function submit() { - if (_phase == 0) { - if (pin.text == pincode) { - pin.text = '' - if (mode == 'check') - accepted() - else - _phase = 1 - } else { - pin.text = '' - checkError = true - } - } else if (_phase == 1) { - _pin = pin.text - pin.text = '' - _phase = 2 - } else if (_phase == 2) { - if (_pin == pin.text) { - pincode = pin.text - accepted() - } else { - pin.text = '' - checkError = true - } - } - } - - onAccepted: result = Dialog.Accepted - onRejected: result = Dialog.Rejected - onClosed: { - if (!root.result) { - root.reject() // make sure we reject the authed fn() - } - } - - ColumnLayout { - width: parent.width - - Label { - text: [qsTr('Enter PIN'), qsTr('Enter New PIN'), qsTr('Re-enter New PIN')][_phase] - font.pixelSize: constants.fontSizeXLarge - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.Wrap - Layout.fillWidth: true - } - - TextField { - id: pin - Layout.preferredWidth: fontMetrics.advanceWidth(passwordCharacter) * 6 + pin.leftPadding + pin.rightPadding - Layout.preferredHeight: fontMetrics.height + pin.topPadding + pin.bottomPadding - Layout.alignment: Qt.AlignHCenter - font.pixelSize: constants.fontSizeXXLarge - maximumLength: 6 - inputMethodHints: Qt.ImhDigitsOnly - - echoMode: TextInput.Password - focus: true - onTextChanged: { - checkError = false - if (text.length == 6) { - submit() - } - } - } - - Label { - opacity: checkError ? 1 : 0 - text: _phase == 0 ? qsTr('Wrong PIN') : qsTr('PIN doesn\'t match') - color: constants.colorError - Layout.alignment: Qt.AlignHCenter - } - } - - FontMetrics { - id: fontMetrics - font: pin.font - } - -} diff --git a/electrum/gui/qml/components/Preferences.qml b/electrum/gui/qml/components/Preferences.qml index 3626c4908..fe560d37f 100644 --- a/electrum/gui/qml/components/Preferences.qml +++ b/electrum/gui/qml/components/Preferences.qml @@ -157,62 +157,6 @@ Pane { } } - RowLayout { - Layout.fillWidth: true - spacing: 0 - Switch { - id: usePin - checked: Config.pinCode - onCheckedChanged: { - if (activeFocus) { - console.log('PIN active ' + checked) - if (checked) { - var dialog = pinSetup.createObject(preferences, {mode: 'enter'}) - dialog.accepted.connect(function() { - Config.pinCode = dialog.pincode - dialog.close() - }) - dialog.rejected.connect(function() { - checked = false - }) - dialog.open() - } else { - focus = false - Config.pinCode = '' - // re-add binding, pincode still set if auth failed - checked = Qt.binding(function () { return Config.pinCode }) - } - } - - } - } - Label { - Layout.fillWidth: true - text: qsTr('PIN protect payments') - wrapMode: Text.Wrap - } - } - - Pane { - background: Rectangle { color: Material.dialogColor } - padding: 0 - visible: Config.pinCode != '' - FlatButton { - text: qsTr('Modify') - onClicked: { - var dialog = pinSetup.createObject(preferences, { - mode: 'change', - pincode: Config.pinCode - }) - dialog.accepted.connect(function() { - Config.pinCode = dialog.pincode - dialog.close() - }) - dialog.open() - } - } - } - RowLayout { Layout.columnSpan: 2 Layout.fillWidth: true @@ -223,7 +167,6 @@ Pane { Connections { target: Biometrics function onEnablingFailed(error) { - useBiometrics.checked = false if (error === 'CANCELLED') { return // don't show error popup } @@ -237,8 +180,9 @@ Pane { Switch { id: useBiometrics checked: Biometrics.isEnabled - onToggled: { + onCheckedChanged: { if (activeFocus) { + useBiometrics.focus = false if (checked) { if (Daemon.singlePasswordEnabled) { Biometrics.enable(Daemon.singlePassword) @@ -254,7 +198,7 @@ Pane { err.open() } } else { - Biometrics.disable() + Biometrics.disableProtected() } } } @@ -266,6 +210,33 @@ Pane { } } + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + spacing: 0 + + property bool noWalletPassword: Daemon.currentWallet ? Daemon.currentWallet.verifyPassword('') : true + enabled: Daemon.currentWallet && !noWalletPassword + + Switch { + id: paymentAuthentication + // showing the toggle as checked even if the wallet has no password would be misleading + checked: Config.paymentAuthentication && !(Daemon.currentWallet && parent.noWalletPassword) + onCheckedChanged: { + if (activeFocus) { + // will request authentication when checked = false + console.log('paymentAuthentication: ' + checked) + Config.paymentAuthentication = checked; + } + } + } + Label { + Layout.fillWidth: true + text: qsTr('Payment authentication') + wrapMode: Text.Wrap + } + } + RowLayout { Layout.columnSpan: 2 Layout.fillWidth: true @@ -515,11 +486,6 @@ Pane { } } - Component { - id: pinSetup - Pin {} - } - Component.onCompleted: { language.currentIndex = language.indexOfValue(Config.language) baseUnit.currentIndex = _baseunits.indexOf(Config.baseUnit) diff --git a/electrum/gui/qml/components/WalletDetails.qml b/electrum/gui/qml/components/WalletDetails.qml index 68aab9636..a86cce3dd 100644 --- a/electrum/gui/qml/components/WalletDetails.qml +++ b/electrum/gui/qml/components/WalletDetails.qml @@ -577,6 +577,25 @@ Pane { } } + Connections { + target: Biometrics + function onEnablingFailed(error) { + if (error === 'CANCELLED') { + var biometrics_disabled_dialog = app.messageDialog.createObject(app, { + title: qsTr('Biometric Authentication'), + iconSource: Qt.resolvedUrl('../../icons/warning.png'), + text: qsTr('Biometric authentication disabled. You can enable it again in the settings.') + }) + biometrics_disabled_dialog.open() + return + } + var err = app.messageDialog.createObject(app, { + text: qsTr('Failed to update biometric authentication to new password: ') + error + }) + err.open() + } + } + Component { id: importAddressesKeysDialog ImportAddressesKeysDialog { diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index d5aa808fe..5a5fb5875 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -419,14 +419,6 @@ ApplicationWindow } } - property alias pinDialog: _pinDialog - Component { - id: _pinDialog - Pin { - onClosed: destroy() - } - } - property alias genericShareDialog: _genericShareDialog Component { id: _genericShareDialog @@ -639,42 +631,49 @@ ApplicationWindow Connections { target: Biometrics function onUnlockSuccess(password) { - if (_pendingBiometricAuth) { - if (_pendingBiometricAuth.action === 'load_wallet') { - _loadingWalletContext = _pendingBiometricAuth - Daemon.loadWallet(_pendingBiometricAuth.path, password) - _pendingBiometricAuth = null + if (app._pendingBiometricAuth) { + if (app._pendingBiometricAuth.action === 'load_wallet') { + app._loadingWalletContext = _pendingBiometricAuth + Daemon.loadWallet(app._pendingBiometricAuth.path, password) + app._pendingBiometricAuth = null return } - var qtobject = _pendingBiometricAuth.qtobject - var method = _pendingBiometricAuth.method + let qtobject = app._pendingBiometricAuth.qtobject + let method = app._pendingBiometricAuth.method if (Daemon.currentWallet.verifyPassword(password)) { qtobject.authProceed() } else { - console.log("Biometric password invalid falling back to manual input") - handleManualAuth(qtobject, method, _pendingBiometricAuth.authMessage) + console.warn("Biometric password invalid falling back to manual input") + // this shouldn't really happen so we better disable biometric auth + Biometrics.disable() + handleManualAuth(qtobject, method, app._pendingBiometricAuth.authMessage) } - _pendingBiometricAuth = null + app._pendingBiometricAuth = null } } + function onUnlockError(error) { console.log("Biometric auth failed: " + error) // we end up here if QEBiometrics fails to give us the decrypted password. The user might // have cancelled the biometric auth popup or the key got invalidated because a new fingerprint got registered. - if (_pendingBiometricAuth) { - // try manual auth - if (_pendingBiometricAuth.action === 'load_wallet') { + if (app._pendingBiometricAuth) { + if (app._pendingBiometricAuth.action === 'load_wallet') { // set loadingWalletContext to disable biometric auth until the OpenWalletDialog is closed - _loadingWalletContext = _pendingBiometricAuth - showOpenWalletDialog(_pendingBiometricAuth.name, _pendingBiometricAuth.path) + app._loadingWalletContext = app._pendingBiometricAuth + showOpenWalletDialog(app._pendingBiometricAuth.name, app._pendingBiometricAuth.path) } else { - handleManualAuth(_pendingBiometricAuth.qtobject, _pendingBiometricAuth.method, _pendingBiometricAuth.authMessage) + console.log('biometric auth failed, not falling back to passwordDialog') + app._pendingBiometricAuth.qtobject.authCancel() // no fallback to password dialog } - _pendingBiometricAuth = null + app._pendingBiometricAuth = null } } + + function onAuthRequired(method, authMessage) { + handleAuthRequired(Biometrics, method, authMessage) + } } property var _opendialog: null @@ -689,7 +688,7 @@ ApplicationWindow }) _opendialog.closed.connect(function() { _opendialog = null - _loadingWalletContext = null // dialog closed, we can allow trying biometric auth again + app._loadingWalletContext = null // dialog closed, we can allow trying biometric auth again _opendialog_startup = false }) _opendialog.open() @@ -700,8 +699,8 @@ ApplicationWindow target: Daemon function onWalletRequiresPassword(name, path) { console.log('wallet requires password') - if (Biometrics.isAvailable && Biometrics.isEnabled && !_loadingWalletContext) { - _pendingBiometricAuth = { + if (Biometrics.isAvailable && Biometrics.isEnabled && !app._loadingWalletContext) { + app._pendingBiometricAuth = { action: 'load_wallet', name: name, path: path @@ -731,7 +730,7 @@ ApplicationWindow dialog.open() } function onWalletLoaded() { - _loadingWalletContext = null // either biometric auth or manual auth was successful + app._loadingWalletContext = null // either biometric auth or manual auth was successful } } @@ -818,42 +817,41 @@ ApplicationWindow function handleAuthRequired(qtobject, method, authMessage) { console.log('auth using method ' + method) - if (method == 'wallet_else_pin') { - // if there is a loaded wallet and all wallets use the same password, use that - // else delegate to pin auth - if (Daemon.currentWallet && Daemon.singlePasswordEnabled) { + if (method === 'payment_auth') { + if (Config.paymentAuthentication) { + // treat like a wallet auth request method = 'wallet' } else { - method = 'pin' - } - } - - if (method === 'wallet') { - if (Daemon.currentWallet.verifyPassword('')) { - // wallet has no password - qtobject.authProceed() - return - } - } else if (method === 'pin') { - if (Config.pinCode === '') { - // no PIN configured handleAuthConfirmationOnly(qtobject, authMessage) return } } - if (Biometrics.isAvailable && Biometrics.isEnabled) { - _pendingBiometricAuth = { qtobject: qtobject, method: method, authMessage: authMessage } - Biometrics.unlock() - return + if (Daemon.currentWallet.verifyPassword('')) { + // wallet has no password + qtobject.authProceed() + return + } + + if (method !== 'wallet_password_only') { + if (Biometrics.isAvailable && Biometrics.isEnabled) { + app._pendingBiometricAuth = { + qtobject: qtobject, + method: method, + authMessage: authMessage + } + Biometrics.unlock(authMessage) + return + } } handleManualAuth(qtobject, method, authMessage) } function handleManualAuth(qtobject, method, authMessage) { - if (method == 'wallet') { - var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')}) + // 'payment_auth' should have been converted to 'wallet' at this point + if (method === 'wallet' || method === 'wallet_password_only') { + var dialog = app.passwordDialog.createObject(app, authMessage ? {'title': authMessage} : {}) dialog.accepted.connect(function() { if (Daemon.currentWallet.verifyPassword(dialog.password)) { qtobject.authProceed() @@ -865,20 +863,6 @@ ApplicationWindow qtobject.authCancel() }) dialog.open() - } else if (method == 'pin') { - var dialog = app.pinDialog.createObject(app, { - mode: 'check', - pincode: Config.pinCode, - authMessage: authMessage - }) - dialog.accepted.connect(function() { - qtobject.authProceed() - dialog.close() - }) - dialog.rejected.connect(function() { - qtobject.authCancel() - }) - dialog.open() } else { console.log('unknown auth method ' + method) qtobject.authCancel() diff --git a/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricActivity.java b/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricActivity.java index dea680dc6..acf94e8fc 100644 --- a/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricActivity.java +++ b/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricActivity.java @@ -5,6 +5,7 @@ import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.content.Intent; +import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -34,8 +35,8 @@ public class BiometricActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - Log.e(TAG, "Biometrics not supported on this Android version (requires API 29+)"); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + Log.e(TAG, "Biometrics not supported on this Android version (requires API 30+)"); setResult(RESULT_CANCELED); finish(); return; @@ -45,20 +46,17 @@ public class BiometricActivity extends Activity { } private void handleIntent() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return; Intent intent = getIntent(); String action = intent.getStringExtra("action"); + String authMessage = intent.getStringExtra("auth_message"); Executor executor = getMainExecutor(); BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(this) .setTitle("Electrum Wallet") - .setSubtitle("Confirm your identity") - .setNegativeButton("Cancel", executor, (dialog, which) -> { - Log.d(TAG, "Authentication cancelled"); - setResult(RESULT_POPUP_CANCELLED); - finish(); - }) + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .setSubtitle(authMessage) .build(); cancellationSignal = new CancellationSignal(); @@ -67,8 +65,17 @@ public class BiometricActivity extends Activity { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { super.onAuthenticationError(errorCode, errString); - Log.e(TAG, "Authentication error: " + errString); - setResult(RESULT_CANCELED); + Log.e(TAG, "Authentication error: " + errorCode + " " + errString); + + if ( + errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED || + errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED || + errorCode == BiometricPrompt.BIOMETRIC_ERROR_TIMEOUT + ) { + setResult(RESULT_POPUP_CANCELLED); + } else { + setResult(RESULT_CANCELED); + } finish(); } @@ -152,7 +159,7 @@ public class BiometricActivity extends Activity { .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) - .setInvalidatedByBiometricEnrollment(true); + .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG | KeyProperties.AUTH_DEVICE_CREDENTIAL); keyGenerator.init(builder.build()); keyGenerator.generateKey(); diff --git a/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricHelper.java b/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricHelper.java index 8be4d3a66..fd0922711 100644 --- a/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricHelper.java +++ b/electrum/gui/qml/java_classes/org/electrum/biometry/BiometricHelper.java @@ -10,10 +10,7 @@ public class BiometricHelper { public static boolean isAvailable(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // API 30+ BiometricManager biometricManager = context.getSystemService(BiometricManager.class); - return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS; - } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { // API 29 - BiometricManager biometricManager = context.getSystemService(BiometricManager.class); - return biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS; + return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS; } return false; } diff --git a/electrum/gui/qml/qebiometrics.py b/electrum/gui/qml/qebiometrics.py index bcf46d3e1..3d806a68a 100644 --- a/electrum/gui/qml/qebiometrics.py +++ b/electrum/gui/qml/qebiometrics.py @@ -3,11 +3,14 @@ import secrets from enum import Enum from typing import Optional, TYPE_CHECKING +from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty + +from electrum.i18n import _ from electrum.logging import get_logger from electrum.base_crash_reporter import send_exception_to_crash_reporter from electrum.crypto import aes_encrypt_with_iv, aes_decrypt_with_iv -from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty +from .auth import auth_protect, AuthMixin if TYPE_CHECKING: from electrum.simple_config import SimpleConfig @@ -37,7 +40,7 @@ class BiometricAction(str, Enum): DECRYPT = "DECRYPT" -class QEBiometrics(QObject): +class QEBiometrics(AuthMixin, QObject): REQUEST_CODE_BIOMETRIC_ACTIVITY = 24553 # random 16 bit int RESULT_CODE_SETUP_FAILED = 101 # codes duplicated from BiometricActivity.java RESULT_CODE_POPUP_CANCELLED = 102 @@ -94,22 +97,40 @@ class QEBiometrics(QObject): _logger.info("Android biometric authentication disabled") @pyqtSlot() - def unlock(self): + @auth_protect(method='wallet_password_only', reject='_disable_protected_failed') + def disableProtected(self): + """ + Exists to ensure the user knows the wallet password when manually disabling + biometric authentication. If they don't remember the password they can still do a seed + backup or transactions if biometrics stay enabled. However, note it is still possible for + biometrics to get disabled automatically on invalidation or error, so this cannot + fully protect the user from forgetting their wallet password either. + """ + self.disable() + + def _disable_protected_failed(self): + self.isEnabledChanged.emit() + + @pyqtSlot() + @pyqtSlot(str) + def unlock(self, auth_message: str = None): """ Called when the user needs to authenticate. Makes the AndroidKeyStore decrypt our encrypted wrap key, we then use the decrypted wrap key to decrypt the encrypted wallet password. + auth_message is shown in the system auth popup and defaults to 'Confirm your identity'. """ encrypted_wrap_key = self.config.WALLET_ANDROID_BIOMETRIC_AUTH_ENCRYPTED_WRAP_KEY assert encrypted_wrap_key, "shouldn't unlock if biometric auth is disabled" - self._start_activity(BiometricAction.DECRYPT, data=encrypted_wrap_key) + self._start_activity(BiometricAction.DECRYPT, data=encrypted_wrap_key, auth_message=auth_message) - def _start_activity(self, action: BiometricAction, data: str): + def _start_activity(self, action: BiometricAction, data: str, auth_message: str = None): self._current_action = action _logger.debug(f"_start_activity: {action.value}, {len(data)=}") intent = jIntent(jPythonActivity, jBiometricActivity) intent.putExtra(jString("action"), jString(action.value)) + intent.putExtra(jString("auth_message"), jString(auth_message or _("Confirm your identity"))) if action == BiometricAction.ENCRYPT: intent.putExtra(jString("data"), jString(data)) # wrap_key elif action == BiometricAction.DECRYPT: @@ -178,4 +199,3 @@ class QEBiometrics(QObject): self.config.WALLET_ANDROID_BIOMETRIC_AUTH_ENCRYPTED_WRAP_KEY = encrypted_bundle self.config.WALLET_ANDROID_USE_BIOMETRIC_AUTHENTICATION = True self.isEnabledChanged.emit() - diff --git a/electrum/gui/qml/qeconfig.py b/electrum/gui/qml/qeconfig.py index d0d4d79e1..8c0607b51 100644 --- a/electrum/gui/qml/qeconfig.py +++ b/electrum/gui/qml/qeconfig.py @@ -153,23 +153,26 @@ class QEConfig(AuthMixin, QObject): self.config.WALLET_PAYREQ_EXPIRY_SECONDS = expiry self.requestExpiryChanged.emit() - pinCodeChanged = pyqtSignal() - @pyqtProperty(str, notify=pinCodeChanged) - def pinCode(self): - return self.config.CONFIG_PIN_CODE or "" + paymentAuthenticationChanged = pyqtSignal() + @pyqtProperty(bool, notify=paymentAuthenticationChanged) + def paymentAuthentication(self): + return self.config.GUI_QML_PAYMENT_AUTHENTICATION - @pinCode.setter - def pinCode(self, pin_code): - if pin_code == '': - self.pinCodeRemoveAuth() + @paymentAuthentication.setter + def paymentAuthentication(self, enabled: bool): + if enabled: + self.config.GUI_QML_PAYMENT_AUTHENTICATION = True + self.paymentAuthenticationChanged.emit() else: - self.config.CONFIG_PIN_CODE = pin_code - self.pinCodeChanged.emit() + self._disable_payment_authentication() - @auth_protect(method='wallet_else_pin') - def pinCodeRemoveAuth(self): - self.config.CONFIG_PIN_CODE = "" - self.pinCodeChanged.emit() + @auth_protect(method='wallet', reject='_payment_auth_reject') + def _disable_payment_authentication(self): + self.config.GUI_QML_PAYMENT_AUTHENTICATION = False + self.paymentAuthenticationChanged.emit() + + def _payment_auth_reject(self): + self.paymentAuthenticationChanged.emit() useGossipChanged = pyqtSignal() @pyqtProperty(bool, notify=useGossipChanged) diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index 5b2ef6334..311eebc18 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -388,7 +388,7 @@ class QEDaemon(AuthMixin, QObject): return f'wallet_{i}' @pyqtSlot() - @auth_protect(method='wallet') + @auth_protect(method='wallet_password_only') def startChangePassword(self): if self._use_single_password: self.requestNewPassword.emit() diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 2623d77f2..85ac6d2d3 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -853,6 +853,7 @@ Warning: setting this to too low will result in lots of payment failures."""), GUI_QML_ADDRESS_LIST_SHOW_USED = ConfigVar('address_list_show_used', default=False, type_=bool) GUI_QML_ALWAYS_ALLOW_SCREENSHOTS = ConfigVar('android_always_allow_screenshots', default=False, type_=bool) GUI_QML_SET_MAX_BRIGHTNESS_ON_QR_DISPLAY = ConfigVar('android_set_max_brightness_on_qr_display', default=True, type_=bool) + GUI_QML_PAYMENT_AUTHENTICATION = ConfigVar('qml_payment_authentication', default=False, type_=bool) BTC_AMOUNTS_DECIMAL_POINT = ConfigVar('decimal_point', default=DECIMAL_POINT_DEFAULT, type_=int) BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT = ConfigVar( @@ -924,7 +925,6 @@ Warning: setting this to too low will result in lots of payment failures."""), RECENTLY_OPEN_WALLET_FILES = ConfigVar('recently_open', default=None) IO_DIRECTORY = ConfigVar('io_dir', default=os.path.expanduser('~'), type_=str) WALLET_BACKUP_DIRECTORY = ConfigVar('backup_dir', default=None, type_=str) - CONFIG_PIN_CODE = ConfigVar('pin_code', default=None, type_=str) QR_READER_FLIP_X = ConfigVar('qrreader_flip_x', default=True, type_=bool) WIZARD_DONT_CREATE_SEGWIT = ConfigVar('nosegwit', default=False, type_=bool) CONFIG_FORGET_CHANGES = ConfigVar('forget_config', default=False, type_=bool)