@capgo/capacitor-native-biometric 7.1.2 → 7.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,28 +57,21 @@ public class AuthActivity extends AppCompatActivity {
57
57
  boolean useFallback = getIntent().getBooleanExtra("useFallback", false);
58
58
  int[] allowedTypes = getIntent().getIntArrayExtra("allowedBiometryTypes");
59
59
 
60
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
61
- int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
62
- if (useFallback) {
63
- authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
64
- }
65
- if (allowedTypes != null) {
66
- // Filter authenticators based on allowed types
67
- authenticators = getAllowedAuthenticators(allowedTypes);
68
- }
69
- builder.setAllowedAuthenticators(authenticators);
70
- } else {
71
- if (useFallback) {
72
- builder.setDeviceCredentialAllowed(true);
73
- } else {
74
- builder.setNegativeButtonText(
75
- getIntent().hasExtra("negativeButtonText")
76
- ? Objects.requireNonNull(
77
- getIntent().getStringExtra("negativeButtonText")
78
- )
79
- : "Cancel"
80
- );
81
- }
60
+ int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
61
+ if (useFallback) {
62
+ authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
63
+ }
64
+ if (allowedTypes != null) {
65
+ // Filter authenticators based on allowed types
66
+ authenticators = getAllowedAuthenticators(allowedTypes);
67
+ }
68
+ builder.setAllowedAuthenticators(authenticators);
69
+
70
+ if (!useFallback) {
71
+ String negativeText = getIntent().getStringExtra("negativeButtonText");
72
+ builder.setNegativeButtonText(
73
+ negativeText != null ? negativeText : "Cancel"
74
+ );
82
75
  }
83
76
 
84
77
  BiometricPrompt.PromptInfo promptInfo = builder.build();
@@ -93,9 +86,16 @@ public class AuthActivity extends AppCompatActivity {
93
86
  @NonNull CharSequence errString
94
87
  ) {
95
88
  super.onAuthenticationError(errorCode, errString);
96
- int pluginErrorCode = AuthActivity.convertToPluginErrorCode(
97
- errorCode
98
- );
89
+ // Handle lockout cases explicitly
90
+ if (
91
+ errorCode == BiometricPrompt.ERROR_LOCKOUT ||
92
+ errorCode == BiometricPrompt.ERROR_LOCKOUT_PERMANENT
93
+ ) {
94
+ int pluginErrorCode = convertToPluginErrorCode(errorCode);
95
+ finishActivity("error", pluginErrorCode, errString.toString());
96
+ return;
97
+ }
98
+ int pluginErrorCode = convertToPluginErrorCode(errorCode);
99
99
  finishActivity("error", pluginErrorCode, errString.toString());
100
100
  }
101
101
 
@@ -111,11 +111,10 @@ public class AuthActivity extends AppCompatActivity {
111
111
  public void onAuthenticationFailed() {
112
112
  super.onAuthenticationFailed();
113
113
  counter++;
114
- if (counter == maxAttempts) finishActivity(
115
- "failed",
116
- 10,
117
- "Authentication failed."
118
- );
114
+ if (counter >= maxAttempts) {
115
+ // Use error code 4 for too many attempts to match iOS behavior
116
+ finishActivity("error", 4, "Too many failed attempts");
117
+ }
119
118
  }
120
119
  }
121
120
  );
@@ -153,11 +152,11 @@ public class AuthActivity extends AppCompatActivity {
153
152
  case BiometricPrompt.ERROR_HW_NOT_PRESENT:
154
153
  return 1;
155
154
  case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
156
- return 2;
155
+ return 2; // Permanent lockout
157
156
  case BiometricPrompt.ERROR_NO_BIOMETRICS:
158
157
  return 3;
159
158
  case BiometricPrompt.ERROR_LOCKOUT:
160
- return 4;
159
+ return 4; // Temporary lockout (too many attempts)
161
160
  // Authentication Failure (10) Handled by `onAuthenticationFailed`.
162
161
  // App Cancel (11), Invalid Context (12), and Not Interactive (13) are not valid error codes for Android.
163
162
  case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
@@ -168,6 +167,8 @@ public class AuthActivity extends AppCompatActivity {
168
167
  case BiometricPrompt.ERROR_USER_CANCELED:
169
168
  case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
170
169
  return 16;
170
+ case BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC:
171
+ return 0; // Success case, should not be handled here
171
172
  default:
172
173
  return 0;
173
174
  }
@@ -8,7 +8,6 @@ import android.content.Intent;
8
8
  import android.content.SharedPreferences;
9
9
  import android.content.pm.PackageManager;
10
10
  import android.os.Build;
11
- import android.security.KeyPairGeneratorSpec;
12
11
  import android.security.keystore.KeyGenParameterSpec;
13
12
  import android.security.keystore.KeyProperties;
14
13
  import android.security.keystore.StrongBoxUnavailableException;
@@ -74,42 +73,55 @@ public class NativeBiometric extends Plugin {
74
73
 
75
74
  private int getAvailableFeature() {
76
75
  // default to none
77
- int type = NONE;
78
-
79
- // if has fingerprint
80
- if (
81
- getContext()
82
- .getPackageManager()
83
- .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
84
- ) {
85
- type = FINGERPRINT;
86
- }
76
+ BiometricManager biometricManager = BiometricManager.from(getContext());
87
77
 
88
- // if has face auth
89
- if (
90
- getContext()
91
- .getPackageManager()
92
- .hasSystemFeature(PackageManager.FEATURE_FACE)
93
- ) {
94
- // if also has fingerprint
95
- if (type != NONE) return MULTIPLE;
78
+ // Check for biometric capabilities
79
+ int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
80
+ int canAuthenticate = biometricManager.canAuthenticate(authenticators);
96
81
 
97
- type = FACE_AUTHENTICATION;
98
- }
82
+ if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
83
+ // Check specific features
84
+ PackageManager pm = getContext().getPackageManager();
85
+ boolean hasFinger = pm.hasSystemFeature(
86
+ PackageManager.FEATURE_FINGERPRINT
87
+ );
88
+ boolean hasIris = false;
89
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
90
+ hasIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
91
+ }
99
92
 
100
- // if has iris auth
101
- if (
102
- getContext()
103
- .getPackageManager()
104
- .hasSystemFeature(PackageManager.FEATURE_IRIS)
105
- ) {
106
- // if also has fingerprint or face auth
107
- if (type != NONE) return MULTIPLE;
93
+ // For face, we rely on BiometricManager since it's more reliable
94
+ boolean hasFace = false;
95
+ try {
96
+ // Try to create a face authentication prompt - if it succeeds, face auth is available
97
+ androidx.biometric.BiometricPrompt.PromptInfo promptInfo =
98
+ new androidx.biometric.BiometricPrompt.PromptInfo.Builder()
99
+ .setTitle("Test")
100
+ .setNegativeButtonText("Cancel")
101
+ .setAllowedAuthenticators(
102
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
103
+ )
104
+ .build();
105
+ hasFace = true;
106
+ } catch (Exception e) {
107
+ System.out.println(
108
+ "Error creating face authentication prompt: " + e.getMessage()
109
+ );
110
+ }
108
111
 
109
- type = IRIS_AUTHENTICATION;
112
+ // Determine the type based on available features
113
+ if (hasFinger && (hasFace || hasIris)) {
114
+ return MULTIPLE;
115
+ } else if (hasFinger) {
116
+ return FINGERPRINT;
117
+ } else if (hasFace) {
118
+ return FACE_AUTHENTICATION;
119
+ } else if (hasIris) {
120
+ return IRIS_AUTHENTICATION;
121
+ }
110
122
  }
111
123
 
112
- return type;
124
+ return NONE;
113
125
  }
114
126
 
115
127
  @PluginMethod
@@ -121,7 +133,13 @@ public class NativeBiometric extends Plugin {
121
133
  );
122
134
 
123
135
  BiometricManager biometricManager = BiometricManager.from(getContext());
124
- int canAuthenticateResult = biometricManager.canAuthenticate();
136
+ int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
137
+ if (useFallback) {
138
+ authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
139
+ }
140
+ int canAuthenticateResult = biometricManager.canAuthenticate(
141
+ authenticators
142
+ );
125
143
  // Using deviceHasCredentials instead of canAuthenticate(DEVICE_CREDENTIAL)
126
144
  // > "Developers that wish to check for the presence of a PIN, pattern, or password on these versions should instead use isDeviceSecure."
127
145
  // @see https://developer.android.com/reference/androidx/biometric/BiometricManager#canAuthenticate(int)
@@ -153,23 +171,24 @@ public class NativeBiometric extends Plugin {
153
171
 
154
172
  intent.putExtra("title", call.getString("title", "Authenticate"));
155
173
 
156
- if (call.hasOption("subtitle")) {
157
- intent.putExtra("subtitle", call.getString("subtitle"));
174
+ String subtitle = call.getString("subtitle");
175
+ if (subtitle != null) {
176
+ intent.putExtra("subtitle", subtitle);
158
177
  }
159
178
 
160
- if (call.hasOption("description")) {
161
- intent.putExtra("description", call.getString("description"));
179
+ String description = call.getString("description");
180
+ if (description != null) {
181
+ intent.putExtra("description", description);
162
182
  }
163
183
 
164
- if (call.hasOption("negativeButtonText")) {
165
- intent.putExtra(
166
- "negativeButtonText",
167
- call.getString("negativeButtonText")
168
- );
184
+ String negativeButtonText = call.getString("negativeButtonText");
185
+ if (negativeButtonText != null) {
186
+ intent.putExtra("negativeButtonText", negativeButtonText);
169
187
  }
170
188
 
171
- if (call.hasOption("maxAttempts")) {
172
- intent.putExtra("maxAttempts", call.getInt("maxAttempts"));
189
+ Integer maxAttempts = call.getInt("maxAttempts");
190
+ if (maxAttempts != null) {
191
+ intent.putExtra("maxAttempts", maxAttempts);
173
192
  }
174
193
 
175
194
  // Pass allowed biometry types
@@ -220,7 +239,7 @@ public class NativeBiometric extends Plugin {
220
239
  call.resolve();
221
240
  } catch (GeneralSecurityException | IOException e) {
222
241
  call.reject("Failed to save credentials", e);
223
- e.printStackTrace();
242
+ System.out.println("Error saving credentials: " + e.getMessage());
224
243
  }
225
244
  } else {
226
245
  call.reject("Missing properties");
@@ -443,8 +462,12 @@ public class NativeBiometric extends Plugin {
443
462
  ANDROID_KEY_STORE
444
463
  );
445
464
  keyPairGenerator.initialize(
446
- new KeyPairGeneratorSpec.Builder(getContext())
447
- .setAlias(KEY_ALIAS)
465
+ new KeyGenParameterSpec.Builder(
466
+ KEY_ALIAS,
467
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
468
+ )
469
+ .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
470
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
448
471
  .build()
449
472
  );
450
473
  keyPairGenerator.generateKeyPair();
@@ -471,8 +494,7 @@ public class NativeBiometric extends Plugin {
471
494
  cipherOutputStream.write(secret);
472
495
  cipherOutputStream.close();
473
496
 
474
- byte[] vals = outputStream.toByteArray();
475
- return vals;
497
+ return outputStream.toByteArray();
476
498
  }
477
499
 
478
500
  private byte[] rsaDecrypt(byte[] encrypted, String KEY_ALIAS)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-biometric",
3
- "version": "7.1.2",
3
+ "version": "7.1.7",
4
4
  "description": "This plugin gives access to the native biometric apis for android and iOS",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/esm/index.d.ts",