expo-local-authentication 13.3.0 → 13.4.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/CHANGELOG.md
CHANGED
|
@@ -10,7 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
-
## 13.
|
|
13
|
+
## 13.4.1 — 2023-06-13
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [Android] Fixed device credentials fallback when biometric sensors are unavailable ([#22388](https://github.com/expo/expo/pull/22388) by [@hubastard](https://github.com/hubastard))
|
|
18
|
+
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
|
|
19
|
+
|
|
20
|
+
## 13.4.0 — 2023-05-08
|
|
21
|
+
|
|
22
|
+
_This version does not introduce any user-facing changes._
|
|
23
|
+
|
|
24
|
+
## 13.3.0 - 2023-04-10
|
|
14
25
|
|
|
15
26
|
### 🐛 Bug fixes
|
|
16
27
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Provides an API for FaceID and TouchID (iOS) or the Fingerprint API (Android) to
|
|
|
16
16
|
|
|
17
17
|
# Installation in managed Expo projects
|
|
18
18
|
|
|
19
|
-
For [managed](https://docs.expo.dev/
|
|
19
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/local-authentication/).
|
|
20
20
|
|
|
21
21
|
# Installation in bare React Native projects
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ For bare React Native projects, you must ensure that you have [installed and con
|
|
|
25
25
|
### Add the package to your npm dependencies
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
expo install expo-local-authentication
|
|
28
|
+
npx expo install expo-local-authentication
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
### Configure for iOS
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '13.
|
|
6
|
+
version = '13.4.1'
|
|
7
7
|
|
|
8
8
|
buildscript {
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
@@ -35,19 +35,11 @@ buildscript {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Creating sources with comments
|
|
39
|
-
task androidSourcesJar(type: Jar) {
|
|
40
|
-
classifier = 'sources'
|
|
41
|
-
from android.sourceSets.main.java.srcDirs
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
afterEvaluate {
|
|
45
39
|
publishing {
|
|
46
40
|
publications {
|
|
47
41
|
release(MavenPublication) {
|
|
48
42
|
from components.release
|
|
49
|
-
// Add additional sourcesJar to artifacts
|
|
50
|
-
artifact(androidSourcesJar)
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
45
|
repositories {
|
|
@@ -70,15 +62,21 @@ android {
|
|
|
70
62
|
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
|
71
63
|
}
|
|
72
64
|
|
|
65
|
+
namespace "expo.modules.localauthentication"
|
|
73
66
|
defaultConfig {
|
|
74
67
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
75
68
|
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
76
69
|
versionCode 30
|
|
77
|
-
versionName "13.
|
|
70
|
+
versionName "13.4.1"
|
|
78
71
|
}
|
|
79
72
|
lintOptions {
|
|
80
73
|
abortOnError false
|
|
81
74
|
}
|
|
75
|
+
publishing {
|
|
76
|
+
singleVariant("release") {
|
|
77
|
+
withSourcesJar()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
dependencies {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
2
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
|
3
3
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
|
4
4
|
</manifest>
|
|
@@ -30,10 +30,13 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
|
|
|
30
30
|
private val SECURITY_LEVEL_NONE = 0
|
|
31
31
|
private val SECURITY_LEVEL_SECRET = 1
|
|
32
32
|
private val SECURITY_LEVEL_BIOMETRIC = 2
|
|
33
|
+
private val DEVICE_CREDENTIAL_FALLBACK_CODE = 6
|
|
33
34
|
private val biometricManager = BiometricManager.from(context)
|
|
34
35
|
private val packageManager = context.packageManager
|
|
35
36
|
private var biometricPrompt: BiometricPrompt? = null
|
|
36
37
|
private var promise: Promise? = null
|
|
38
|
+
private var authOptions: Map<String?, Any?>? = null
|
|
39
|
+
private var isRetryingWithDeviceCredentials = false
|
|
37
40
|
private var isAuthenticating = false
|
|
38
41
|
private val moduleRegistryDelegate: ModuleRegistryDelegate = ModuleRegistryDelegate()
|
|
39
42
|
private val uIManager: UIManager by moduleRegistry()
|
|
@@ -52,9 +55,21 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
|
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
private fun isBiometricUnavailable(code: Int): Boolean {
|
|
59
|
+
return when (code) {
|
|
60
|
+
BiometricPrompt.ERROR_HW_NOT_PRESENT,
|
|
61
|
+
BiometricPrompt.ERROR_HW_UNAVAILABLE,
|
|
62
|
+
BiometricPrompt.ERROR_NO_BIOMETRICS,
|
|
63
|
+
BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
|
|
64
|
+
BiometricPrompt.ERROR_NO_SPACE -> true
|
|
65
|
+
else -> false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
private val authenticationCallback: BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
|
|
56
70
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
|
57
71
|
isAuthenticating = false
|
|
72
|
+
isRetryingWithDeviceCredentials = false
|
|
58
73
|
biometricPrompt = null
|
|
59
74
|
promise?.resolve(
|
|
60
75
|
Bundle().apply {
|
|
@@ -62,10 +77,30 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
|
|
|
62
77
|
}
|
|
63
78
|
)
|
|
64
79
|
promise = null
|
|
80
|
+
authOptions = null
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
override fun onAuthenticationError(errMsgId: Int, errString: CharSequence) {
|
|
84
|
+
// Make sure to fallback to the Device Credentials if the Biometrics hardware is unavailable.
|
|
85
|
+
if (isBiometricUnavailable(errMsgId) && isDeviceSecure && !isRetryingWithDeviceCredentials) {
|
|
86
|
+
val options = authOptions
|
|
87
|
+
|
|
88
|
+
if (options != null) {
|
|
89
|
+
val disableDeviceFallback = options["disableDeviceFallback"] as Boolean?
|
|
90
|
+
|
|
91
|
+
// Don't run the device credentials fallback if it's disabled.
|
|
92
|
+
if (disableDeviceFallback != true) {
|
|
93
|
+
promise?.let {
|
|
94
|
+
isRetryingWithDeviceCredentials = true
|
|
95
|
+
promptDeviceCredentialsFallback(options, it)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
68
102
|
isAuthenticating = false
|
|
103
|
+
isRetryingWithDeviceCredentials = false
|
|
69
104
|
biometricPrompt = null
|
|
70
105
|
promise?.resolve(
|
|
71
106
|
Bundle().apply {
|
|
@@ -75,6 +110,7 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
|
|
|
75
110
|
}
|
|
76
111
|
)
|
|
77
112
|
promise = null
|
|
113
|
+
authOptions = null
|
|
78
114
|
}
|
|
79
115
|
}
|
|
80
116
|
|
|
@@ -177,6 +213,8 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
|
|
|
177
213
|
return
|
|
178
214
|
}
|
|
179
215
|
|
|
216
|
+
this.authOptions = options
|
|
217
|
+
|
|
180
218
|
// BiometricPrompt callbacks are invoked on the main thread so also run this there to avoid
|
|
181
219
|
// having to do locking.
|
|
182
220
|
uIManager.runOnUiQueueThread(
|
|
@@ -245,11 +283,85 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
|
|
|
245
283
|
}
|
|
246
284
|
}
|
|
247
285
|
|
|
286
|
+
fun promptDeviceCredentialsFallback(options: Map<String?, Any?>, promise: Promise) {
|
|
287
|
+
val fragmentActivity = currentActivity as FragmentActivity?
|
|
288
|
+
if (fragmentActivity == null) {
|
|
289
|
+
promise.resolve(
|
|
290
|
+
Bundle().apply {
|
|
291
|
+
putBoolean("success", false)
|
|
292
|
+
putString("error", "not_available")
|
|
293
|
+
putString("warning", "getCurrentActivity() returned null")
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
val promptMessage = options["promptMessage"] as? String ?: ""
|
|
300
|
+
val requireConfirmation = options["requireConfirmation"] as? Boolean ?: true
|
|
301
|
+
|
|
302
|
+
// BiometricPrompt callbacks are invoked on the main thread so also run this there to avoid
|
|
303
|
+
// having to do locking.
|
|
304
|
+
uIManager.runOnUiQueueThread(
|
|
305
|
+
Runnable {
|
|
306
|
+
// On Android devices older than 11, we need to use Keyguard to unlock by Device Credentials.
|
|
307
|
+
if (Build.VERSION.SDK_INT < 30) {
|
|
308
|
+
val credentialConfirmationIntent = keyguardManager.createConfirmDeviceCredentialIntent(promptMessage, "")
|
|
309
|
+
fragmentActivity.startActivityForResult(credentialConfirmationIntent, DEVICE_CREDENTIAL_FALLBACK_CODE)
|
|
310
|
+
return@Runnable
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
val executor: Executor = Executors.newSingleThreadExecutor()
|
|
314
|
+
val localBiometricPrompt = BiometricPrompt(fragmentActivity, executor, authenticationCallback)
|
|
315
|
+
if (localBiometricPrompt == null) {
|
|
316
|
+
promise.reject("E_INTERNAL_ERRROR", "Canceled authentication due to an internal error")
|
|
317
|
+
return@Runnable
|
|
318
|
+
}
|
|
319
|
+
biometricPrompt = localBiometricPrompt
|
|
320
|
+
|
|
321
|
+
val promptInfoBuilder = PromptInfo.Builder()
|
|
322
|
+
promptMessage?.let {
|
|
323
|
+
promptInfoBuilder.setTitle(it)
|
|
324
|
+
}
|
|
325
|
+
promptInfoBuilder.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
|
|
326
|
+
promptInfoBuilder.setConfirmationRequired(requireConfirmation)
|
|
327
|
+
val promptInfo = promptInfoBuilder.build()
|
|
328
|
+
try {
|
|
329
|
+
localBiometricPrompt.authenticate(promptInfo)
|
|
330
|
+
} catch (ex: NullPointerException) {
|
|
331
|
+
promise.reject("E_INTERNAL_ERRROR", "Canceled authentication due to an internal error")
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
248
337
|
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
338
|
+
// When Biometric is unavailable and using Keyguard fallback, the result will be handled here.
|
|
339
|
+
if (requestCode == DEVICE_CREDENTIAL_FALLBACK_CODE) {
|
|
340
|
+
if (resultCode == Activity.RESULT_OK) {
|
|
341
|
+
promise?.resolve(
|
|
342
|
+
Bundle().apply {
|
|
343
|
+
putBoolean("success", true)
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
} else {
|
|
347
|
+
promise?.resolve(
|
|
348
|
+
Bundle().apply {
|
|
349
|
+
putBoolean("success", false)
|
|
350
|
+
putString("error", "user_cancel")
|
|
351
|
+
putString("warning", "Device Credentials canceled")
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
isAuthenticating = false
|
|
357
|
+
isRetryingWithDeviceCredentials = false
|
|
358
|
+
biometricPrompt = null
|
|
359
|
+
promise = null
|
|
360
|
+
authOptions = null
|
|
361
|
+
} else if (activity is FragmentActivity) {
|
|
362
|
+
// If the user uses PIN as an authentication method, the result will be passed to the `onActivityResult`.
|
|
363
|
+
// Unfortunately, react-native doesn't pass this value to the underlying fragment - we won't resolve the promise.
|
|
364
|
+
// So we need to do it manually.
|
|
253
365
|
val fragment = activity.supportFragmentManager.findFragmentByTag("androidx.biometric.BiometricFragment")
|
|
254
366
|
fragment?.onActivityResult(requestCode and 0xffff, resultCode, data)
|
|
255
367
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-local-authentication",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.4.1",
|
|
4
4
|
"description": "Provides an API for FaceID and TouchID (iOS) or the Fingerprint API (Android) to authenticate the user with a face or fingerprint scan.",
|
|
5
5
|
"main": "build/LocalAuthentication.js",
|
|
6
6
|
"types": "build/LocalAuthentication.d.ts",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"expo": "*"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "3ccd2edee9cbfed217557675cb50f0ba5e55a9e4"
|
|
50
50
|
}
|