diff --git a/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java b/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java index 00bf6ad..c119be9 100644 --- a/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java +++ b/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java @@ -1,5 +1,7 @@ package com.rnbiometrics; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -54,31 +56,39 @@ public void isSensorAvailable(final ReadableMap params, final Promise promise) { boolean allowDeviceCredentials = params.getBoolean("allowDeviceCredentials"); ReactApplicationContext reactApplicationContext = getReactApplicationContext(); BiometricManager biometricManager = BiometricManager.from(reactApplicationContext); - int canAuthenticate = biometricManager.canAuthenticate(getAllowedAuthenticators(allowDeviceCredentials)); + PackageManager packageManager = reactApplicationContext.getPackageManager(); + int canAuthenticate = biometricManager.canAuthenticate(getAllowedAuthenticators(false)); if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) { WritableMap resultMap = new WritableNativeMap(); resultMap.putBoolean("available", true); resultMap.putString("biometryType", "Biometrics"); + + if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) == true) { + resultMap.putString("biometryType", "Fingerprint"); + } + promise.resolve(resultMap); + } else if (allowDeviceCredentials) { + int canAuthenticateCredentials = biometricManager.canAuthenticate(getAllowedAuthenticators(true)); + + if (canAuthenticateCredentials == BiometricManager.BIOMETRIC_SUCCESS) { + WritableMap resultMap = new WritableNativeMap(); + resultMap.putBoolean("available", true); + resultMap.putString("biometryType", "Credentials"); + + promise.resolve(resultMap); + } else { + WritableMap resultMap = new WritableNativeMap(); + resultMap.putBoolean("available", false); + resultMap.putString("error", parseError(canAuthenticateCredentials)); + + promise.resolve(resultMap); + } } else { WritableMap resultMap = new WritableNativeMap(); resultMap.putBoolean("available", false); - - switch (canAuthenticate) { - case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE: - resultMap.putString("error", "BIOMETRIC_ERROR_NO_HARDWARE"); - break; - case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE: - resultMap.putString("error", "BIOMETRIC_ERROR_HW_UNAVAILABLE"); - break; - case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED: - resultMap.putString("error", "BIOMETRIC_ERROR_NONE_ENROLLED"); - break; - case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: - resultMap.putString("error", "BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED"); - break; - } + resultMap.putString("error", parseError(canAuthenticate)); promise.resolve(resultMap); } @@ -89,10 +99,32 @@ public void isSensorAvailable(final ReadableMap params, final Promise promise) { promise.resolve(resultMap); } } catch (Exception e) { - promise.reject("Error detecting biometrics availability: " + e.getMessage(), "Error detecting biometrics availability: " + e.getMessage()); + promise.reject("Error detecting biometrics availability: " + e.getMessage(), + "Error detecting biometrics availability: " + e.getMessage()); } } + private String parseError(final int authenticationResult) { + String message = ""; + + switch (authenticationResult) { + case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE: + message = "BIOMETRIC_ERROR_NO_HARDWARE"; + break; + case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE: + message = "BIOMETRIC_ERROR_HW_UNAVAILABLE"; + break; + case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED: + message = "BIOMETRIC_ERROR_NONE_ENROLLED"; + break; + case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: + message = "BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED"; + break; + } + + return message; + } + @ReactMethod public void createKeys(final ReadableMap params, Promise promise) { try { diff --git a/index.ts b/index.ts index ef8f260..2fd2f13 100644 --- a/index.ts +++ b/index.ts @@ -5,12 +5,18 @@ const { ReactNativeBiometrics: bridge } = NativeModules /** * Type alias for possible biometry types */ -export type BiometryType = 'TouchID' | 'FaceID' | 'Biometrics' +export type BiometryTypeIOS = 'TouchID' | 'FaceID' +export type BiometryTypeAndroid ='Fingerprint' | 'Biometrics' | 'Credentials' +export type BiometryType = BiometryTypeIOS | BiometryTypeAndroid interface RNBiometricsOptions { allowDeviceCredentials?: boolean } +interface isSensorAvailable { + allowDeviceCredentials?: boolean +} + interface IsSensorAvailableResult { available: boolean biometryType?: BiometryType @@ -45,6 +51,7 @@ interface SimplePromptOptions { promptMessage: string fallbackPromptMessage?: string cancelButtonText?: string + allowDeviceCredentials?: boolean } interface SimplePromptResult { @@ -64,20 +71,24 @@ export const FaceID = 'FaceID' * Enum for generic biometrics (this is the only value available on android) */ export const Biometrics = 'Biometrics' +export const Fingerprint = 'Fingerprint' +export const Credentials = 'Credentials' export const BiometryTypes = { TouchID, FaceID, - Biometrics + Biometrics, + Fingerprint, + Credentials } export module ReactNativeBiometricsLegacy { /** - * Returns promise that resolves to an object with object.biometryType = Biometrics | TouchID | FaceID + * Returns promise that resolves to an object with object.biometryType = Biometrics | TouchID | FaceID | Credentials * @returns {Promise} Promise that resolves to an object with details about biometrics available */ - export function isSensorAvailable(): Promise { - return new ReactNativeBiometrics().isSensorAvailable() + export function isSensorAvailable(params: isSensorAvailable): Promise { + return new ReactNativeBiometrics().isSensorAvailable(params) } /** @@ -127,6 +138,7 @@ export module ReactNativeBiometricsLegacy { * @param {Object} simplePromptOptions * @param {string} simplePromptOptions.promptMessage * @param {string} simplePromptOptions.fallbackPromptMessage + * @param {boolean} simplePromptOptions.allowDeviceCredentials * @returns {Promise} Promise that resolves an object with details about the biometrics result */ export function simplePrompt(simplePromptOptions: SimplePromptOptions): Promise { @@ -150,9 +162,9 @@ export default class ReactNativeBiometrics { * Returns promise that resolves to an object with object.biometryType = Biometrics | TouchID | FaceID * @returns {Promise} Promise that resolves to an object with details about biometrics available */ - isSensorAvailable(): Promise { + isSensorAvailable(params?: isSensorAvailable): Promise { return bridge.isSensorAvailable({ - allowDeviceCredentials: this.allowDeviceCredentials + allowDeviceCredentials: params?.allowDeviceCredentials ?? this.allowDeviceCredentials }) } @@ -217,7 +229,7 @@ export default class ReactNativeBiometrics { simplePromptOptions.fallbackPromptMessage = simplePromptOptions.fallbackPromptMessage ?? 'Use Passcode' return bridge.simplePrompt({ - allowDeviceCredentials: this.allowDeviceCredentials, + allowDeviceCredentials: simplePromptOptions.allowDeviceCredentials ?? this.allowDeviceCredentials, ...simplePromptOptions }) } diff --git a/ios/ReactNativeBiometrics.m b/ios/ReactNativeBiometrics.m index 03ec388..880337d 100644 --- a/ios/ReactNativeBiometrics.m +++ b/ios/ReactNativeBiometrics.m @@ -16,14 +16,10 @@ @implementation ReactNativeBiometrics RCT_EXPORT_METHOD(isSensorAvailable: (NSDictionary *)params resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { LAContext *context = [[LAContext alloc] init]; NSError *la_error = nil; + NSError *la_errorWithCredentials = nil; BOOL allowDeviceCredentials = [RCTConvert BOOL:params[@"allowDeviceCredentials"]]; - LAPolicy laPolicy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - if (allowDeviceCredentials == TRUE) { - laPolicy = LAPolicyDeviceOwnerAuthentication; - } - - BOOL canEvaluatePolicy = [context canEvaluatePolicy:laPolicy error:&la_error]; + BOOL canEvaluatePolicy = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&la_error]; if (canEvaluatePolicy) { NSString *biometryType = [self getBiometryType:context]; @@ -33,14 +29,32 @@ @implementation ReactNativeBiometrics }; resolve(result); - } else { - NSString *errorMessage = [NSString stringWithFormat:@"%@", la_error]; - NSDictionary *result = @{ - @"available": @(NO), - @"error": errorMessage - }; + } else if (allowDeviceCredentials == TRUE) { + BOOL canEvaluatePolicyWithCredentials = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&la_errorWithCredentials]; + if(canEvaluatePolicyWithCredentials == TRUE) { + NSDictionary *result = @{ + @"available": @(YES), + @"biometryType": @"Credentials" + }; - resolve(result); + resolve(result); + } else { + NSString *errorMessage = [NSString stringWithFormat:@"%@", la_errorWithCredentials]; + NSDictionary *result = @{ + @"available": @(NO), + @"error": errorMessage + }; + + resolve(result); + } + } else { + NSString *errorMessage = [NSString stringWithFormat:@"%@", la_error]; + NSDictionary *result = @{ + @"available": @(NO), + @"error": errorMessage + }; + + resolve(result); } }