Skip to content

Commit 23ee523

Browse files
committed
qml: remove pin code authentication
Completely removes the pin code authentication from qml. The config option in the wallet preferences has been renamed to "Payment authentication" and now either asks for the Android system authentication (Biometric or system pin/password) if enabled or will ask for the wallet password as fallback.
1 parent 6450187 commit 23ee523

9 files changed

Lines changed: 87 additions & 255 deletions

File tree

electrum/gui/qml/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from electrum.logging import get_logger
66

77

8-
def auth_protect(func=None, reject=None, method='pin', message=''):
8+
def auth_protect(func=None, reject=None, method='if_config_enabled', message=''):
99
if func is None:
1010
return partial(auth_protect, reject=reject, method=method, message=message)
1111

electrum/gui/qml/components/Pin.qml

Lines changed: 0 additions & 114 deletions
This file was deleted.

electrum/gui/qml/components/Preferences.qml

Lines changed: 27 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -157,62 +157,6 @@ Pane {
157157
}
158158
}
159159

160-
RowLayout {
161-
Layout.fillWidth: true
162-
spacing: 0
163-
Switch {
164-
id: usePin
165-
checked: Config.pinCode
166-
onCheckedChanged: {
167-
if (activeFocus) {
168-
console.log('PIN active ' + checked)
169-
if (checked) {
170-
var dialog = pinSetup.createObject(preferences, {mode: 'enter'})
171-
dialog.accepted.connect(function() {
172-
Config.pinCode = dialog.pincode
173-
dialog.close()
174-
})
175-
dialog.rejected.connect(function() {
176-
checked = false
177-
})
178-
dialog.open()
179-
} else {
180-
focus = false
181-
Config.pinCode = ''
182-
// re-add binding, pincode still set if auth failed
183-
checked = Qt.binding(function () { return Config.pinCode })
184-
}
185-
}
186-
187-
}
188-
}
189-
Label {
190-
Layout.fillWidth: true
191-
text: qsTr('PIN protect payments')
192-
wrapMode: Text.Wrap
193-
}
194-
}
195-
196-
Pane {
197-
background: Rectangle { color: Material.dialogColor }
198-
padding: 0
199-
visible: Config.pinCode != ''
200-
FlatButton {
201-
text: qsTr('Modify')
202-
onClicked: {
203-
var dialog = pinSetup.createObject(preferences, {
204-
mode: 'change',
205-
pincode: Config.pinCode
206-
})
207-
dialog.accepted.connect(function() {
208-
Config.pinCode = dialog.pincode
209-
dialog.close()
210-
})
211-
dialog.open()
212-
}
213-
}
214-
}
215-
216160
RowLayout {
217161
Layout.columnSpan: 2
218162
Layout.fillWidth: true
@@ -266,6 +210,33 @@ Pane {
266210
}
267211
}
268212

213+
RowLayout {
214+
Layout.columnSpan: 2
215+
Layout.fillWidth: true
216+
spacing: 0
217+
218+
property bool noWalletPassword: Daemon.currentWallet ? Daemon.currentWallet.verifyPassword('') : true
219+
enabled: Daemon.currentWallet && !noWalletPassword
220+
221+
Switch {
222+
id: paymentAuthentication
223+
// showing the toggle as checked even if the wallet has no password would be misleading
224+
checked: Config.paymentAuthentication && !(Daemon.currentWallet && parent.noWalletPassword)
225+
onToggled: {
226+
console.log('paymentAuthentication: ' + checked)
227+
if (activeFocus) {
228+
// will request authentication when checked = false
229+
Config.paymentAuthentication = !!checked;
230+
}
231+
}
232+
}
233+
Label {
234+
Layout.fillWidth: true
235+
text: qsTr('Payment authentication')
236+
wrapMode: Text.Wrap
237+
}
238+
}
239+
269240
RowLayout {
270241
Layout.columnSpan: 2
271242
Layout.fillWidth: true
@@ -515,11 +486,6 @@ Pane {
515486
}
516487
}
517488

518-
Component {
519-
id: pinSetup
520-
Pin {}
521-
}
522-
523489
Component.onCompleted: {
524490
language.currentIndex = language.indexOfValue(Config.language)
525491
baseUnit.currentIndex = _baseunits.indexOf(Config.baseUnit)

electrum/gui/qml/components/main.qml

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -419,14 +419,6 @@ ApplicationWindow
419419
}
420420
}
421421

422-
property alias pinDialog: _pinDialog
423-
Component {
424-
id: _pinDialog
425-
Pin {
426-
onClosed: destroy()
427-
}
428-
}
429-
430422
property alias genericShareDialog: _genericShareDialog
431423
Component {
432424
id: _genericShareDialog
@@ -818,42 +810,34 @@ ApplicationWindow
818810
function handleAuthRequired(qtobject, method, authMessage) {
819811
console.log('auth using method ' + method)
820812

821-
if (method == 'wallet_else_pin') {
822-
// if there is a loaded wallet and all wallets use the same password, use that
823-
// else delegate to pin auth
824-
if (Daemon.currentWallet && Daemon.singlePasswordEnabled) {
825-
method = 'wallet'
813+
if (method === 'if_config_enabled') {
814+
if (Config.paymentAuthentication) {
815+
method = 'wallet' // treat like a wallet auth request
826816
} else {
827-
method = 'pin'
828-
}
829-
}
830-
831-
if (method === 'wallet') {
832-
if (Daemon.currentWallet.verifyPassword('')) {
833-
// wallet has no password
834-
qtobject.authProceed()
835-
return
836-
}
837-
} else if (method === 'pin') {
838-
if (Config.pinCode === '') {
839-
// no PIN configured
840817
handleAuthConfirmationOnly(qtobject, authMessage)
841818
return
842819
}
843820
}
844821

822+
console.assert(method === 'wallet', 'unsupported method')
823+
if (Daemon.currentWallet.verifyPassword('')) {
824+
// wallet has no password
825+
qtobject.authProceed()
826+
return
827+
}
828+
845829
if (Biometrics.isAvailable && Biometrics.isEnabled) {
846830
_pendingBiometricAuth = { qtobject: qtobject, method: method, authMessage: authMessage }
847-
Biometrics.unlock()
831+
Biometrics.unlock(authMessage)
848832
return
849833
}
850834

851835
handleManualAuth(qtobject, method, authMessage)
852836
}
853837

854838
function handleManualAuth(qtobject, method, authMessage) {
855-
if (method == 'wallet') {
856-
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')})
839+
if (method === 'wallet') { // 'if_config_enabled' should have been converted to 'wallet' at this point
840+
var dialog = app.passwordDialog.createObject(app, authMessage ? {'title': authMessage} : {})
857841
dialog.accepted.connect(function() {
858842
if (Daemon.currentWallet.verifyPassword(dialog.password)) {
859843
qtobject.authProceed()
@@ -865,20 +849,6 @@ ApplicationWindow
865849
qtobject.authCancel()
866850
})
867851
dialog.open()
868-
} else if (method == 'pin') {
869-
var dialog = app.pinDialog.createObject(app, {
870-
mode: 'check',
871-
pincode: Config.pinCode,
872-
authMessage: authMessage
873-
})
874-
dialog.accepted.connect(function() {
875-
qtobject.authProceed()
876-
dialog.close()
877-
})
878-
dialog.rejected.connect(function() {
879-
qtobject.authCancel()
880-
})
881-
dialog.open()
882852
} else {
883853
console.log('unknown auth method ' + method)
884854
qtobject.authCancel()

electrum/gui/qml/java_classes/org/electrum/biometry/BiometricActivity.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.os.Bundle;
66
import android.os.CancellationSignal;
77
import android.content.Intent;
8+
import android.hardware.biometrics.BiometricManager;
89
import android.hardware.biometrics.BiometricPrompt;
910
import android.security.keystore.KeyGenParameterSpec;
1011
import android.security.keystore.KeyProperties;
@@ -34,8 +35,8 @@ public class BiometricActivity extends Activity {
3435
protected void onCreate(Bundle savedInstanceState) {
3536
super.onCreate(savedInstanceState);
3637

37-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
38-
Log.e(TAG, "Biometrics not supported on this Android version (requires API 29+)");
38+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
39+
Log.e(TAG, "Biometrics not supported on this Android version (requires API 30+)");
3940
setResult(RESULT_CANCELED);
4041
finish();
4142
return;
@@ -45,20 +46,17 @@ protected void onCreate(Bundle savedInstanceState) {
4546
}
4647

4748
private void handleIntent() {
48-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return;
49+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return;
4950

5051
Intent intent = getIntent();
5152
String action = intent.getStringExtra("action");
53+
String authMessage = intent.getStringExtra("auth_message");
5254

5355
Executor executor = getMainExecutor();
5456
BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(this)
5557
.setTitle("Electrum Wallet")
56-
.setSubtitle("Confirm your identity")
57-
.setNegativeButton("Cancel", executor, (dialog, which) -> {
58-
Log.d(TAG, "Authentication cancelled");
59-
setResult(RESULT_POPUP_CANCELLED);
60-
finish();
61-
})
58+
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
59+
.setSubtitle(authMessage)
6260
.build();
6361

6462
cancellationSignal = new CancellationSignal();
@@ -67,8 +65,17 @@ private void handleIntent() {
6765
@Override
6866
public void onAuthenticationError(int errorCode, CharSequence errString) {
6967
super.onAuthenticationError(errorCode, errString);
70-
Log.e(TAG, "Authentication error: " + errString);
71-
setResult(RESULT_CANCELED);
68+
Log.e(TAG, "Authentication error: " + errorCode + " " + errString);
69+
70+
if (
71+
errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED ||
72+
errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED ||
73+
errorCode == BiometricPrompt.BIOMETRIC_ERROR_TIMEOUT
74+
) {
75+
setResult(RESULT_POPUP_CANCELLED);
76+
} else {
77+
setResult(RESULT_CANCELED);
78+
}
7279
finish();
7380
}
7481

@@ -152,7 +159,7 @@ private SecretKey genSecretKey() throws Exception {
152159
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
153160
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
154161
.setUserAuthenticationRequired(true)
155-
.setInvalidatedByBiometricEnrollment(true);
162+
.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG | KeyProperties.AUTH_DEVICE_CREDENTIAL);
156163

157164
keyGenerator.init(builder.build());
158165
keyGenerator.generateKey();

0 commit comments

Comments
 (0)