🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

expo-local-authentication

Package Overview
Dependencies
Maintainers
28
Versions
220
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

expo-local-authentication - npm Package Compare versions

Comparing version
11.1.1
to
12.0.0
+288
android/src/main/j...entication/LocalAuthenticationModule.kt
// Copyright 2015-present 650 Industries. All rights reserved.
package expo.modules.localauthentication
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
import androidx.fragment.app.FragmentActivity
import expo.modules.core.ExportedModule
import expo.modules.core.ModuleRegistry
import expo.modules.core.ModuleRegistryDelegate
import expo.modules.core.Promise
import expo.modules.core.interfaces.ActivityEventListener
import expo.modules.core.interfaces.ActivityProvider
import expo.modules.core.interfaces.ExpoMethod
import expo.modules.core.interfaces.services.UIManager
import java.util.*
import java.util.concurrent.Executor
import java.util.concurrent.Executors
class LocalAuthenticationModule(context: Context) : ExportedModule(context), ActivityEventListener {
private val AUTHENTICATION_TYPE_FINGERPRINT = 1
private val AUTHENTICATION_TYPE_FACIAL_RECOGNITION = 2
private val AUTHENTICATION_TYPE_IRIS = 3
private val SECURITY_LEVEL_NONE = 0
private val SECURITY_LEVEL_SECRET = 1
private val SECURITY_LEVEL_BIOMETRIC = 2
private val biometricManager = BiometricManager.from(context)
private val packageManager = context.packageManager
private var biometricPrompt: BiometricPrompt? = null
private var promise: Promise? = null
private var isAuthenticating = false
private val moduleRegistryDelegate: ModuleRegistryDelegate = ModuleRegistryDelegate()
private val uIManager: UIManager by moduleRegistry()
private inline fun <reified T> moduleRegistry() = moduleRegistryDelegate.getFromModuleRegistry<T>()
private fun convertErrorCode(code: Int): String {
return when (code) {
BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_NEGATIVE_BUTTON, BiometricPrompt.ERROR_USER_CANCELED -> "user_cancel"
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE, BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> "not_available"
BiometricPrompt.ERROR_LOCKOUT, BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> "lockout"
BiometricPrompt.ERROR_NO_SPACE -> "no_space"
BiometricPrompt.ERROR_TIMEOUT -> "timeout"
BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> "unable_to_process"
else -> "unknown"
}
}
private val authenticationCallback: BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
isAuthenticating = false
biometricPrompt = null
promise?.resolve(
Bundle().apply {
putBoolean("success", true)
}
)
promise = null
}
override fun onAuthenticationError(errMsgId: Int, errString: CharSequence) {
isAuthenticating = false
biometricPrompt = null
promise?.resolve(
Bundle().apply {
putBoolean("success", false)
putString("error", convertErrorCode(errMsgId))
putString("warning", errString.toString())
}
)
promise = null
}
}
override fun getName(): String {
return "ExpoLocalAuthentication"
}
override fun onCreate(moduleRegistry: ModuleRegistry) {
moduleRegistryDelegate.onCreate(moduleRegistry)
uIManager.registerActivityEventListener(this)
}
@ExpoMethod
fun supportedAuthenticationTypesAsync(promise: Promise) {
val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
val results: MutableList<Int> = ArrayList()
if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
promise.resolve(results)
return
}
// note(cedric): replace hardcoded system feature strings with constants from
// PackageManager when dropping support for Android SDK 28
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (packageManager.hasSystemFeature("android.hardware.fingerprint")) {
results.add(AUTHENTICATION_TYPE_FINGERPRINT)
}
}
if (Build.VERSION.SDK_INT >= 29) {
if (packageManager.hasSystemFeature("android.hardware.biometrics.face")) {
results.add(AUTHENTICATION_TYPE_FACIAL_RECOGNITION)
}
if (packageManager.hasSystemFeature("android.hardware.biometrics.iris")) {
results.add(AUTHENTICATION_TYPE_IRIS)
}
}
// check for face recognition support on some samsung devices
if (packageManager.hasSystemFeature("com.samsung.android.bio.face") && !results.contains(AUTHENTICATION_TYPE_FACIAL_RECOGNITION)) {
results.add(AUTHENTICATION_TYPE_FACIAL_RECOGNITION)
}
promise.resolve(results)
}
@ExpoMethod
fun hasHardwareAsync(promise: Promise) {
val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
promise.resolve(result != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE)
}
@ExpoMethod
fun isEnrolledAsync(promise: Promise) {
val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
promise.resolve(result == BiometricManager.BIOMETRIC_SUCCESS)
}
@ExpoMethod
fun getEnrolledLevelAsync(promise: Promise) {
var level = SECURITY_LEVEL_NONE
if (isDeviceSecure) {
level = SECURITY_LEVEL_SECRET
}
val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
if (result == BiometricManager.BIOMETRIC_SUCCESS) {
level = SECURITY_LEVEL_BIOMETRIC
}
promise.resolve(level)
}
@ExpoMethod
fun authenticateAsync(options: Map<String?, Any?>, promise: Promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
promise.reject("E_NOT_SUPPORTED", "Cannot display biometric prompt on android versions below 6.0")
return
}
if (currentActivity == null) {
promise.reject("E_NOT_FOREGROUND", "Cannot display biometric prompt when the app is not in the foreground")
return
}
if (!keyguardManager.isDeviceSecure) {
promise.resolve(
Bundle().apply {
putBoolean("success", false)
putString("error", "not_enrolled")
putString("warning", "KeyguardManager#isDeviceSecure() returned false")
}
)
return
}
val fragmentActivity = currentActivity as FragmentActivity?
if (fragmentActivity == null) {
promise.resolve(
Bundle().apply {
putBoolean("success", false)
putString("error", "not_available")
putString("warning", "getCurrentActivity() returned null")
}
)
return
}
// BiometricPrompt callbacks are invoked on the main thread so also run this there to avoid
// having to do locking.
uIManager.runOnUiQueueThread(
Runnable {
if (isAuthenticating) {
this.promise?.resolve(
Bundle().apply {
putBoolean("success", false)
putString("error", "app_cancel")
}
)
this.promise = promise
return@Runnable
}
val promptMessage = if (options.containsKey("promptMessage")) {
options["promptMessage"] as String?
} else {
""
}
val cancelLabel = if (options.containsKey("cancelLabel")) {
options["cancelLabel"] as String?
} else {
""
}
val disableDeviceFallback = if (options.containsKey("disableDeviceFallback")) {
options["disableDeviceFallback"] as Boolean?
} else {
false
}
isAuthenticating = true
this.promise = promise
val executor: Executor = Executors.newSingleThreadExecutor()
biometricPrompt = BiometricPrompt(fragmentActivity, executor, authenticationCallback)
val promptInfoBuilder = PromptInfo.Builder()
promptMessage?.let {
promptInfoBuilder.setTitle(it)
}
if (disableDeviceFallback == true) {
cancelLabel?.let {
promptInfoBuilder.setNegativeButtonText(it)
}
} else {
promptInfoBuilder.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_WEAK
or BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
}
val promptInfo = promptInfoBuilder.build()
try {
biometricPrompt!!.authenticate(promptInfo)
} catch (ex: NullPointerException) {
promise.reject("E_INTERNAL_ERRROR", "Canceled authentication due to an internal error")
}
}
)
}
@ExpoMethod
fun cancelAuthenticate(promise: Promise) {
uIManager.runOnUiQueueThread {
biometricPrompt?.cancelAuthentication()
isAuthenticating = false
promise.resolve(null)
}
}
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
// If the user uses PIN as an authentication method, the result will be passed to the `onActivityResult`.
// Unfortunately, react-native doesn't pass this value to the underlying fragment - we won't resolve the promise.
// So we need to do it manually.
if (activity is FragmentActivity) {
val fragment = activity.supportFragmentManager.findFragmentByTag("androidx.biometric.BiometricFragment")
fragment?.onActivityResult(requestCode and 0xffff, resultCode, data)
}
}
override fun onNewIntent(intent: Intent) = Unit
// NOTE: `KeyguardManager#isKeyguardSecure()` considers SIM locked state,
// but it will be ignored on falling-back to device credential on biometric authentication.
// That means, setting level to `SECURITY_LEVEL_SECRET` might be misleading for some users.
// But there is no equivalent APIs prior to M.
// `andriodx.biometric.BiometricManager#canAuthenticate(int)` looks like an alternative,
// but specifying `BiometricManager.Authenticators.DEVICE_CREDENTIAL` alone is not
// supported prior to API 30.
// https://developer.android.com/reference/androidx/biometric/BiometricManager#canAuthenticate(int)
private val isDeviceSecure: Boolean
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
keyguardManager.isDeviceSecure
} else {
// NOTE: `KeyguardManager#isKeyguardSecure()` considers SIM locked state,
// but it will be ignored on falling-back to device credential on biometric authentication.
// That means, setting level to `SECURITY_LEVEL_SECRET` might be misleading for some users.
// But there is no equivalent APIs prior to M.
// `andriodx.biometric.BiometricManager#canAuthenticate(int)` looks like an alternative,
// but specifying `BiometricManager.Authenticators.DEVICE_CREDENTIAL` alone is not
// supported prior to API 30.
// https://developer.android.com/reference/androidx/biometric/BiometricManager#canAuthenticate(int)
keyguardManager.isKeyguardSecure
}
private val keyguardManager: KeyguardManager
get() = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
private val currentActivity: Activity?
get() {
val activityProvider: ActivityProvider by moduleRegistry()
return activityProvider.currentActivity
}
}
package expo.modules.localauthentication
import android.content.Context
import expo.modules.core.BasePackage
import expo.modules.core.ExportedModule
class LocalAuthenticationPackage : BasePackage() {
override fun createExportedModules(context: Context): List<ExportedModule> {
return listOf<ExportedModule>(LocalAuthenticationModule(context))
}
}
+7
-11

@@ -6,3 +6,3 @@ apply plugin: 'com.android.library'

group = 'host.exp.exponent'
version = '11.1.1'
version = '12.0.0'

@@ -57,2 +57,6 @@ buildscript {

kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {

@@ -62,3 +66,3 @@ minSdkVersion safeExtGet("minSdkVersion", 21)

versionCode 30
versionName "11.1.1"
versionName "12.0.0"
}

@@ -70,12 +74,4 @@ lintOptions {

if (new File(rootProject.projectDir.parentFile, 'package.json').exists()) {
apply from: project(":unimodules-core").file("../unimodules-core.gradle")
} else {
throw new GradleException(
"'unimodules-core.gradle' was not found in the usual React Native dependency location. " +
"This package can only be used in such projects. Are you sure you've installed the dependencies properly?")
}
dependencies {
unimodule "unimodules-core"
implementation project(':expo-modules-core')

@@ -82,0 +78,0 @@ api "androidx.biometric:biometric:1.1.0"

@@ -1,2 +0,2 @@

declare const _default: import("@unimodules/core").ProxyNativeModule;
declare const _default: import("expo-modules-core").ProxyNativeModule;
export default _default;

@@ -1,3 +0,3 @@

import { NativeModulesProxy } from '@unimodules/core';
import { NativeModulesProxy } from 'expo-modules-core';
export default NativeModulesProxy.ExpoLocalAuthentication;
//# sourceMappingURL=ExpoLocalAuthentication.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"ExpoLocalAuthentication.js","sourceRoot":"","sources":["../src/ExpoLocalAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,eAAe,kBAAkB,CAAC,uBAAuB,CAAC","sourcesContent":["import { NativeModulesProxy } from '@unimodules/core';\n\nexport default NativeModulesProxy.ExpoLocalAuthentication;\n"]}
{"version":3,"file":"ExpoLocalAuthentication.js","sourceRoot":"","sources":["../src/ExpoLocalAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,eAAe,kBAAkB,CAAC,uBAAuB,CAAC","sourcesContent":["import { NativeModulesProxy } from 'expo-modules-core';\n\nexport default NativeModulesProxy.ExpoLocalAuthentication;\n"]}
import { LocalAuthenticationOptions, AuthenticationType, LocalAuthenticationResult, SecurityLevel } from './LocalAuthentication.types';
export { LocalAuthenticationOptions, AuthenticationType, LocalAuthenticationResult, SecurityLevel };
/**
* Determine whether a face or fingerprint scanner is available on the device.
* @return Returns a promise which fulfils with a `boolean` value indicating whether a face or
* fingerprint scanner is available on this device.
*/
export declare function hasHardwareAsync(): Promise<boolean>;
/**
* Determine what kinds of authentications are available on the device.
* @return Returns a promise which fulfils to an array containing [`AuthenticationType`s](#authenticationtype).
*
* Devices can support multiple authentication methods- i.e. `[1,2]` means the device supports both
* fingerprint and facial recognition. If none are supported, this method returns an empty array.
*/
export declare function supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]>;
/**
* Determine whether the device has saved fingerprints or facial data to use for authentication.
* @return Returns a promise which fulfils to `boolean` value indicating whether the device has
* saved fingerprints or facial data for authentication.
*/
export declare function isEnrolledAsync(): Promise<boolean>;
/**
* Determine what kind of authentication is enrolled on the device.
* @return Returns a promise which fulfils with [`SecurityLevel`](#securitylevel).
* > **Note:** On Android devices prior to M, `SECRET` can be returned if only the SIM lock has been
* enrolled, which is not the method that [`authenticateAsync`](#localauthenticationauthenticateasyncoptions)
* prompts.
*/
export declare function getEnrolledLevelAsync(): Promise<SecurityLevel>;
/**
* Attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).
* > **Note:** Apple requires apps which use FaceID to provide a description of why they use this API.
* If you try to use FaceID on an iPhone with FaceID without providing `infoPlist.NSFaceIDUsageDescription`
* in `app.json`, the module will authenticate using device passcode. For more information about
* usage descriptions on iOS, see [Deploying to App Stores](/distribution/app-stores#system-permissions-dialogs-on-ios).
* @param options
* @return Returns a promise which fulfils with [`LocalAuthenticationResult`](#localauthenticationresult).
*/
export declare function authenticateAsync(options?: LocalAuthenticationOptions): Promise<LocalAuthenticationResult>;
/**
* **(Android Only)** Cancels authentication flow.
*/
export declare function cancelAuthenticate(): Promise<void>;

@@ -1,2 +0,2 @@

import { UnavailabilityError } from '@unimodules/core';
import { UnavailabilityError } from 'expo-modules-core';
import invariant from 'invariant';

@@ -6,2 +6,8 @@ import ExpoLocalAuthentication from './ExpoLocalAuthentication';

export { AuthenticationType, SecurityLevel };
// @needsAudit
/**
* Determine whether a face or fingerprint scanner is available on the device.
* @return Returns a promise which fulfils with a `boolean` value indicating whether a face or
* fingerprint scanner is available on this device.
*/
export async function hasHardwareAsync() {

@@ -13,2 +19,10 @@ if (!ExpoLocalAuthentication.hasHardwareAsync) {

}
// @needsAudit
/**
* Determine what kinds of authentications are available on the device.
* @return Returns a promise which fulfils to an array containing [`AuthenticationType`s](#authenticationtype).
*
* Devices can support multiple authentication methods- i.e. `[1,2]` means the device supports both
* fingerprint and facial recognition. If none are supported, this method returns an empty array.
*/
export async function supportedAuthenticationTypesAsync() {

@@ -20,2 +34,8 @@ if (!ExpoLocalAuthentication.supportedAuthenticationTypesAsync) {

}
// @needsAudit
/**
* Determine whether the device has saved fingerprints or facial data to use for authentication.
* @return Returns a promise which fulfils to `boolean` value indicating whether the device has
* saved fingerprints or facial data for authentication.
*/
export async function isEnrolledAsync() {

@@ -27,2 +47,10 @@ if (!ExpoLocalAuthentication.isEnrolledAsync) {

}
// @needsAudit
/**
* Determine what kind of authentication is enrolled on the device.
* @return Returns a promise which fulfils with [`SecurityLevel`](#securitylevel).
* > **Note:** On Android devices prior to M, `SECRET` can be returned if only the SIM lock has been
* enrolled, which is not the method that [`authenticateAsync`](#localauthenticationauthenticateasyncoptions)
* prompts.
*/
export async function getEnrolledLevelAsync() {

@@ -34,2 +62,12 @@ if (!ExpoLocalAuthentication.getEnrolledLevelAsync) {

}
// @needsAudit
/**
* Attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).
* > **Note:** Apple requires apps which use FaceID to provide a description of why they use this API.
* If you try to use FaceID on an iPhone with FaceID without providing `infoPlist.NSFaceIDUsageDescription`
* in `app.json`, the module will authenticate using device passcode. For more information about
* usage descriptions on iOS, see [Deploying to App Stores](/distribution/app-stores#system-permissions-dialogs-on-ios).
* @param options
* @return Returns a promise which fulfils with [`LocalAuthenticationResult`](#localauthenticationresult).
*/
export async function authenticateAsync(options = {}) {

@@ -49,2 +87,6 @@ if (!ExpoLocalAuthentication.authenticateAsync) {

}
// @needsAudit
/**
* **(Android Only)** Cancels authentication flow.
*/
export async function cancelAuthenticate() {

@@ -51,0 +93,0 @@ if (!ExpoLocalAuthentication.cancelAuthenticate) {

@@ -1,1 +0,1 @@

{"version":3,"file":"LocalAuthentication.js","sourceRoot":"","sources":["../src/LocalAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC,OAAO,uBAAuB,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAEL,kBAAkB,EAElB,aAAa,GACd,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAA8B,kBAAkB,EAA6B,aAAa,EAAE,CAAC;AAEpG,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC,uBAAuB,CAAC,gBAAgB,EAAE;QAC7C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,kBAAkB,CAAC,CAAC;KAChF;IACD,OAAO,MAAM,uBAAuB,CAAC,gBAAgB,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iCAAiC;IACrD,IAAI,CAAC,uBAAuB,CAAC,iCAAiC,EAAE;QAC9D,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,mCAAmC,CAAC,CAAC;KACjG;IACD,OAAO,MAAM,uBAAuB,CAAC,iCAAiC,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC,uBAAuB,CAAC,eAAe,EAAE;QAC5C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,iBAAiB,CAAC,CAAC;KAC/E;IACD,OAAO,MAAM,uBAAuB,CAAC,eAAe,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC,uBAAuB,CAAC,qBAAqB,EAAE;QAClD,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,uBAAuB,CAAC,CAAC;KACrF;IACD,OAAO,MAAM,uBAAuB,CAAC,qBAAqB,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAsC,EAAE;IAExC,IAAI,CAAC,uBAAuB,CAAC,iBAAiB,EAAE;QAC9C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,mBAAmB,CAAC,CAAC;KACjF;IAED,IAAI,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE;QAC3C,SAAS,CACP,OAAO,OAAO,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,MAAM,EACzE,6FAA6F,CAC9F,CAAC;KACH;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,cAAc,CAAC;IAC9D,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,iBAAiB,CAAC,EAAE,GAAG,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IAE9F,IAAI,MAAM,CAAC,OAAO,EAAE;QAClB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;KAC9B;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC,uBAAuB,CAAC,kBAAkB,EAAE;QAC/C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,oBAAoB,CAAC,CAAC;KAClF;IACD,MAAM,uBAAuB,CAAC,kBAAkB,EAAE,CAAC;AACrD,CAAC","sourcesContent":["import { UnavailabilityError } from '@unimodules/core';\nimport invariant from 'invariant';\n\nimport ExpoLocalAuthentication from './ExpoLocalAuthentication';\nimport {\n LocalAuthenticationOptions,\n AuthenticationType,\n LocalAuthenticationResult,\n SecurityLevel,\n} from './LocalAuthentication.types';\n\nexport { LocalAuthenticationOptions, AuthenticationType, LocalAuthenticationResult, SecurityLevel };\n\nexport async function hasHardwareAsync(): Promise<boolean> {\n if (!ExpoLocalAuthentication.hasHardwareAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'hasHardwareAsync');\n }\n return await ExpoLocalAuthentication.hasHardwareAsync();\n}\n\nexport async function supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]> {\n if (!ExpoLocalAuthentication.supportedAuthenticationTypesAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'supportedAuthenticationTypesAsync');\n }\n return await ExpoLocalAuthentication.supportedAuthenticationTypesAsync();\n}\n\nexport async function isEnrolledAsync(): Promise<boolean> {\n if (!ExpoLocalAuthentication.isEnrolledAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'isEnrolledAsync');\n }\n return await ExpoLocalAuthentication.isEnrolledAsync();\n}\n\nexport async function getEnrolledLevelAsync(): Promise<SecurityLevel> {\n if (!ExpoLocalAuthentication.getEnrolledLevelAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'getEnrolledLevelAsync');\n }\n return await ExpoLocalAuthentication.getEnrolledLevelAsync();\n}\n\nexport async function authenticateAsync(\n options: LocalAuthenticationOptions = {}\n): Promise<LocalAuthenticationResult> {\n if (!ExpoLocalAuthentication.authenticateAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'authenticateAsync');\n }\n\n if (options.hasOwnProperty('promptMessage')) {\n invariant(\n typeof options.promptMessage === 'string' && options.promptMessage.length,\n 'LocalAuthentication.authenticateAsync : `options.promptMessage` must be a non-empty string.'\n );\n }\n\n const promptMessage = options.promptMessage || 'Authenticate';\n const result = await ExpoLocalAuthentication.authenticateAsync({ ...options, promptMessage });\n\n if (result.warning) {\n console.warn(result.warning);\n }\n return result;\n}\n\nexport async function cancelAuthenticate(): Promise<void> {\n if (!ExpoLocalAuthentication.cancelAuthenticate) {\n throw new UnavailabilityError('expo-local-authentication', 'cancelAuthenticate');\n }\n await ExpoLocalAuthentication.cancelAuthenticate();\n}\n"]}
{"version":3,"file":"LocalAuthentication.js","sourceRoot":"","sources":["../src/LocalAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC,OAAO,uBAAuB,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAEL,kBAAkB,EAElB,aAAa,GACd,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAA8B,kBAAkB,EAA6B,aAAa,EAAE,CAAC;AAEpG,cAAc;AACd;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC,uBAAuB,CAAC,gBAAgB,EAAE;QAC7C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,kBAAkB,CAAC,CAAC;KAChF;IACD,OAAO,MAAM,uBAAuB,CAAC,gBAAgB,EAAE,CAAC;AAC1D,CAAC;AAED,cAAc;AACd;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC;IACrD,IAAI,CAAC,uBAAuB,CAAC,iCAAiC,EAAE;QAC9D,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,mCAAmC,CAAC,CAAC;KACjG;IACD,OAAO,MAAM,uBAAuB,CAAC,iCAAiC,EAAE,CAAC;AAC3E,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC,uBAAuB,CAAC,eAAe,EAAE;QAC5C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,iBAAiB,CAAC,CAAC;KAC/E;IACD,OAAO,MAAM,uBAAuB,CAAC,eAAe,EAAE,CAAC;AACzD,CAAC;AAED,cAAc;AACd;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC,uBAAuB,CAAC,qBAAqB,EAAE;QAClD,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,uBAAuB,CAAC,CAAC;KACrF;IACD,OAAO,MAAM,uBAAuB,CAAC,qBAAqB,EAAE,CAAC;AAC/D,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAsC,EAAE;IAExC,IAAI,CAAC,uBAAuB,CAAC,iBAAiB,EAAE;QAC9C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,mBAAmB,CAAC,CAAC;KACjF;IAED,IAAI,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE;QAC3C,SAAS,CACP,OAAO,OAAO,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,MAAM,EACzE,6FAA6F,CAC9F,CAAC;KACH;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,cAAc,CAAC;IAC9D,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,iBAAiB,CAAC,EAAE,GAAG,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IAE9F,IAAI,MAAM,CAAC,OAAO,EAAE;QAClB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;KAC9B;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,cAAc;AACd;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC,uBAAuB,CAAC,kBAAkB,EAAE;QAC/C,MAAM,IAAI,mBAAmB,CAAC,2BAA2B,EAAE,oBAAoB,CAAC,CAAC;KAClF;IACD,MAAM,uBAAuB,CAAC,kBAAkB,EAAE,CAAC;AACrD,CAAC","sourcesContent":["import { UnavailabilityError } from 'expo-modules-core';\nimport invariant from 'invariant';\n\nimport ExpoLocalAuthentication from './ExpoLocalAuthentication';\nimport {\n LocalAuthenticationOptions,\n AuthenticationType,\n LocalAuthenticationResult,\n SecurityLevel,\n} from './LocalAuthentication.types';\n\nexport { LocalAuthenticationOptions, AuthenticationType, LocalAuthenticationResult, SecurityLevel };\n\n// @needsAudit\n/**\n * Determine whether a face or fingerprint scanner is available on the device.\n * @return Returns a promise which fulfils with a `boolean` value indicating whether a face or\n * fingerprint scanner is available on this device.\n */\nexport async function hasHardwareAsync(): Promise<boolean> {\n if (!ExpoLocalAuthentication.hasHardwareAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'hasHardwareAsync');\n }\n return await ExpoLocalAuthentication.hasHardwareAsync();\n}\n\n// @needsAudit\n/**\n * Determine what kinds of authentications are available on the device.\n * @return Returns a promise which fulfils to an array containing [`AuthenticationType`s](#authenticationtype).\n *\n * Devices can support multiple authentication methods- i.e. `[1,2]` means the device supports both\n * fingerprint and facial recognition. If none are supported, this method returns an empty array.\n */\nexport async function supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]> {\n if (!ExpoLocalAuthentication.supportedAuthenticationTypesAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'supportedAuthenticationTypesAsync');\n }\n return await ExpoLocalAuthentication.supportedAuthenticationTypesAsync();\n}\n\n// @needsAudit\n/**\n * Determine whether the device has saved fingerprints or facial data to use for authentication.\n * @return Returns a promise which fulfils to `boolean` value indicating whether the device has\n * saved fingerprints or facial data for authentication.\n */\nexport async function isEnrolledAsync(): Promise<boolean> {\n if (!ExpoLocalAuthentication.isEnrolledAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'isEnrolledAsync');\n }\n return await ExpoLocalAuthentication.isEnrolledAsync();\n}\n\n// @needsAudit\n/**\n * Determine what kind of authentication is enrolled on the device.\n * @return Returns a promise which fulfils with [`SecurityLevel`](#securitylevel).\n * > **Note:** On Android devices prior to M, `SECRET` can be returned if only the SIM lock has been\n * enrolled, which is not the method that [`authenticateAsync`](#localauthenticationauthenticateasyncoptions)\n * prompts.\n */\nexport async function getEnrolledLevelAsync(): Promise<SecurityLevel> {\n if (!ExpoLocalAuthentication.getEnrolledLevelAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'getEnrolledLevelAsync');\n }\n return await ExpoLocalAuthentication.getEnrolledLevelAsync();\n}\n\n// @needsAudit\n/**\n * Attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).\n * > **Note:** Apple requires apps which use FaceID to provide a description of why they use this API.\n * If you try to use FaceID on an iPhone with FaceID without providing `infoPlist.NSFaceIDUsageDescription`\n * in `app.json`, the module will authenticate using device passcode. For more information about\n * usage descriptions on iOS, see [Deploying to App Stores](/distribution/app-stores#system-permissions-dialogs-on-ios).\n * @param options\n * @return Returns a promise which fulfils with [`LocalAuthenticationResult`](#localauthenticationresult).\n */\nexport async function authenticateAsync(\n options: LocalAuthenticationOptions = {}\n): Promise<LocalAuthenticationResult> {\n if (!ExpoLocalAuthentication.authenticateAsync) {\n throw new UnavailabilityError('expo-local-authentication', 'authenticateAsync');\n }\n\n if (options.hasOwnProperty('promptMessage')) {\n invariant(\n typeof options.promptMessage === 'string' && options.promptMessage.length,\n 'LocalAuthentication.authenticateAsync : `options.promptMessage` must be a non-empty string.'\n );\n }\n\n const promptMessage = options.promptMessage || 'Authenticate';\n const result = await ExpoLocalAuthentication.authenticateAsync({ ...options, promptMessage });\n\n if (result.warning) {\n console.warn(result.warning);\n }\n return result;\n}\n\n// @needsAudit\n/**\n * **(Android Only)** Cancels authentication flow.\n */\nexport async function cancelAuthenticate(): Promise<void> {\n if (!ExpoLocalAuthentication.cancelAuthenticate) {\n throw new UnavailabilityError('expo-local-authentication', 'cancelAuthenticate');\n }\n await ExpoLocalAuthentication.cancelAuthenticate();\n}\n"]}

@@ -6,18 +6,56 @@ export declare type LocalAuthenticationResult = {

error: string;
warning?: string;
};
export declare enum AuthenticationType {
/**
* Indicates fingerprint support.
*/
FINGERPRINT = 1,
/**
* Indicates facial recognition support.
*/
FACIAL_RECOGNITION = 2,
/**
* __Android-only.__ Indicates iris recognition support.
*/
IRIS = 3
}
export declare enum SecurityLevel {
/**
* Indicates no enrolled authentication.
*/
NONE = 0,
/**
* Indicates non-biometric authentication (e.g. PIN, Pattern).
*/
SECRET = 1,
/**
* Indicates biometric authentication.
*/
BIOMETRIC = 2
}
export declare type LocalAuthenticationOptions = {
/**
* A message that is shown alongside the TouchID or FaceID prompt.
*/
promptMessage?: string;
/**
* Allows to customize the default `Cancel` label shown.
*/
cancelLabel?: string;
/**
* After several failed attempts the system will fallback to the device passcode. This setting
* allows you to disable this option and instead handle the fallback yourself. This can be
* preferable in certain custom authentication workflows. This behaviour maps to using the iOS
* [LAPolicyDeviceOwnerAuthenticationWithBiometrics](https://developer.apple.com/documentation/localauthentication/lapolicy/lapolicydeviceownerauthenticationwithbiometrics?language=objc)
* policy rather than the [LAPolicyDeviceOwnerAuthentication](https://developer.apple.com/documentation/localauthentication/lapolicy/lapolicydeviceownerauthentication?language=objc)
* policy. Defaults to `false`.
*/
disableDeviceFallback?: boolean;
/**
* **iOS only.** Allows to customize the default `Use Passcode` label shown after several failed
* authentication attempts. Setting this option to an empty string disables this button from
* showing in the prompt.
*/
fallbackLabel?: string;
};

@@ -0,14 +1,33 @@

// @needsAudit
export var AuthenticationType;
(function (AuthenticationType) {
/**
* Indicates fingerprint support.
*/
AuthenticationType[AuthenticationType["FINGERPRINT"] = 1] = "FINGERPRINT";
/**
* Indicates facial recognition support.
*/
AuthenticationType[AuthenticationType["FACIAL_RECOGNITION"] = 2] = "FACIAL_RECOGNITION";
// Android only
/**
* __Android-only.__ Indicates iris recognition support.
*/
AuthenticationType[AuthenticationType["IRIS"] = 3] = "IRIS";
})(AuthenticationType || (AuthenticationType = {}));
// @needsAudit
export var SecurityLevel;
(function (SecurityLevel) {
/**
* Indicates no enrolled authentication.
*/
SecurityLevel[SecurityLevel["NONE"] = 0] = "NONE";
/**
* Indicates non-biometric authentication (e.g. PIN, Pattern).
*/
SecurityLevel[SecurityLevel["SECRET"] = 1] = "SECRET";
/**
* Indicates biometric authentication.
*/
SecurityLevel[SecurityLevel["BIOMETRIC"] = 2] = "BIOMETRIC";
})(SecurityLevel || (SecurityLevel = {}));
//# sourceMappingURL=LocalAuthentication.types.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"LocalAuthentication.types.js","sourceRoot":"","sources":["../src/LocalAuthentication.types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAN,IAAY,kBAKX;AALD,WAAY,kBAAkB;IAC5B,yEAAe,CAAA;IACf,uFAAsB,CAAA;IACtB,eAAe;IACf,2DAAQ,CAAA;AACV,CAAC,EALW,kBAAkB,KAAlB,kBAAkB,QAK7B;AAED,MAAM,CAAN,IAAY,aAIX;AAJD,WAAY,aAAa;IACvB,iDAAQ,CAAA;IACR,qDAAU,CAAA;IACV,2DAAa,CAAA;AACf,CAAC,EAJW,aAAa,KAAb,aAAa,QAIxB","sourcesContent":["export type LocalAuthenticationResult = { success: true } | { success: false; error: string };\n\nexport enum AuthenticationType {\n FINGERPRINT = 1,\n FACIAL_RECOGNITION = 2,\n // Android only\n IRIS = 3,\n}\n\nexport enum SecurityLevel {\n NONE = 0,\n SECRET = 1,\n BIOMETRIC = 2,\n}\n\nexport type LocalAuthenticationOptions = {\n promptMessage?: string;\n cancelLabel?: string;\n disableDeviceFallback?: boolean;\n // iOS only\n fallbackLabel?: string;\n};\n"]}
{"version":3,"file":"LocalAuthentication.types.js","sourceRoot":"","sources":["../src/LocalAuthentication.types.ts"],"names":[],"mappings":"AAIA,cAAc;AACd,MAAM,CAAN,IAAY,kBAaX;AAbD,WAAY,kBAAkB;IAC5B;;OAEG;IACH,yEAAe,CAAA;IACf;;OAEG;IACH,uFAAsB,CAAA;IACtB;;OAEG;IACH,2DAAQ,CAAA;AACV,CAAC,EAbW,kBAAkB,KAAlB,kBAAkB,QAa7B;AAED,cAAc;AACd,MAAM,CAAN,IAAY,aAaX;AAbD,WAAY,aAAa;IACvB;;OAEG;IACH,iDAAQ,CAAA;IACR;;OAEG;IACH,qDAAU,CAAA;IACV;;OAEG;IACH,2DAAa,CAAA;AACf,CAAC,EAbW,aAAa,KAAb,aAAa,QAaxB","sourcesContent":["export type LocalAuthenticationResult =\n | { success: true }\n | { success: false; error: string; warning?: string };\n\n// @needsAudit\nexport enum AuthenticationType {\n /**\n * Indicates fingerprint support.\n */\n FINGERPRINT = 1,\n /**\n * Indicates facial recognition support.\n */\n FACIAL_RECOGNITION = 2,\n /**\n * __Android-only.__ Indicates iris recognition support.\n */\n IRIS = 3,\n}\n\n// @needsAudit\nexport enum SecurityLevel {\n /**\n * Indicates no enrolled authentication.\n */\n NONE = 0,\n /**\n * Indicates non-biometric authentication (e.g. PIN, Pattern).\n */\n SECRET = 1,\n /**\n * Indicates biometric authentication.\n */\n BIOMETRIC = 2,\n}\n\n// @needsAudit\nexport type LocalAuthenticationOptions = {\n /**\n * A message that is shown alongside the TouchID or FaceID prompt.\n */\n promptMessage?: string;\n /**\n * Allows to customize the default `Cancel` label shown.\n */\n cancelLabel?: string;\n /**\n * After several failed attempts the system will fallback to the device passcode. This setting\n * allows you to disable this option and instead handle the fallback yourself. This can be\n * preferable in certain custom authentication workflows. This behaviour maps to using the iOS\n * [LAPolicyDeviceOwnerAuthenticationWithBiometrics](https://developer.apple.com/documentation/localauthentication/lapolicy/lapolicydeviceownerauthenticationwithbiometrics?language=objc)\n * policy rather than the [LAPolicyDeviceOwnerAuthentication](https://developer.apple.com/documentation/localauthentication/lapolicy/lapolicydeviceownerauthentication?language=objc)\n * policy. Defaults to `false`.\n */\n disableDeviceFallback?: boolean;\n /**\n * **iOS only.** Allows to customize the default `Use Passcode` label shown after several failed\n * authentication attempts. Setting this option to an empty string disables this button from\n * showing in the prompt.\n */\n fallbackLabel?: string;\n};\n"]}

@@ -13,6 +13,19 @@ # Changelog

## 11.1.1 — 2021-06-24
## 12.0.0 — 2021-09-28
_This version does not introduce any user-facing changes._
### 🛠 Breaking changes
- Dropped support for iOS 11.0 ([#14383](https://github.com/expo/expo/pull/14383) by [@cruzach](https://github.com/cruzach))
### 🐛 Bug fixes
- Added missing definition on type LocalAuthenticationResult. ([#13636](https://github.com/expo/expo/pull/13636) by [@mstach60161](https://github.com/mstach60161))
- Fixed detection of the available authentication types on some Samsung devices on Android. ([#14300](https://github.com/expo/expo/pull/14300) by [@beaur](https://github.com/beaur))
- Fix building errors from use_frameworks! in Podfile. ([#14523](https://github.com/expo/expo/pull/14523) by [@kudo](https://github.com/kudo))
### 💡 Others
- Rewrite module from Java to Kotlin. ([#13582](https://github.com/expo/expo/pull/13582) by [@mstach60161](https://github.com/mstach60161))
- Updated `@expo/config-plugins` ([#14443](https://github.com/expo/expo/pull/14443) by [@EvanBacon](https://github.com/EvanBacon))
## 11.1.0 — 2021-06-16

@@ -19,0 +32,0 @@

// Copyright 2018-present 650 Industries. All rights reserved.
#import <UMCore/UMExportedModule.h>
#import <UMCore/UMModuleRegistryConsumer.h>
#import <ExpoModulesCore/EXExportedModule.h>
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
@interface EXLocalAuthentication : UMExportedModule
@interface EXLocalAuthentication : EXExportedModule
- (void)authenticateWithOptions:(NSDictionary *)options
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject;
resolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject;
- (NSString *)convertErrorCode:(NSError *)error;

@@ -12,0 +12,0 @@ + (BOOL)isTouchIdDevice;

@@ -5,3 +5,3 @@ // Copyright 2018-present 650 Industries. All rights reserved.

#import <UMCore/UMUtilities.h>
#import <ExpoModulesCore/EXUtilities.h>
#import <EXLocalAuthentication/EXLocalAuthentication.h>

@@ -22,7 +22,7 @@

UM_EXPORT_MODULE(ExpoLocalAuthentication)
EX_EXPORT_MODULE(ExpoLocalAuthentication)
UM_EXPORT_METHOD_AS(supportedAuthenticationTypesAsync,
supportedAuthenticationTypesAsync:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
EX_EXPORT_METHOD_AS(supportedAuthenticationTypesAsync,
supportedAuthenticationTypesAsync:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject)
{

@@ -39,5 +39,5 @@ NSMutableArray *results = [NSMutableArray array];

UM_EXPORT_METHOD_AS(hasHardwareAsync,
hasHardwareAsync:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
EX_EXPORT_METHOD_AS(hasHardwareAsync,
hasHardwareAsync:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject)
{

@@ -48,16 +48,10 @@ LAContext *context = [LAContext new];

BOOL isSupported = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
BOOL isAvailable;
BOOL isAvailable = isSupported || error.code != LAErrorBiometryNotAvailable;
if (@available(iOS 11.0.1, *)) {
isAvailable = isSupported || error.code != LAErrorBiometryNotAvailable;
} else {
isAvailable = isSupported || error.code != LAErrorTouchIDNotAvailable;
}
resolve(@(isAvailable));
}
UM_EXPORT_METHOD_AS(isEnrolledAsync,
isEnrolledAsync:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
EX_EXPORT_METHOD_AS(isEnrolledAsync,
isEnrolledAsync:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject)
{

@@ -73,5 +67,5 @@ LAContext *context = [LAContext new];

UM_EXPORT_METHOD_AS(getEnrolledLevelAsync,
getEnrolledLevelAsync:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
EX_EXPORT_METHOD_AS(getEnrolledLevelAsync,
getEnrolledLevelAsync:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject)
{

@@ -95,6 +89,6 @@ LAContext *context = [LAContext new];

UM_EXPORT_METHOD_AS(authenticateAsync,
EX_EXPORT_METHOD_AS(authenticateAsync,
authenticateWithOptions:(NSDictionary *)options
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
resolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject)
{

@@ -125,5 +119,3 @@ NSString *warningMessage;

if (@available(iOS 11.0, *)) {
context.interactionNotAllowed = false;
}
context.interactionNotAllowed = false;

@@ -147,3 +139,3 @@ if ([disableDeviceFallback boolValue]) {

@"error": error == nil ? [NSNull null] : [self convertErrorCode:error],
@"warning": UMNullIfNil(warningMessage),
@"warning": EXNullIfNil(warningMessage),
});

@@ -158,3 +150,3 @@ }];

@"error": error == nil ? [NSNull null] : [self convertErrorCode:error],
@"warning": UMNullIfNil(warningMessage),
@"warning": EXNullIfNil(warningMessage),
});

@@ -198,11 +190,9 @@ }];

if (@available(iOS 11.0.1, *)) {
static dispatch_once_t onceToken;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
LAContext *context = [LAContext new];
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil];
isFaceIDDevice = context.biometryType == LABiometryTypeFaceID;
});
}
dispatch_once(&onceToken, ^{
LAContext *context = [LAContext new];
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil];
isFaceIDDevice = context.biometryType == LABiometryTypeFaceID;
});

@@ -220,7 +210,3 @@ return isFaceIDDevice;

[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil];
if (@available(iOS 11.0.1, *)) {
isTouchIDDevice = context.biometryType == LABiometryTypeTouchID;
} else {
isTouchIDDevice = true;
}
isTouchIDDevice = context.biometryType == LABiometryTypeTouchID;
});

@@ -227,0 +213,0 @@

{
"name": "expo-local-authentication",
"version": "11.1.1",
"version": "12.0.0",
"description": "Provides an API for FaceID and TouchID (iOS) or the Fingerprint API (Android) to authenticate the user with a face or fingerprint scan.",

@@ -36,3 +36,3 @@ "main": "build/LocalAuthentication.js",

"license": "MIT",
"homepage": "https://docs.expo.io/versions/latest/sdk/local-authentication/",
"homepage": "https://docs.expo.dev/versions/latest/sdk/local-authentication/",
"jest": {

@@ -42,3 +42,3 @@ "preset": "expo-module-scripts"

"dependencies": {
"@expo/config-plugins": "^3.0.0",
"@expo/config-plugins": "^3.1.0",
"invariant": "^2.2.4"

@@ -49,3 +49,3 @@ },

},
"gitHead": "4fc9d282ff7ab2fa9040b775aeca7c30f5167b17"
"gitHead": "1fffde73411ee7a642b98f1506a8de921805d52b"
}

@@ -1,3 +0,3 @@

import { NativeModulesProxy } from '@unimodules/core';
import { NativeModulesProxy } from 'expo-modules-core';
export default NativeModulesProxy.ExpoLocalAuthentication;

@@ -1,2 +0,2 @@

import { UnavailabilityError } from '@unimodules/core';
import { UnavailabilityError } from 'expo-modules-core';
import invariant from 'invariant';

@@ -14,2 +14,8 @@

// @needsAudit
/**
* Determine whether a face or fingerprint scanner is available on the device.
* @return Returns a promise which fulfils with a `boolean` value indicating whether a face or
* fingerprint scanner is available on this device.
*/
export async function hasHardwareAsync(): Promise<boolean> {

@@ -22,2 +28,10 @@ if (!ExpoLocalAuthentication.hasHardwareAsync) {

// @needsAudit
/**
* Determine what kinds of authentications are available on the device.
* @return Returns a promise which fulfils to an array containing [`AuthenticationType`s](#authenticationtype).
*
* Devices can support multiple authentication methods- i.e. `[1,2]` means the device supports both
* fingerprint and facial recognition. If none are supported, this method returns an empty array.
*/
export async function supportedAuthenticationTypesAsync(): Promise<AuthenticationType[]> {

@@ -30,2 +44,8 @@ if (!ExpoLocalAuthentication.supportedAuthenticationTypesAsync) {

// @needsAudit
/**
* Determine whether the device has saved fingerprints or facial data to use for authentication.
* @return Returns a promise which fulfils to `boolean` value indicating whether the device has
* saved fingerprints or facial data for authentication.
*/
export async function isEnrolledAsync(): Promise<boolean> {

@@ -38,2 +58,10 @@ if (!ExpoLocalAuthentication.isEnrolledAsync) {

// @needsAudit
/**
* Determine what kind of authentication is enrolled on the device.
* @return Returns a promise which fulfils with [`SecurityLevel`](#securitylevel).
* > **Note:** On Android devices prior to M, `SECRET` can be returned if only the SIM lock has been
* enrolled, which is not the method that [`authenticateAsync`](#localauthenticationauthenticateasyncoptions)
* prompts.
*/
export async function getEnrolledLevelAsync(): Promise<SecurityLevel> {

@@ -46,2 +74,12 @@ if (!ExpoLocalAuthentication.getEnrolledLevelAsync) {

// @needsAudit
/**
* Attempts to authenticate via Fingerprint/TouchID (or FaceID if available on the device).
* > **Note:** Apple requires apps which use FaceID to provide a description of why they use this API.
* If you try to use FaceID on an iPhone with FaceID without providing `infoPlist.NSFaceIDUsageDescription`
* in `app.json`, the module will authenticate using device passcode. For more information about
* usage descriptions on iOS, see [Deploying to App Stores](/distribution/app-stores#system-permissions-dialogs-on-ios).
* @param options
* @return Returns a promise which fulfils with [`LocalAuthenticationResult`](#localauthenticationresult).
*/
export async function authenticateAsync(

@@ -70,2 +108,6 @@ options: LocalAuthenticationOptions = {}

// @needsAudit
/**
* **(Android Only)** Cancels authentication flow.
*/
export async function cancelAuthenticate(): Promise<void> {

@@ -72,0 +114,0 @@ if (!ExpoLocalAuthentication.cancelAuthenticate) {

@@ -1,22 +0,62 @@

export type LocalAuthenticationResult = { success: true } | { success: false; error: string };
export type LocalAuthenticationResult =
| { success: true }
| { success: false; error: string; warning?: string };
// @needsAudit
export enum AuthenticationType {
/**
* Indicates fingerprint support.
*/
FINGERPRINT = 1,
/**
* Indicates facial recognition support.
*/
FACIAL_RECOGNITION = 2,
// Android only
/**
* __Android-only.__ Indicates iris recognition support.
*/
IRIS = 3,
}
// @needsAudit
export enum SecurityLevel {
/**
* Indicates no enrolled authentication.
*/
NONE = 0,
/**
* Indicates non-biometric authentication (e.g. PIN, Pattern).
*/
SECRET = 1,
/**
* Indicates biometric authentication.
*/
BIOMETRIC = 2,
}
// @needsAudit
export type LocalAuthenticationOptions = {
/**
* A message that is shown alongside the TouchID or FaceID prompt.
*/
promptMessage?: string;
/**
* Allows to customize the default `Cancel` label shown.
*/
cancelLabel?: string;
/**
* After several failed attempts the system will fallback to the device passcode. This setting
* allows you to disable this option and instead handle the fallback yourself. This can be
* preferable in certain custom authentication workflows. This behaviour maps to using the iOS
* [LAPolicyDeviceOwnerAuthenticationWithBiometrics](https://developer.apple.com/documentation/localauthentication/lapolicy/lapolicydeviceownerauthenticationwithbiometrics?language=objc)
* policy rather than the [LAPolicyDeviceOwnerAuthentication](https://developer.apple.com/documentation/localauthentication/lapolicy/lapolicydeviceownerauthentication?language=objc)
* policy. Defaults to `false`.
*/
disableDeviceFallback?: boolean;
// iOS only
/**
* **iOS only.** Allows to customize the default `Use Passcode` label shown after several failed
* authentication attempts. Setting this option to an empty string disables this button from
* showing in the prompt.
*/
fallbackLabel?: string;
};
// Copyright 2015-present 650 Industries. All rights reserved.
package expo.modules.localauthentication;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.unimodules.core.ExportedModule;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.Promise;
import org.unimodules.core.interfaces.ActivityEventListener;
import org.unimodules.core.interfaces.ActivityProvider;
import org.unimodules.core.interfaces.ExpoMethod;
import org.unimodules.core.interfaces.services.UIManager;
public class LocalAuthenticationModule extends ExportedModule implements ActivityEventListener {
private final BiometricManager mBiometricManager;
private final PackageManager mPackageManager;
private BiometricPrompt mBiometricPrompt;
private Promise mPromise;
private boolean mIsAuthenticating = false;
private ModuleRegistry mModuleRegistry;
private UIManager mUIManager;
private static final int AUTHENTICATION_TYPE_FINGERPRINT = 1;
private static final int AUTHENTICATION_TYPE_FACIAL_RECOGNITION = 2;
private static final int AUTHENTICATION_TYPE_IRIS = 3;
private static final int SECURITY_LEVEL_NONE = 0;
private static final int SECURITY_LEVEL_SECRET = 1;
private static final int SECURITY_LEVEL_BIOMETRIC = 2;
private final BiometricPrompt.AuthenticationCallback mAuthenticationCallback =
new BiometricPrompt.AuthenticationCallback () {
@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
mIsAuthenticating = false;
mBiometricPrompt = null;
Bundle successResult = new Bundle();
successResult.putBoolean("success", true);
safeResolve(successResult);
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
mIsAuthenticating = false;
mBiometricPrompt = null;
Bundle errorResult = new Bundle();
errorResult.putBoolean("success", false);
errorResult.putString("error", convertErrorCode(errMsgId));
errorResult.putString("message", errString.toString());
safeResolve(errorResult);
}
};
public LocalAuthenticationModule(Context context) {
super(context);
mBiometricManager = BiometricManager.from(context);
mPackageManager = context.getPackageManager();
}
@Override
public String getName() {
return "ExpoLocalAuthentication";
}
@Override
public void onCreate(ModuleRegistry moduleRegistry) {
mModuleRegistry = moduleRegistry;
mUIManager = moduleRegistry.getModule(UIManager.class);
mUIManager.registerActivityEventListener(this);
}
@ExpoMethod
public void supportedAuthenticationTypesAsync(final Promise promise) {
int result = mBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
List<Integer> results = new ArrayList<>();
if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
promise.resolve(results);
return;
}
// note(cedric): replace hardcoded system feature strings with constants from
// PackageManager when dropping support for Android SDK 28
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (mPackageManager.hasSystemFeature("android.hardware.fingerprint")) {
results.add(AUTHENTICATION_TYPE_FINGERPRINT);
}
}
if (Build.VERSION.SDK_INT >= 29) {
if (mPackageManager.hasSystemFeature("android.hardware.biometrics.face")) {
results.add(AUTHENTICATION_TYPE_FACIAL_RECOGNITION);
}
if (mPackageManager.hasSystemFeature("android.hardware.biometrics.iris")) {
results.add(AUTHENTICATION_TYPE_IRIS);
}
}
promise.resolve(results);
}
@ExpoMethod
public void hasHardwareAsync(final Promise promise) {
int result = mBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
promise.resolve(result != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE);
}
@ExpoMethod
public void isEnrolledAsync(final Promise promise) {
int result = mBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
promise.resolve(result == BiometricManager.BIOMETRIC_SUCCESS);
}
@ExpoMethod
public void getEnrolledLevelAsync(final Promise promise) {
int level = SECURITY_LEVEL_NONE;
if (isDeviceSecure()) {
level = SECURITY_LEVEL_SECRET;
}
int result = mBiometricManager.canAuthenticate();
if (result == BiometricManager.BIOMETRIC_SUCCESS) {
level = SECURITY_LEVEL_BIOMETRIC;
}
promise.resolve(level);
}
@ExpoMethod
public void authenticateAsync(final Map<String, Object> options, final Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
promise.reject("E_NOT_SUPPORTED", "Cannot display biometric prompt on android versions below 6.0");
return;
}
if (getCurrentActivity() == null) {
promise.reject("E_NOT_FOREGROUND", "Cannot display biometric prompt when the app is not in the foreground");
return;
}
if (getKeyguardManager().isDeviceSecure() == false) {
Bundle errorResult = new Bundle();
errorResult.putBoolean("success", false);
errorResult.putString("error", "not_enrolled");
errorResult.putString("message", "KeyguardManager#isDeviceSecure() returned false");
promise.resolve(errorResult);
return;
}
final FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity();
if (fragmentActivity == null) {
Bundle errorResult = new Bundle();
errorResult.putBoolean("success", false);
errorResult.putString("error", "not_available");
errorResult.putString("message", "getCurrentActivity() returned null");
promise.resolve(errorResult);
return;
}
// BiometricPrompt callbacks are invoked on the main thread so also run this there to avoid
// having to do locking.
mUIManager.runOnUiQueueThread(new Runnable() {
@Override
public void run() {
if (mIsAuthenticating) {
Bundle cancelResult = new Bundle();
cancelResult.putBoolean("success", false);
cancelResult.putString("error", "app_cancel");
safeResolve(cancelResult);
mPromise = promise;
return;
}
String promptMessage = "";
String cancelLabel = "";
boolean disableDeviceFallback = false;
if (options.containsKey("promptMessage")) {
promptMessage = (String) options.get("promptMessage");
}
if (options.containsKey("cancelLabel")) {
cancelLabel = (String) options.get("cancelLabel");
}
if (options.containsKey("disableDeviceFallback")) {
disableDeviceFallback = (Boolean) options.get("disableDeviceFallback");
}
mIsAuthenticating = true;
mPromise = promise;
Executor executor = Executors.newSingleThreadExecutor();
mBiometricPrompt = new BiometricPrompt(fragmentActivity, executor, mAuthenticationCallback);
BiometricPrompt.PromptInfo.Builder promptInfoBuilder = new BiometricPrompt.PromptInfo.Builder()
.setTitle(promptMessage);
if (disableDeviceFallback) {
promptInfoBuilder.setNegativeButtonText(cancelLabel);
} else {
promptInfoBuilder.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_WEAK
| BiometricManager.Authenticators.DEVICE_CREDENTIAL
);
}
BiometricPrompt.PromptInfo promptInfo = promptInfoBuilder.build();
try {
mBiometricPrompt.authenticate(promptInfo);
} catch (NullPointerException ex) {
promise.reject("E_INTERNAL_ERRROR", "Canceled authentication due to an internal error");
}
}
});
}
@ExpoMethod
public void cancelAuthenticate(final Promise promise) {
mUIManager.runOnUiQueueThread(new Runnable() {
@Override
public void run() {
safeCancel();
promise.resolve(null);
}
});
}
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
// If the user uses PIN as an authentication method, the result will be passed to the `onActivityResult`.
// Unfortunately, react-native doesn't pass this value to the underlying fragment - we won't resolve the promise.
// So we need to do it manually.
if (activity instanceof FragmentActivity) {
FragmentActivity fragmentActivity = (FragmentActivity) activity;
Fragment fragment = fragmentActivity.getSupportFragmentManager().findFragmentByTag("androidx.biometric.BiometricFragment");
if (fragment != null) {
fragment.onActivityResult(requestCode & 0xffff, resultCode, data);
}
}
}
@Override
public void onNewIntent(Intent intent) {
// noop
}
private boolean isDeviceSecure() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return getKeyguardManager().isDeviceSecure();
} else {
// NOTE: `KeyguardManager#isKeyguardSecure()` considers SIM locked state,
// but it will be ignored on falling-back to device credential on biometric authentication.
// That means, setting level to `SECURITY_LEVEL_SECRET` might be misleading for some users.
// But there is no equivalent APIs prior to M.
// `andriodx.biometric.BiometricManager#canAuthenticate(int)` looks like an alternative,
// but specifying `BiometricManager.Authenticators.DEVICE_CREDENTIAL` alone is not
// supported prior to API 30.
// https://developer.android.com/reference/androidx/biometric/BiometricManager#canAuthenticate(int)
return getKeyguardManager().isKeyguardSecure();
}
}
private void safeCancel() {
if (mBiometricPrompt != null && mIsAuthenticating) {
mBiometricPrompt.cancelAuthentication();
mIsAuthenticating = false;
}
}
private void safeResolve(Object result) {
if (mPromise != null) {
mPromise.resolve(result);
mPromise = null;
}
}
private static String convertErrorCode(int code) {
switch (code) {
case BiometricPrompt.ERROR_CANCELED:
case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
case BiometricPrompt.ERROR_USER_CANCELED:
return "user_cancel";
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
case BiometricPrompt.ERROR_NO_BIOMETRICS:
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
return "not_available";
case BiometricPrompt.ERROR_LOCKOUT:
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
return "lockout";
case BiometricPrompt.ERROR_NO_SPACE:
return "no_space";
case BiometricPrompt.ERROR_TIMEOUT:
return "timeout";
case BiometricPrompt.ERROR_UNABLE_TO_PROCESS:
return "unable_to_process";
default:
return "unknown";
}
}
private KeyguardManager getKeyguardManager() {
return (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
}
private Activity getCurrentActivity() {
ActivityProvider activityProvider = mModuleRegistry.getModule(ActivityProvider.class);
return activityProvider != null ? activityProvider.getCurrentActivity() : null;
}
}
package expo.modules.localauthentication;
import android.content.Context;
import java.util.Collections;
import java.util.List;
import org.unimodules.core.BasePackage;
import org.unimodules.core.ExportedModule;
public class LocalAuthenticationPackage extends BasePackage {
@Override
public List<ExportedModule> createExportedModules(Context context) {
return Collections.<ExportedModule>singletonList(new LocalAuthenticationModule(context));
}
}

Sorry, the diff of this file is not supported yet