Skip to content

Commit

Permalink
Merge pull request #101 from KomodoHQ/master
Browse files Browse the repository at this point in the history
[iOS Only] Support Passcode Fallback Natively
  • Loading branch information
zibs authored Oct 27, 2018
2 parents a6e1278 + 778bde4 commit 520a603
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 54 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ There's excellent documentation on how to do this in the [React Native Docs](htt

iOS and Android differ slightly in their TouchID authentication.

On Android you can customize the title and color of the pop-up by passing in the **optional config object** with a color and title key to the `authenticate` method. Even if you pass in the config object, iOS **does not** allow you change the color nor the title of the pop-up.
On Android you can customize the title and color of the pop-up by passing in the **optional config object** with a color and title key to the `authenticate` method. Even if you pass in the config object, iOS **does not** allow you change the color nor the title of the pop-up. iOS does support `passcodeFallback` as an option, which when set to `true` will allow users to use their device pin - useful for people with Face / Touch ID disabled.

Error handling is also different between the platforms, with iOS currently providing much more descriptive error codes.

Expand Down Expand Up @@ -151,6 +151,7 @@ __Arguments__
- `cancelText` - **Android** - cancel button text
- `fallbackLabel` - **iOS** - by default specified 'Show Password' label. If set to empty string label is invisible.
- `unifiedErrors` - return unified error messages (see below) (default = false)
- `passcodeFallback` - **iOS** - by default set to false. If set to true, will allow use of keypad passcode.


__Examples__
Expand All @@ -161,7 +162,8 @@ const optionalConfigObject = {
sensorDescription: "Touch sensor", // Android
cancelText: "Cancel", // Android
fallbackLabel: "Show Passcode", // iOS (if empty, then label is hidden)
unifiedErrors: false // use unified error messages (default false)
unifiedErrors: false, // use unified error messages (default false)
passcodeFallback: false // iOS
}

TouchID.authenticate('to demo this react-native component', optionalConfigObject)
Expand Down
3 changes: 2 additions & 1 deletion TouchID.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export default {
authenticate(reason, config) {
const DEFAULT_CONFIG = {
fallbackLabel: null,
unifiedErrors: false
unifiedErrors: false,
passcodeFallback: false
};
const authReason = reason ? reason : ' ';
const authConfig = Object.assign({}, DEFAULT_CONFIG, config);
Expand Down
130 changes: 79 additions & 51 deletions TouchID.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ @implementation TouchID

if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
callback(@[[NSNull null], [self getBiometryType:context]]);
// Device does not support TouchID
} else {

} else if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error]) {
callback(@[[NSNull null], [self getBiometryType:context]]);
}
// Device does not support FaceID / TouchID / Pin
else {
callback(@[RCTMakeError(@"RCTTouchIDNotSupported", nil, nil)]);
return;
}
Expand All @@ -24,6 +28,7 @@ @implementation TouchID
options:(NSDictionary *)options
callback: (RCTResponseSenderBlock)callback)
{
NSNumber *passcodeFallback = [NSNumber numberWithBool:false];
LAContext *context = [[LAContext alloc] init];
NSError *error;

Expand All @@ -32,75 +37,98 @@ @implementation TouchID
context.localizedFallbackTitle = fallbackLabel;
}

if (RCTNilIfNull([options objectForKey:@"passcodeFallback"]) != nil) {
passcodeFallback = [RCTConvert NSNumber:options[@"passcodeFallback"]];
}

// Device has TouchID
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// Attempt Authentification
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:reason
reply:^(BOOL success, NSError *error)
{
if (success) { // Authentication Successful
callback(@[[NSNull null], @"Authenticated with Touch ID."]);
} else if (error) { // Authentication Error
NSString *errorReason;

switch (error.code) {
case LAErrorAuthenticationFailed:
errorReason = @"LAErrorAuthenticationFailed";
break;

case LAErrorUserCancel:
errorReason = @"LAErrorUserCancel";
break;

case LAErrorUserFallback:
errorReason = @"LAErrorUserFallback";
break;

case LAErrorSystemCancel:
errorReason = @"LAErrorSystemCancel";
break;

case LAErrorPasscodeNotSet:
errorReason = @"LAErrorPasscodeNotSet";
break;

case LAErrorTouchIDNotAvailable:
errorReason = @"LAErrorTouchIDNotAvailable";
break;

case LAErrorTouchIDNotEnrolled:
errorReason = @"LAErrorTouchIDNotEnrolled";
break;

default:
errorReason = @"RCTTouchIDUnknownError";
break;
}

NSLog(@"Authentication failed: %@", errorReason);
callback(@[RCTMakeError(errorReason, nil, nil)]);
} else { // Authentication Failure
callback(@[RCTMakeError(@"LAErrorAuthenticationFailed", nil, nil)]);
}
[self handleAttemptToUseDeviceIDWithSuccess:success error:error callback:callback];
}];

// Device does not support TouchID
} else {
// Device does not support TouchID but user wishes to use passcode fallback
} else if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error] && [passcodeFallback boolValue]) {
// Attempt Authentification
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication
localizedReason:reason
reply:^(BOOL success, NSError *error)
{
[self handleAttemptToUseDeviceIDWithSuccess:success error:error callback:callback];
}];
}
else {
callback(@[RCTMakeError(@"RCTTouchIDNotSupported", nil, nil)]);
return;
}
}

- (void)handleAttemptToUseDeviceIDWithSuccess:(BOOL)success error:(NSError *)error callback:(RCTResponseSenderBlock)callback {
if (success) { // Authentication Successful
callback(@[[NSNull null], @"Authenticated with Touch ID."]);
} else if (error) { // Authentication Error
NSString *errorReason;

switch (error.code) {
case LAErrorAuthenticationFailed:
errorReason = @"LAErrorAuthenticationFailed";
break;

case LAErrorUserCancel:
errorReason = @"LAErrorUserCancel";
break;

case LAErrorUserFallback:
errorReason = @"LAErrorUserFallback";
break;

case LAErrorSystemCancel:
errorReason = @"LAErrorSystemCancel";
break;

case LAErrorPasscodeNotSet:
errorReason = @"LAErrorPasscodeNotSet";
break;

case LAErrorTouchIDNotAvailable:
errorReason = @"LAErrorTouchIDNotAvailable";
break;

case LAErrorTouchIDNotEnrolled:
errorReason = @"LAErrorTouchIDNotEnrolled";
break;

default:
errorReason = @"RCTTouchIDUnknownError";
break;
}

NSLog(@"Authentication failed: %@", errorReason);
callback(@[RCTMakeError(errorReason, nil, nil)]);
} else { // Authentication Failure
callback(@[RCTMakeError(@"LAErrorAuthenticationFailed", nil, nil)]);
}
}

- (NSString *)getBiometryType:(LAContext *)context
{
if (@available(iOS 11, *)) {
return (context.biometryType == LABiometryTypeFaceID) ? @"FaceID" : @"TouchID";
if (context.biometryType == LABiometryTypeFaceID) {
return @"FaceID";
}
else if (context.biometryType == LABiometryTypeTouchID) {
return @"TouchID";
}
else if (context.biometryType == LABiometryNone) {
return @"None";
}
}

return @"TouchID";
}

@end


0 comments on commit 520a603

Please sign in to comment.