Skip to content
66 changes: 49 additions & 17 deletions android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down
28 changes: 20 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,6 +51,7 @@ interface SimplePromptOptions {
promptMessage: string
fallbackPromptMessage?: string
cancelButtonText?: string
allowDeviceCredentials?: boolean
}

interface SimplePromptResult {
Expand All @@ -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<Object>} Promise that resolves to an object with details about biometrics available
*/
export function isSensorAvailable(): Promise<IsSensorAvailableResult> {
return new ReactNativeBiometrics().isSensorAvailable()
export function isSensorAvailable(params: isSensorAvailable): Promise<IsSensorAvailableResult> {
return new ReactNativeBiometrics().isSensorAvailable(params)
}

/**
Expand Down Expand Up @@ -127,6 +138,7 @@ export module ReactNativeBiometricsLegacy {
* @param {Object} simplePromptOptions
* @param {string} simplePromptOptions.promptMessage
* @param {string} simplePromptOptions.fallbackPromptMessage
* @param {boolean} simplePromptOptions.allowDeviceCredentials
* @returns {Promise<Object>} Promise that resolves an object with details about the biometrics result
*/
export function simplePrompt(simplePromptOptions: SimplePromptOptions): Promise<SimplePromptResult> {
Expand All @@ -150,9 +162,9 @@ export default class ReactNativeBiometrics {
* Returns promise that resolves to an object with object.biometryType = Biometrics | TouchID | FaceID
* @returns {Promise<Object>} Promise that resolves to an object with details about biometrics available
*/
isSensorAvailable(): Promise<IsSensorAvailableResult> {
isSensorAvailable(params?: isSensorAvailable): Promise<IsSensorAvailableResult> {
return bridge.isSensorAvailable({
allowDeviceCredentials: this.allowDeviceCredentials
allowDeviceCredentials: params?.allowDeviceCredentials ?? this.allowDeviceCredentials
})
}

Expand Down Expand Up @@ -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
})
}
Expand Down
40 changes: 27 additions & 13 deletions ios/ReactNativeBiometrics.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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);
}
}

Expand Down