@capgo/capacitor-native-biometric 7.1.12 → 7.1.14
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/Package.swift +28 -0
- package/README.md +0 -20
- package/android/src/main/java/ee/forgr/biometric/AuthActivity.java +141 -166
- package/android/src/main/java/ee/forgr/biometric/NativeBiometric.java +351 -443
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js +10 -10
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +10 -10
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +10 -10
- package/dist/plugin.js.map +1 -1
- package/ios/{Plugin/Plugin.swift → Sources/CapgoNativeBiometricPlugin/CapgoNativeBiometricPlugin.swift} +3 -3
- package/ios/Tests/CapgoNativeBiometricPluginTests/CapgoNativeBiometricPluginTests.swift +13 -0
- package/package.json +20 -19
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/android/gradle.properties +0 -20
- package/android/gradlew +0 -252
- package/android/gradlew.bat +0 -94
- package/android/proguard-rules.pro +0 -21
- package/android/settings.gradle +0 -2
- package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +0 -27
- package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +0 -18
- package/ios/Plugin/Info.plist +0 -24
- package/ios/Plugin.xcodeproj/project.pbxproj +0 -546
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/PluginTests/Info.plist +0 -22
- package/ios/PluginTests/PluginTests.swift +0 -25
- package/ios/Podfile +0 -16
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapgoCapacitorNativeBiometric",
|
|
6
|
+
platforms: [.iOS(.v14)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapgoCapacitorNativeBiometric",
|
|
10
|
+
targets: ["CapgoNativeBiometricPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "CapgoNativeBiometricPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/CapgoNativeBiometricPlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "CapgoNativeBiometricPluginTests",
|
|
25
|
+
dependencies: ["CapgoNativeBiometricPlugin"],
|
|
26
|
+
path: "ios/Tests/CapgoNativeBiometricPluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
CHANGED
|
@@ -278,26 +278,6 @@ To use android's BiometricPrompt api you must add the following permission to yo
|
|
|
278
278
|
<uses-permission android:name="android.permission.USE_BIOMETRIC">
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
-
And register the plugin by adding it to you MainActivity's onCreate (Not needed for Capacitor 3):
|
|
282
|
-
|
|
283
|
-
```java
|
|
284
|
-
import ee.forgr.biometric.NativeBiometric;
|
|
285
|
-
|
|
286
|
-
public class MainActivity extends BridgeActivity {
|
|
287
|
-
@Override
|
|
288
|
-
public void onCreate(Bundle savedInstanceState) {
|
|
289
|
-
super.onCreate(savedInstanceState);
|
|
290
|
-
|
|
291
|
-
// Initializes the Bridge
|
|
292
|
-
this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
|
|
293
|
-
// Additional plugins you've installed go here
|
|
294
|
-
// Ex: add(TotallyAwesomePlugin.class);
|
|
295
|
-
add(NativeBiometric.class);
|
|
296
|
-
}});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
```
|
|
300
|
-
|
|
301
281
|
## Contributors
|
|
302
282
|
|
|
303
283
|
[Jonthia](https://github.com/jonthia)
|
|
@@ -14,185 +14,160 @@ import java.util.concurrent.Executor;
|
|
|
14
14
|
|
|
15
15
|
public class AuthActivity extends AppCompatActivity {
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
17
|
+
private BiometricPrompt biometricPrompt;
|
|
18
|
+
private int maxAttempts;
|
|
19
|
+
private int counter = 0;
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
23
|
+
super.onCreate(savedInstanceState);
|
|
24
|
+
setContentView(R.layout.activity_auth_acitivy);
|
|
25
|
+
|
|
26
|
+
maxAttempts = getIntent().getIntExtra("maxAttempts", 1);
|
|
27
|
+
|
|
28
|
+
Executor executor;
|
|
29
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
30
|
+
executor = this.getMainExecutor();
|
|
31
|
+
} else {
|
|
32
|
+
executor = new Executor() {
|
|
33
|
+
@Override
|
|
34
|
+
public void execute(Runnable command) {
|
|
35
|
+
new Handler().post(command);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
36
38
|
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
BiometricPrompt.PromptInfo.Builder builder =
|
|
41
|
-
new BiometricPrompt.PromptInfo.Builder()
|
|
42
|
-
.setTitle(
|
|
43
|
-
getIntent().hasExtra("title")
|
|
44
|
-
? Objects.requireNonNull(getIntent().getStringExtra("title"))
|
|
45
|
-
: "Authenticate"
|
|
46
|
-
)
|
|
47
|
-
.setSubtitle(
|
|
48
|
-
getIntent().hasExtra("subtitle")
|
|
49
|
-
? getIntent().getStringExtra("subtitle")
|
|
50
|
-
: null
|
|
51
|
-
)
|
|
52
|
-
.setDescription(
|
|
53
|
-
getIntent().hasExtra("description")
|
|
54
|
-
? getIntent().getStringExtra("description")
|
|
55
|
-
: null
|
|
56
|
-
);
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
|
|
40
|
+
BiometricPrompt.PromptInfo.Builder builder = new BiometricPrompt.PromptInfo.Builder()
|
|
41
|
+
.setTitle(getIntent().hasExtra("title") ? Objects.requireNonNull(getIntent().getStringExtra("title")) : "Authenticate")
|
|
42
|
+
.setSubtitle(getIntent().hasExtra("subtitle") ? getIntent().getStringExtra("subtitle") : null)
|
|
43
|
+
.setDescription(getIntent().hasExtra("description") ? getIntent().getStringExtra("description") : null);
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
|
|
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);
|
|
45
|
+
boolean useFallback = getIntent().getBooleanExtra("useFallback", false);
|
|
46
|
+
int[] allowedTypes = getIntent().getIntArrayExtra("allowedBiometryTypes");
|
|
70
47
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
negativeText != null ? negativeText : "Cancel"
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
BiometricPrompt.PromptInfo promptInfo = builder.build();
|
|
79
|
-
|
|
80
|
-
biometricPrompt = new BiometricPrompt(
|
|
81
|
-
this,
|
|
82
|
-
executor,
|
|
83
|
-
new BiometricPrompt.AuthenticationCallback() {
|
|
84
|
-
@Override
|
|
85
|
-
public void onAuthenticationError(
|
|
86
|
-
int errorCode,
|
|
87
|
-
@NonNull CharSequence errString
|
|
88
|
-
) {
|
|
89
|
-
super.onAuthenticationError(errorCode, errString);
|
|
90
|
-
// Handle lockout cases explicitly
|
|
91
|
-
if (
|
|
92
|
-
errorCode == BiometricPrompt.ERROR_LOCKOUT ||
|
|
93
|
-
errorCode == BiometricPrompt.ERROR_LOCKOUT_PERMANENT
|
|
94
|
-
) {
|
|
95
|
-
int pluginErrorCode = convertToPluginErrorCode(errorCode);
|
|
96
|
-
finishActivity("error", pluginErrorCode, errString.toString());
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
int pluginErrorCode = convertToPluginErrorCode(errorCode);
|
|
100
|
-
finishActivity("error", pluginErrorCode, errString.toString());
|
|
48
|
+
int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
49
|
+
if (useFallback) {
|
|
50
|
+
authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
|
101
51
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@NonNull BiometricPrompt.AuthenticationResult result
|
|
106
|
-
) {
|
|
107
|
-
super.onAuthenticationSucceeded(result);
|
|
108
|
-
finishActivity();
|
|
52
|
+
if (allowedTypes != null) {
|
|
53
|
+
// Filter authenticators based on allowed types
|
|
54
|
+
authenticators = getAllowedAuthenticators(allowedTypes);
|
|
109
55
|
}
|
|
56
|
+
builder.setAllowedAuthenticators(authenticators);
|
|
110
57
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
counter++;
|
|
115
|
-
if (counter >= maxAttempts) {
|
|
116
|
-
biometricPrompt.cancelAuthentication();
|
|
117
|
-
// Use error code 4 for too many attempts to match iOS behavior
|
|
118
|
-
finishActivity("error", 4, "Too many failed attempts");
|
|
119
|
-
}
|
|
58
|
+
if (!useFallback) {
|
|
59
|
+
String negativeText = getIntent().getStringExtra("negativeButtonText");
|
|
60
|
+
builder.setNegativeButtonText(negativeText != null ? negativeText : "Cancel");
|
|
120
61
|
}
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
62
|
|
|
124
|
-
|
|
125
|
-
|
|
63
|
+
BiometricPrompt.PromptInfo promptInfo = builder.build();
|
|
64
|
+
|
|
65
|
+
biometricPrompt = new BiometricPrompt(
|
|
66
|
+
this,
|
|
67
|
+
executor,
|
|
68
|
+
new BiometricPrompt.AuthenticationCallback() {
|
|
69
|
+
@Override
|
|
70
|
+
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
|
71
|
+
super.onAuthenticationError(errorCode, errString);
|
|
72
|
+
// Handle lockout cases explicitly
|
|
73
|
+
if (errorCode == BiometricPrompt.ERROR_LOCKOUT || errorCode == BiometricPrompt.ERROR_LOCKOUT_PERMANENT) {
|
|
74
|
+
int pluginErrorCode = convertToPluginErrorCode(errorCode);
|
|
75
|
+
finishActivity("error", pluginErrorCode, errString.toString());
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
int pluginErrorCode = convertToPluginErrorCode(errorCode);
|
|
79
|
+
finishActivity("error", pluginErrorCode, errString.toString());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Override
|
|
83
|
+
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
|
84
|
+
super.onAuthenticationSucceeded(result);
|
|
85
|
+
finishActivity();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Override
|
|
89
|
+
public void onAuthenticationFailed() {
|
|
90
|
+
super.onAuthenticationFailed();
|
|
91
|
+
counter++;
|
|
92
|
+
if (counter >= maxAttempts) {
|
|
93
|
+
biometricPrompt.cancelAuthentication();
|
|
94
|
+
// Use error code 4 for too many attempts to match iOS behavior
|
|
95
|
+
finishActivity("error", 4, "Too many failed attempts");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
);
|
|
126
100
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
101
|
+
biometricPrompt.authenticate(promptInfo);
|
|
102
|
+
}
|
|
130
103
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
intent.putExtra("result", result);
|
|
134
|
-
if (errorCode != null) {
|
|
135
|
-
intent.putExtra("errorCode", String.valueOf(errorCode));
|
|
104
|
+
void finishActivity() {
|
|
105
|
+
finishActivity("success", null, null);
|
|
136
106
|
}
|
|
137
|
-
|
|
138
|
-
|
|
107
|
+
|
|
108
|
+
void finishActivity(String result, Integer errorCode, String errorDetails) {
|
|
109
|
+
Intent intent = new Intent();
|
|
110
|
+
intent.putExtra("result", result);
|
|
111
|
+
if (errorCode != null) {
|
|
112
|
+
intent.putExtra("errorCode", String.valueOf(errorCode));
|
|
113
|
+
}
|
|
114
|
+
if (errorDetails != null) {
|
|
115
|
+
intent.putExtra("errorDetails", errorDetails);
|
|
116
|
+
}
|
|
117
|
+
setResult(RESULT_OK, intent);
|
|
118
|
+
finish();
|
|
139
119
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
default:
|
|
175
|
-
return 0;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Convert Auth Error Codes to plugin expected Biometric Auth Errors (in README.md)
|
|
123
|
+
* This way both iOS and Android return the same error codes for the same authentication failure reasons.
|
|
124
|
+
* !!IMPORTANT!!: Whenever this is modified, check if similar function in iOS Plugin.swift needs to be modified as well
|
|
125
|
+
* @see <a href="https://developer.android.com/reference/androidx/biometric/BiometricPrompt#constants">...</a>
|
|
126
|
+
* @return BiometricAuthError
|
|
127
|
+
*/
|
|
128
|
+
public static int convertToPluginErrorCode(int errorCode) {
|
|
129
|
+
switch (errorCode) {
|
|
130
|
+
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
|
|
131
|
+
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
|
|
132
|
+
return 1;
|
|
133
|
+
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
|
|
134
|
+
return 2; // Permanent lockout
|
|
135
|
+
case BiometricPrompt.ERROR_NO_BIOMETRICS:
|
|
136
|
+
return 3;
|
|
137
|
+
case BiometricPrompt.ERROR_LOCKOUT:
|
|
138
|
+
return 4; // Temporary lockout (too many attempts)
|
|
139
|
+
// Authentication Failure (10) Handled by `onAuthenticationFailed`.
|
|
140
|
+
// App Cancel (11), Invalid Context (12), and Not Interactive (13) are not valid error codes for Android.
|
|
141
|
+
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
|
|
142
|
+
return 14;
|
|
143
|
+
case BiometricPrompt.ERROR_TIMEOUT:
|
|
144
|
+
case BiometricPrompt.ERROR_CANCELED:
|
|
145
|
+
return 15;
|
|
146
|
+
case BiometricPrompt.ERROR_USER_CANCELED:
|
|
147
|
+
case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
|
|
148
|
+
return 16;
|
|
149
|
+
case BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC:
|
|
150
|
+
return 0; // Success case, should not be handled here
|
|
151
|
+
default:
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
176
154
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
155
|
+
|
|
156
|
+
private int getAllowedAuthenticators(int[] allowedTypes) {
|
|
157
|
+
int authenticators = 0;
|
|
158
|
+
for (int type : allowedTypes) {
|
|
159
|
+
switch (type) {
|
|
160
|
+
case 3: // FINGERPRINT
|
|
161
|
+
authenticators |= BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
162
|
+
break;
|
|
163
|
+
case 4: // FACE_AUTHENTICATION
|
|
164
|
+
authenticators |= BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
165
|
+
break;
|
|
166
|
+
case 5: // IRIS_AUTHENTICATION
|
|
167
|
+
authenticators |= BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return authenticators > 0 ? authenticators : BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
193
172
|
}
|
|
194
|
-
return authenticators > 0
|
|
195
|
-
? authenticators
|
|
196
|
-
: BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
197
|
-
}
|
|
198
173
|
}
|