@aparajita/capacitor-biometric-auth 5.1.0 → 5.2.1
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.
- package/README.md +9 -8
- package/android/src/main/java/com/aparajita/capacitor/biometricauth/AuthActivity.java +5 -0
- package/android/src/main/java/com/aparajita/capacitor/biometricauth/BiometricAuthNative.java +43 -20
- package/dist/esm/definitions.d.ts +8 -0
- package/ios/Plugin/Plugin.swift +33 -12
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -147,14 +147,15 @@ Register a function that will be called when the app resumes. The function will
|
|
|
147
147
|
|
|
148
148
|
#### AuthenticateOptions
|
|
149
149
|
|
|
150
|
-
| Prop
|
|
151
|
-
|
|
|
152
|
-
| reason
|
|
153
|
-
| cancelTitle
|
|
154
|
-
| allowDeviceCredential
|
|
155
|
-
| iosFallbackTitle
|
|
156
|
-
| androidTitle
|
|
157
|
-
| androidSubtitle
|
|
150
|
+
| Prop | Type | Description |
|
|
151
|
+
| :-------------------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| reason | string | The reason for requesting authentication. Displays in the authentication dialog presented to the user. If not supplied, a default message is displayed. |
|
|
153
|
+
| cancelTitle | string | iOS: The system presents a cancel button during biometric authentication to let the user abort the authentication attempt. The button appears every time the system asks the user to present a finger registered with Touch ID. For Face ID, the button only appears if authentication fails and the user is prompted to try again. Either way, the user can stop trying to authenticate by tapping the button.<br><br>Android: The text for the negative button. This would typically be used as a "Cancel" button, but may be also used to show an alternative method for authentication, such as a screen that asks for a backup password.<br><br>Default: "Cancel" |
|
|
154
|
+
| allowDeviceCredential | boolean | If true, allows for authentication using device unlock credentials. Default is false.<br><br>iOS: If biometry is available, enrolled, and not disabled, the system uses that first. After the first Touch ID failure or second Face ID failure, if `iosFallbackTitle` is not an empty string, a fallback button appears in the authentication dialog. If the user taps the fallback button, the system prompts the user for the device passcode or the user’s password. If `iosFallbackTitle` is an empty string, no fallback button will appear.<br><br>If biometry is not available, enrolled and enabled, and a passcode is set, the system immediately prompts the user for the device passcode or user’s password. Authentication fails with the error code `passcodeNotSet` if the device passcode isn’t enabled.<br><br>If a passcode is not set on the device, a `passcodeNotSet` error is returned.<br><br>The system disables passcode authentication after 6 unsuccessful attempts, with progressively increasing delays between attempts.<br><br>The title of the fallback button may be customized by setting `iosFallbackTitle` to a non-empty string.<br><br>Android: The user will first be prompted to authenticate with biometrics, but also given the option to authenticate with their device PIN, pattern, or password by tapping a button in the authentication dialog. The title of the button is supplied by the system. |
|
|
155
|
+
| iosFallbackTitle | string | The system presents a fallback button when biometric authentication fails — for example, because the system doesn’t recognize the presented finger, or after several failed attempts to recognize the user’s face.<br><br>If `allowDeviceCredential` is false, tapping this button dismisses the authentication dialog and returns the error code userFallback. If undefined, the localized system default title is used. Passing an empty string hides the fallback button completely.<br><br>If `allowDeviceCredential` is true and this is undefined, the localized system default title is used. |
|
|
156
|
+
| androidTitle | string | Title for the Android dialog. If not supplied, the system default is used. |
|
|
157
|
+
| androidSubtitle | string | Subtitle for the Android dialog. If not supplied, the system default is used. |
|
|
158
|
+
| androidConfirmationRequired | boolean | For information on this setting, see:<br><br>https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder#setConfirmationRequired(boolean)<br><br>If not set, defaults to true. |
|
|
158
159
|
|
|
159
160
|
#### PluginListenerHandle
|
|
160
161
|
|
|
@@ -7,6 +7,7 @@ import android.hardware.biometrics.BiometricManager;
|
|
|
7
7
|
import android.os.Build;
|
|
8
8
|
import android.os.Bundle;
|
|
9
9
|
import android.os.Handler;
|
|
10
|
+
import android.util.Log;
|
|
10
11
|
import androidx.annotation.NonNull;
|
|
11
12
|
import androidx.appcompat.app.AppCompatActivity;
|
|
12
13
|
import androidx.biometric.BiometricPrompt;
|
|
@@ -81,6 +82,10 @@ public class AuthActivity extends AppCompatActivity {
|
|
|
81
82
|
);
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
builder.setConfirmationRequired(
|
|
86
|
+
intent.getBooleanExtra(BiometricAuthNative.CONFIRMATION_REQUIRED, true)
|
|
87
|
+
);
|
|
88
|
+
|
|
84
89
|
BiometricPrompt.PromptInfo promptInfo = builder.build();
|
|
85
90
|
BiometricPrompt prompt = new BiometricPrompt(
|
|
86
91
|
this,
|
package/android/src/main/java/com/aparajita/capacitor/biometricauth/BiometricAuthNative.java
CHANGED
|
@@ -30,6 +30,8 @@ public class BiometricAuthNative extends Plugin {
|
|
|
30
30
|
public static final String REASON = "reason";
|
|
31
31
|
public static final String CANCEL_TITLE = "cancelTitle";
|
|
32
32
|
public static final String DEVICE_CREDENTIAL = "allowDeviceCredential";
|
|
33
|
+
public static final String CONFIRMATION_REQUIRED =
|
|
34
|
+
"androidConfirmationRequired";
|
|
33
35
|
public static final String MAX_ATTEMPTS = "androidMaxAttempts";
|
|
34
36
|
public static final int DEFAULT_MAX_ATTEMPTS = 3;
|
|
35
37
|
// Error code when biometry is not recognized
|
|
@@ -94,6 +96,13 @@ public class BiometricAuthNative extends Plugin {
|
|
|
94
96
|
*/
|
|
95
97
|
@PluginMethod
|
|
96
98
|
public void checkBiometry(PluginCall call) {
|
|
99
|
+
call.resolve(checkDeviceBiometry());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check the device's availability and type of biometric authentication.
|
|
104
|
+
*/
|
|
105
|
+
private JSObject checkDeviceBiometry() {
|
|
97
106
|
BiometricManager manager = BiometricManager.from(getContext());
|
|
98
107
|
int biometryResult;
|
|
99
108
|
|
|
@@ -104,14 +113,14 @@ public class BiometricAuthNative extends Plugin {
|
|
|
104
113
|
biometryResult = manager.canAuthenticate();
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
JSObject
|
|
108
|
-
|
|
116
|
+
JSObject result = new JSObject();
|
|
117
|
+
result.put(
|
|
109
118
|
"isAvailable",
|
|
110
119
|
biometryResult == BiometricManager.BIOMETRIC_SUCCESS
|
|
111
120
|
);
|
|
112
121
|
|
|
113
122
|
biometryTypes = getDeviceBiometryTypes();
|
|
114
|
-
|
|
123
|
+
result.put("biometryType", biometryTypes.get(0).getType());
|
|
115
124
|
|
|
116
125
|
JSArray returnTypes = new JSArray();
|
|
117
126
|
|
|
@@ -119,7 +128,7 @@ public class BiometricAuthNative extends Plugin {
|
|
|
119
128
|
returnTypes.put(type.getType());
|
|
120
129
|
}
|
|
121
130
|
|
|
122
|
-
|
|
131
|
+
result.put("biometryTypes", returnTypes);
|
|
123
132
|
|
|
124
133
|
String reason = "";
|
|
125
134
|
|
|
@@ -154,13 +163,13 @@ public class BiometricAuthNative extends Plugin {
|
|
|
154
163
|
errorCode = "biometryNotAvailable";
|
|
155
164
|
}
|
|
156
165
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
result.put("reason", reason);
|
|
167
|
+
result.put("code", errorCode);
|
|
168
|
+
return result;
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
private ArrayList<BiometryType> getDeviceBiometryTypes() {
|
|
163
|
-
ArrayList<BiometryType> types = new ArrayList
|
|
172
|
+
ArrayList<BiometryType> types = new ArrayList<>();
|
|
164
173
|
PackageManager manager = getContext().getPackageManager();
|
|
165
174
|
|
|
166
175
|
if (manager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
|
@@ -187,6 +196,17 @@ public class BiometricAuthNative extends Plugin {
|
|
|
187
196
|
*/
|
|
188
197
|
@PluginMethod
|
|
189
198
|
public void authenticate(final PluginCall call) {
|
|
199
|
+
// Make sure biometry is available
|
|
200
|
+
JSObject checkResult = checkDeviceBiometry();
|
|
201
|
+
|
|
202
|
+
if (Boolean.FALSE.equals(checkResult.getBoolean("isAvailable", false))) {
|
|
203
|
+
call.reject(
|
|
204
|
+
checkResult.getString("reason", ""),
|
|
205
|
+
checkResult.getString("code", "")
|
|
206
|
+
);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
190
210
|
// The result of an intent is supposed to have the package name as a prefix
|
|
191
211
|
RESULT_EXTRA_PREFIX = getContext().getPackageName() + ".";
|
|
192
212
|
|
|
@@ -205,6 +225,13 @@ public class BiometricAuthNative extends Plugin {
|
|
|
205
225
|
call.getBoolean(DEVICE_CREDENTIAL, false)
|
|
206
226
|
);
|
|
207
227
|
|
|
228
|
+
if (call.hasOption(CONFIRMATION_REQUIRED)) {
|
|
229
|
+
intent.putExtra(
|
|
230
|
+
CONFIRMATION_REQUIRED,
|
|
231
|
+
call.getBoolean(CONFIRMATION_REQUIRED, true)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
208
235
|
// Just in case the developer does something dumb like using a number < 1...
|
|
209
236
|
Integer maxAttemptsConfig = call.getInt(MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS);
|
|
210
237
|
int maxAttempts = Math.max(
|
|
@@ -270,23 +297,19 @@ public class BiometricAuthNative extends Plugin {
|
|
|
270
297
|
);
|
|
271
298
|
|
|
272
299
|
switch (resultType) {
|
|
273
|
-
case SUCCESS
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
case ERROR:
|
|
281
|
-
// The user cancelled, the system cancelled, or some error occurred.
|
|
282
|
-
// If the user cancelled, errorMessage is the text of the "negative" button,
|
|
283
|
-
// which is not especially descriptive.
|
|
300
|
+
case SUCCESS -> call.resolve();
|
|
301
|
+
// Biometry was successfully presented but was not recognized
|
|
302
|
+
case FAILURE -> call.reject(errorMessage, BIOMETRIC_FAILURE);
|
|
303
|
+
// The user cancelled, the system cancelled, or some error occurred.
|
|
304
|
+
// If the user cancelled, errorMessage is the text of the "negative" button,
|
|
305
|
+
// which is not especially descriptive.
|
|
306
|
+
case ERROR -> {
|
|
284
307
|
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
|
|
285
308
|
errorMessage = "Cancel button was pressed";
|
|
286
309
|
}
|
|
287
310
|
|
|
288
311
|
call.reject(errorMessage, biometryErrorCodeMap.get(errorCode));
|
|
289
|
-
|
|
312
|
+
}
|
|
290
313
|
}
|
|
291
314
|
}
|
|
292
315
|
|
|
@@ -100,6 +100,14 @@ export interface AuthenticateOptions {
|
|
|
100
100
|
* Subtitle for the Android dialog. If not supplied, the system default is used.
|
|
101
101
|
*/
|
|
102
102
|
androidSubtitle?: string;
|
|
103
|
+
/**
|
|
104
|
+
* For information on this setting, see:
|
|
105
|
+
*
|
|
106
|
+
* https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder#setConfirmationRequired(boolean)
|
|
107
|
+
*
|
|
108
|
+
* If not set, defaults to true.
|
|
109
|
+
*/
|
|
110
|
+
androidConfirmationRequired?: boolean;
|
|
103
111
|
}
|
|
104
112
|
/**
|
|
105
113
|
* If the `authenticate()` method throws an exception, the error object
|
package/ios/Plugin/Plugin.swift
CHANGED
|
@@ -22,12 +22,32 @@ public class BiometricAuthNative: CAPPlugin {
|
|
|
22
22
|
LAError.biometryNotEnrolled.rawValue: "biometryNotEnrolled"
|
|
23
23
|
]
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
struct CheckDeviceBiometryResult {
|
|
26
|
+
let isAvailable: Bool
|
|
27
|
+
let biometryType: LABiometryType.RawValue
|
|
28
|
+
let biometryTypes: JSArray
|
|
29
|
+
let reason: String
|
|
30
|
+
let code: String
|
|
31
|
+
}
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
|
-
*
|
|
34
|
+
* Plugin call checkBiometry()
|
|
29
35
|
*/
|
|
30
36
|
@objc func checkBiometry(_ call: CAPPluginCall) {
|
|
37
|
+
let checkResult = checkDeviceBiometry()
|
|
38
|
+
call.resolve([
|
|
39
|
+
"isAvailable": checkResult.isAvailable,
|
|
40
|
+
"biometryType": checkResult.biometryType,
|
|
41
|
+
"biometryTypes": checkResult.biometryTypes,
|
|
42
|
+
"reason": checkResult.reason,
|
|
43
|
+
"code": checkResult.code
|
|
44
|
+
])
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check the device's availability and type of biometric authentication.
|
|
49
|
+
*/
|
|
50
|
+
func checkDeviceBiometry() -> CheckDeviceBiometryResult {
|
|
31
51
|
let context = LAContext()
|
|
32
52
|
var error: NSError?
|
|
33
53
|
var available = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
|
|
@@ -42,7 +62,6 @@ public class BiometricAuthNative: CAPPlugin {
|
|
|
42
62
|
|
|
43
63
|
if entry == nil {
|
|
44
64
|
available = false
|
|
45
|
-
canEvaluatePolicy = false
|
|
46
65
|
reason = kMissingFaceIDUsageEntry
|
|
47
66
|
errorCode = biometryErrorCodeMap[LAError.biometryNotAvailable.rawValue] ?? ""
|
|
48
67
|
}
|
|
@@ -61,13 +80,13 @@ public class BiometricAuthNative: CAPPlugin {
|
|
|
61
80
|
var types = JSArray()
|
|
62
81
|
types.append(context.biometryType.rawValue)
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
return CheckDeviceBiometryResult(
|
|
84
|
+
isAvailable: available,
|
|
85
|
+
biometryType: context.biometryType.rawValue,
|
|
86
|
+
biometryTypes: types,
|
|
87
|
+
reason: reason,
|
|
88
|
+
code: errorCode
|
|
89
|
+
)
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
/**
|
|
@@ -79,9 +98,11 @@ public class BiometricAuthNative: CAPPlugin {
|
|
|
79
98
|
*/
|
|
80
99
|
@objc func authenticate(_ call: CAPPluginCall) {
|
|
81
100
|
// Make sure the app can evaluate policy, otherwise evaluatePolicy() will crash
|
|
82
|
-
|
|
101
|
+
let checkResult = checkDeviceBiometry()
|
|
102
|
+
|
|
103
|
+
guard checkResult.isAvailable else {
|
|
83
104
|
call.reject(
|
|
84
|
-
|
|
105
|
+
checkResult.reason,
|
|
85
106
|
biometryErrorCodeMap[LAError.biometryNotAvailable.rawValue]
|
|
86
107
|
)
|
|
87
108
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aparajita/capacitor-biometric-auth",
|
|
3
|
-
"version": "5.1
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "Provides access to the native biometric auth APIs for Capacitor apps",
|
|
5
5
|
"author": "Aparajita Fishman",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,21 +56,21 @@
|
|
|
56
56
|
"@aparajita/eslint-config-base": "^1.1.6",
|
|
57
57
|
"@aparajita/prettier-config": "^2.0.0",
|
|
58
58
|
"@aparajita/swiftly": "^1.0.4",
|
|
59
|
-
"@capacitor/cli": "^5.4.
|
|
59
|
+
"@capacitor/cli": "^5.4.2",
|
|
60
60
|
"@commitlint/cli": "^17.7.2",
|
|
61
61
|
"@commitlint/config-conventional": "^17.7.0",
|
|
62
62
|
"@ionic/swiftlint-config": "^1.1.2",
|
|
63
|
-
"@rollup/plugin-json": "^6.0.
|
|
64
|
-
"@types/node": "^20.8.
|
|
65
|
-
"@typescript-eslint/eslint-plugin": "^6.7.
|
|
66
|
-
"@typescript-eslint/parser": "^6.7.
|
|
67
|
-
"commit-and-tag-version": "^11.
|
|
68
|
-
"eslint": "^8.
|
|
63
|
+
"@rollup/plugin-json": "^6.0.1",
|
|
64
|
+
"@types/node": "^20.8.4",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
|
66
|
+
"@typescript-eslint/parser": "^6.7.5",
|
|
67
|
+
"commit-and-tag-version": "^11.3.0",
|
|
68
|
+
"eslint": "^8.51.0",
|
|
69
69
|
"eslint-config-prettier": "^9.0.0",
|
|
70
70
|
"eslint-config-standard": "^17.1.0",
|
|
71
71
|
"eslint-import-resolver-typescript": "^3.6.1",
|
|
72
72
|
"eslint-plugin-import": "^2.28.1",
|
|
73
|
-
"eslint-plugin-n": "^16.
|
|
73
|
+
"eslint-plugin-n": "^16.2.0",
|
|
74
74
|
"eslint-plugin-promise": "^6.1.1",
|
|
75
75
|
"nodemon": "^3.0.1",
|
|
76
76
|
"prettier": "^3.0.3",
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
"typescript": "~5.2.2"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@capacitor/android": "^5.4.
|
|
84
|
+
"@capacitor/android": "^5.4.2",
|
|
85
85
|
"@capacitor/app": "^5.0.6",
|
|
86
|
-
"@capacitor/core": "^5.4.
|
|
87
|
-
"@capacitor/ios": "^5.4.
|
|
86
|
+
"@capacitor/core": "^5.4.2",
|
|
87
|
+
"@capacitor/ios": "^5.4.2"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
90
|
"clean": "rimraf dist",
|