mirror of
https://github.com/status-im/react-native-keychain.git
synced 2025-02-17 15:47:29 +00:00
Remove *PasswordWithAuthentication and replace with accessControl option
This commit is contained in:
parent
37ea15ae5e
commit
add90c5769
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
SegmentedControlIOS,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
@ -11,12 +12,16 @@ import {
|
||||
|
||||
import * as Keychain from 'react-native-keychain';
|
||||
|
||||
const ACCESS_CONTROL_OPTIONS = ['None', 'Passcode', 'Password'];
|
||||
const ACCESS_CONTROL_MAP = [null, Keychain.ACCESS_CONTROL.DEVICE_PASSCODE, Keychain.ACCESS_CONTROL.APPLICATION_PASSWORD, Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET]
|
||||
|
||||
export default class KeychainExample extends Component {
|
||||
state = {
|
||||
username: '',
|
||||
password: '',
|
||||
status: '',
|
||||
biometryType: null,
|
||||
accessControl: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -25,24 +30,13 @@ export default class KeychainExample extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
async save(accessControl) {
|
||||
try {
|
||||
if (this.state.biometryType) {
|
||||
await Keychain.setPasswordWithAuthentication(
|
||||
this.state.username,
|
||||
this.state.password,
|
||||
{
|
||||
accessControl:
|
||||
Keychain.ACCESS_CONTROL.TOUCH_ID_ANY_OR_DEVICE_PASSCODE,
|
||||
authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await Keychain.setGenericPassword(
|
||||
this.state.username,
|
||||
this.state.password
|
||||
);
|
||||
}
|
||||
await Keychain.setGenericPassword(
|
||||
this.state.username,
|
||||
this.state.password,
|
||||
{ accessControl: this.state.accessControl }
|
||||
);
|
||||
this.setState({ status: 'Credentials saved!' });
|
||||
} catch (err) {
|
||||
this.setState({ status: 'Could not save credentials, ' + err });
|
||||
@ -51,13 +45,7 @@ export default class KeychainExample extends Component {
|
||||
|
||||
async load() {
|
||||
try {
|
||||
const credentials = await (this.state.biometryType
|
||||
? Keychain.getPasswordWithAuthentication({
|
||||
accessControl:
|
||||
Keychain.ACCESS_CONTROL.TOUCH_ID_ANY_OR_DEVICE_PASSCODE,
|
||||
authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
|
||||
})
|
||||
: Keychain.getGenericPassword());
|
||||
const credentials = await Keychain.getGenericPassword();
|
||||
if (credentials) {
|
||||
this.setState({ ...credentials, status: 'Credentials loaded!' });
|
||||
} else {
|
||||
@ -113,14 +101,24 @@ export default class KeychainExample extends Component {
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
{Platform.OS === 'ios' && (
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Access Control</Text>
|
||||
<SegmentedControlIOS
|
||||
selectedIndex={0}
|
||||
values={this.state.biometryType ? [...ACCESS_CONTROL_OPTIONS, this.state.biometryType] : ACCESS_CONTROL_OPTIONS}
|
||||
onChange={({ nativeEvent }) => {
|
||||
this.setState({
|
||||
accessControl: ACCESS_CONTROL_MAP[nativeEvent.selectedSegmentIndex],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{!!this.state.status && (
|
||||
<Text style={styles.status}>{this.state.status}</Text>
|
||||
)}
|
||||
{!!this.state.biometryType && (
|
||||
<Text style={styles.biometryType}>
|
||||
Supported biometry: {this.state.biometryType}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<View style={styles.buttons}>
|
||||
<TouchableHighlight
|
||||
onPress={() => this.save()}
|
||||
@ -156,12 +154,11 @@ export default class KeychainExample extends Component {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
content: {
|
||||
width: 250,
|
||||
marginHorizontal: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
|
@ -11,26 +11,9 @@
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
#import <RNKeychain/RNKeychainAuthenticationListener.h>
|
||||
|
||||
@interface AppDelegate() <RNKeychainAuthenticationListener>
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
@synthesize willPromptForAuthentication = _willPromptForAuthentication;
|
||||
|
||||
- (void)setWillPromptForAuthentication:(BOOL)willPromptForAuthentication {
|
||||
_willPromptForAuthentication = willPromptForAuthentication;
|
||||
|
||||
if (willPromptForAuthentication) {
|
||||
NSLog(@"APPDELEGATE::: will prompt TouchId");
|
||||
} else {
|
||||
NSLog(@"APPDELEGATE::: ended prompt TouchId");
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
10
README.md
10
README.md
@ -127,12 +127,12 @@ Keychain
|
||||
|
||||
| Key | Applies to | Description | Default |
|
||||
|---|---|---|---|
|
||||
|**`accessControl`**|`PasswordWithAuthentication`|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*`Keychain.ACCESS_CONTROL.TOUCH_ID_CURRENT_SET_OR_DEVICE_PASSCODE`*|
|
||||
|**`accessible`**|`GenericPassword`, `InternetCredentials`|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|
||||
|**`accessGroup`**|`GenericPassword`, `InternetCredentials`, `PasswordWithAuthentication`|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|
||||
|**`authenticationPrompt`**|`PasswordWithAuthentication`|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret!`|
|
||||
|**`accessControl`**|`setGenericPassword`, `setInternetCredentials`|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*None*|
|
||||
|**`accessible`**|`setGenericPassword`, `setInternetCredentials`|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|
||||
|**`accessGroup`**|`setGenericPassword`, `setInternetCredentials`|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|
||||
|**`authenticationPrompt`**|`getGenericPassword`, `getInternetCredentials`|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret`|
|
||||
|**`authenticationType`**|`canImplyAuthentication`|Policies specifying which forms of authentication are acceptable. |`Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS`|
|
||||
|**`service`**|`GenericPassword`, `PasswordWithAuthentication`|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
|
||||
|**`service`**|`setGenericPassword`, `getGenericPassword`|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
|
||||
|
||||
#### `Keychain.ACCESS_CONTROL` enum
|
||||
|
||||
|
@ -10,11 +10,7 @@
|
||||
5D82368F1B0CE3CB005A9EF3 /* RNKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */; };
|
||||
5D8236911B0CE3D6005A9EF3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D8236901B0CE3D6005A9EF3 /* Security.framework */; };
|
||||
5DE632D52043423E004F9598 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DE632D42043423E004F9598 /* LocalAuthentication.framework */; };
|
||||
5DE632D720434276004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
|
||||
5DE632D920434291004F9598 /* RNKeychainAuthenticationListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
|
||||
5DE632DB204342AE004F9598 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DE632DA204342AE004F9598 /* Security.framework */; };
|
||||
5DE632DC204342B5004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
|
||||
5DE632DE204342C3004F9598 /* RNKeychainAuthenticationListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
|
||||
6478986B1F38BFA100DA1C12 /* RNKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -25,7 +21,6 @@
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
5DE632D720434276004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -36,7 +31,6 @@
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
5DE632DC204342B5004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -46,10 +40,9 @@
|
||||
/* Begin PBXFileReference section */
|
||||
5D82366F1B0CE05B005A9EF3 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5D82368B1B0CE2A6005A9EF3 /* RNKeychainManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNKeychainManager.h; sourceTree = "<group>"; };
|
||||
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNKeychainManager.m; sourceTree = "<group>"; };
|
||||
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RNKeychainManager.m; sourceTree = "<group>"; tabWidth = 2; };
|
||||
5D8236901B0CE3D6005A9EF3 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
5DE632D42043423E004F9598 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
|
||||
5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNKeychainAuthenticationListener.h; sourceTree = "<group>"; };
|
||||
5DE632DA204342AE004F9598 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
|
||||
6478985F1F38BF9100DA1C12 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
@ -97,7 +90,6 @@
|
||||
5D82368A1B0CE2A6005A9EF3 /* RNKeychainManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */,
|
||||
5D82368B1B0CE2A6005A9EF3 /* RNKeychainManager.h */,
|
||||
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */,
|
||||
);
|
||||
@ -121,7 +113,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5DE632D920434291004F9598 /* RNKeychainAuthenticationListener.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -129,7 +120,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5DE632DE204342C3004F9598 /* RNKeychainAuthenticationListener.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// TouchIdPromptListener.h
|
||||
// RNKeychain
|
||||
//
|
||||
// Created by Steffen Blümm on 05/04/17.
|
||||
// Copyright © 2017 Joel Arvidsson. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
This is a protocol to be implemented by the AppDelegate in case
|
||||
the AppDelegate takes precautions to obfuscate the screen when
|
||||
the app resigns active state.
|
||||
Thus the AppDelegate can avoid to obfuscating the screen when
|
||||
the TouchId-prompt is brought up by the OS
|
||||
*/
|
||||
@protocol RNKeychainAuthenticationListener <NSObject>
|
||||
|
||||
@property (nonatomic, assign) BOOL willPromptForAuthentication;
|
||||
|
||||
@end
|
@ -15,8 +15,6 @@
|
||||
#import <LocalAuthentication/LAContext.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RNKeychainAuthenticationListener.h"
|
||||
|
||||
@implementation RNKeychainManager
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@ -148,43 +146,69 @@ LAPolicy authPolicy(NSDictionary *options)
|
||||
return LAPolicyDeviceOwnerAuthentication;
|
||||
}
|
||||
|
||||
SecAccessControlCreateFlags secureAccessControl(NSDictionary *options)
|
||||
SecAccessControlCreateFlags accessControlValue(NSDictionary *options)
|
||||
{
|
||||
if (options && options[kAccessControlType]) {
|
||||
if ([ options[kAccessControlType] isEqualToString: kAccessControlUserPresence ]) {
|
||||
if (options && options[kAccessControlType] && [options[kAccessControlType] isKindOfClass:[NSString class]]) {
|
||||
if ([options[kAccessControlType] isEqualToString: kAccessControlUserPresence]) {
|
||||
return kSecAccessControlUserPresence;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryAny ]) {
|
||||
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryAny]) {
|
||||
return kSecAccessControlTouchIDAny;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSet ]) {
|
||||
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSet]) {
|
||||
return kSecAccessControlTouchIDCurrentSet;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlDevicePasscode ]) {
|
||||
else if ([options[kAccessControlType] isEqualToString: kAccessControlDevicePasscode]) {
|
||||
return kSecAccessControlDevicePasscode;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryAnyOrDevicePasscode ]) {
|
||||
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryAnyOrDevicePasscode]) {
|
||||
return kSecAccessControlTouchIDAny|kSecAccessControlOr|kSecAccessControlDevicePasscode;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSetOrDevicePasscode ]) {
|
||||
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSetOrDevicePasscode]) {
|
||||
return kSecAccessControlTouchIDCurrentSet|kSecAccessControlOr|kSecAccessControlDevicePasscode;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlApplicationPassword ]) {
|
||||
else if ([options[kAccessControlType] isEqualToString: kAccessControlApplicationPassword]) {
|
||||
return kSecAccessControlApplicationPassword;
|
||||
}
|
||||
}
|
||||
return kSecAccessControlTouchIDCurrentSet|kSecAccessControlOr|kSecAccessControlDevicePasscode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//LAPolicyDeviceOwnerAuthenticationWithBiometrics | LAPolicyDeviceOwnerAuthentication
|
||||
|
||||
- (void)insertKeychainEntry:(NSDictionary *)attributes withAccessGroup:(NSString * __nullable)accessGroup resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
|
||||
- (void)insertKeychainEntry:(NSDictionary *)attributes withOptions:(NSDictionary * __nullable)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
|
||||
{
|
||||
if (accessGroup != nil) {
|
||||
NSMutableDictionary *mAttributes = attributes.mutableCopy;
|
||||
mAttributes[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
|
||||
attributes = [NSDictionary dictionaryWithDictionary:mAttributes];
|
||||
NSString *accessGroup = accessGroupValue(options);
|
||||
CFStringRef accessible = accessibleValue(options);
|
||||
SecAccessControlCreateFlags accessControl = accessControlValue(options);
|
||||
|
||||
NSMutableDictionary *mAttributes = attributes.mutableCopy;
|
||||
|
||||
if (accessControl) {
|
||||
NSError *aerr = nil;
|
||||
BOOL canAuthenticate = [[LAContext new] canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&aerr];
|
||||
if (aerr || !canAuthenticate) {
|
||||
return rejectWithError(reject, aerr);
|
||||
}
|
||||
|
||||
CFErrorRef error = NULL;
|
||||
SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
|
||||
accessible,
|
||||
accessControl,
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
return rejectWithError(reject, aerr);
|
||||
}
|
||||
mAttributes[(__bridge NSString *)kSecAttrAccessControl] = (__bridge id)sacRef;
|
||||
} else {
|
||||
mAttributes[(__bridge NSString *)kSecAttrAccessible] = (__bridge id)accessible;
|
||||
}
|
||||
|
||||
if (accessGroup != nil) {
|
||||
mAttributes[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
|
||||
}
|
||||
|
||||
attributes = [NSDictionary dictionaryWithDictionary:mAttributes];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef) attributes, NULL);
|
||||
|
||||
@ -223,7 +247,7 @@ SecAccessControlCreateFlags secureAccessControl(NSDictionary *options)
|
||||
return SecItemDelete((__bridge CFDictionaryRef) query);
|
||||
}
|
||||
|
||||
#pragma mark - Proposed functionality - RCT_EXPORT_METHOD
|
||||
#pragma mark - RNKeychain
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
RCT_EXPORT_METHOD(canCheckAuthentication:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
@ -261,129 +285,28 @@ RCT_EXPORT_METHOD(getSupportedBiometryType:(RCTPromiseResolveBlock)resolve rejec
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
RCT_EXPORT_METHOD(setPasswordWithAuthentication:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSString *service = serviceValue(options);
|
||||
NSError *aerr = nil;
|
||||
BOOL canAuthenticate = [[LAContext new] canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&aerr];
|
||||
if (aerr || !canAuthenticate) {
|
||||
return rejectWithError(reject, aerr);
|
||||
}
|
||||
|
||||
[self deletePasswordsForService:service];
|
||||
|
||||
CFErrorRef error = NULL;
|
||||
SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, //kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
|
||||
secureAccessControl(options),
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
// ok: failed
|
||||
return rejectWithError(reject, aerr);
|
||||
}
|
||||
|
||||
NSDictionary *attributes = @{
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
|
||||
(__bridge NSString *)kSecAttrService: service,
|
||||
(__bridge NSString *)kSecAttrAccount: username,
|
||||
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
|
||||
(__bridge NSString *)kSecAttrAccessControl: (__bridge id)sacRef
|
||||
};
|
||||
|
||||
[self insertKeychainEntry:attributes withAccessGroup:accessGroupValue(options) resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getPasswordWithAuthentication:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSString *service = serviceValue(options);
|
||||
NSString *promptMessage = @"Authenticate to retrieve secret!";
|
||||
if (options && options[kAuthenticationPromptMessage]) {
|
||||
promptMessage = options[kAuthenticationPromptMessage];
|
||||
}
|
||||
|
||||
NSDictionary *query = @{
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
|
||||
(__bridge NSString *)kSecAttrService: service,
|
||||
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
|
||||
(__bridge NSString *)kSecUseOperationPrompt: promptMessage
|
||||
};
|
||||
|
||||
// Notify AppDelegate
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self notifyAuthenticationListener: YES];
|
||||
});
|
||||
|
||||
// Look up password for service in the keychain
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
__block NSDictionary* found = nil;
|
||||
CFTypeRef foundTypeRef = NULL;
|
||||
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef*)&foundTypeRef);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self notifyAuthenticationListener: NO];
|
||||
|
||||
if (osStatus != noErr && osStatus != errSecItemNotFound) {
|
||||
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
|
||||
return rejectWithError(reject, error);
|
||||
}
|
||||
|
||||
found = (__bridge NSDictionary*)(foundTypeRef);
|
||||
if (!found) {
|
||||
return resolve(@(NO));
|
||||
}
|
||||
|
||||
// Found
|
||||
NSString *username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)];
|
||||
NSString *password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
|
||||
|
||||
return resolve(@{
|
||||
@"service": service,
|
||||
@"username": username,
|
||||
@"password": password
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void) notifyAuthenticationListener:(BOOL)willPresent {
|
||||
id<UIApplicationDelegate> appDelegate = [ UIApplication sharedApplication ].delegate;
|
||||
|
||||
if ([ appDelegate conformsToProtocol:@protocol(RNKeychainAuthenticationListener) ]) {
|
||||
((id<RNKeychainAuthenticationListener>)appDelegate).willPromptForAuthentication = willPresent;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RNKeychain
|
||||
|
||||
RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSString *service = serviceValue(options);
|
||||
|
||||
[self deletePasswordsForService:service];
|
||||
|
||||
// Create dictionary of parameters to add
|
||||
NSDictionary *attributes = @{
|
||||
NSDictionary *attributes = attributes = @{
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
|
||||
(__bridge NSString *)kSecAttrService: service,
|
||||
(__bridge NSString *)kSecAttrAccount: username,
|
||||
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
|
||||
(__bridge NSString *)kSecAttrAccessible: (__bridge id)accessibleValue(options)
|
||||
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
};
|
||||
|
||||
[self insertKeychainEntry:attributes withAccessGroup:accessGroupValue(options) resolver:resolve rejecter:reject];
|
||||
[self deletePasswordsForService:service];
|
||||
|
||||
[self insertKeychainEntry:attributes withOptions:options resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSString *service = serviceValue(options);
|
||||
|
||||
// secure compatibility with TouchId / Passcode secured stored items
|
||||
// http://stackoverflow.com/questions/42339000/ksecuseauthenticationuiskip-how-to-use-it
|
||||
// Silently skip any items that require user authentication. Only use this value with the SecItemCopyMatching function.
|
||||
NSString *authenticationPrompt = @"Authenticate to retrieve secret";
|
||||
if (options && options[kAuthenticationPromptMessage]) {
|
||||
authenticationPrompt = options[kAuthenticationPromptMessage];
|
||||
}
|
||||
|
||||
NSDictionary *query = @{
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
|
||||
@ -391,7 +314,7 @@ RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:
|
||||
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
|
||||
(__bridge NSString *)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUISkip
|
||||
(__bridge NSString *)kSecUseOperationPrompt: authenticationPrompt
|
||||
};
|
||||
|
||||
// Look up service in the keychain
|
||||
@ -443,26 +366,20 @@ RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsernam
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword),
|
||||
(__bridge NSString *)kSecAttrServer: server,
|
||||
(__bridge NSString *)kSecAttrAccount: username,
|
||||
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
|
||||
(__bridge NSString *)kSecAttrAccessible: (__bridge id)accessibleValue(options)
|
||||
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
};
|
||||
|
||||
[self insertKeychainEntry:attributes withAccessGroup:accessGroupValue(options) resolver:resolve rejecter:reject];
|
||||
[self insertKeychainEntry:attributes withOptions:options resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
// secure compatibility with TouchId / Passcode secured stored items
|
||||
// http://stackoverflow.com/questions/42339000/ksecuseauthenticationuiskip-how-to-use-it
|
||||
// Silently skip any items that require user authentication. Only use this value with the SecItemCopyMatching function.
|
||||
|
||||
NSDictionary *query = @{
|
||||
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword),
|
||||
(__bridge NSString *)kSecAttrServer: server,
|
||||
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
|
||||
(__bridge NSString *)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUISkip
|
||||
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne
|
||||
};
|
||||
|
||||
// Look up server in the keychain
|
||||
@ -502,7 +419,6 @@ RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString *)server withOptio
|
||||
}
|
||||
|
||||
return resolve(@(YES));
|
||||
|
||||
}
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
63
index.js
63
index.js
@ -41,12 +41,6 @@ type SecAccessible =
|
||||
| 'AccessibleAfterFirstUnlockThisDeviceOnly'
|
||||
| 'AccessibleAlwaysThisDeviceOnly';
|
||||
|
||||
type Options = {
|
||||
accessGroup?: string,
|
||||
accessible?: SecAccessible,
|
||||
service?: string,
|
||||
};
|
||||
|
||||
type SecAccessControl =
|
||||
| 'UserPresence'
|
||||
| 'BiometryAny'
|
||||
@ -58,9 +52,10 @@ type SecAccessControl =
|
||||
|
||||
type LAPolicy = 'Authentication' | 'AuthenticationWithBiometrics';
|
||||
|
||||
type SecureOptions = {
|
||||
type Options = {
|
||||
accessControl?: SecAccessControl,
|
||||
accessGroup?: string,
|
||||
accessible?: SecAccessible,
|
||||
authenticationPrompt?: string,
|
||||
authenticationType?: LAPolicy,
|
||||
service?: string,
|
||||
@ -72,7 +67,7 @@ type SecureOptions = {
|
||||
* @param {object} options LAPolicy option, iOS only
|
||||
* @return {Promise} Resolves to `true` when supported, otherwise `false`
|
||||
*/
|
||||
export function canImplyAuthentication(options?: SecureOptions): Promise {
|
||||
export function canImplyAuthentication(options?: Options): Promise {
|
||||
if (RNKeychainManager.canCheckAuthentication) {
|
||||
return Promise.reject(
|
||||
new Error(`canImplyAuthentication() is not supported on this platform`)
|
||||
@ -93,50 +88,6 @@ export function getSupportedBiometryType(): Promise {
|
||||
return RNKeychainManager.getSupportedBiometryType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the `username` and `password` combination securely - needs authentication to retrieve it.
|
||||
* @param {string} username Associated username or e-mail to be saved.
|
||||
* @param {string} password Associated password to be saved.
|
||||
* @param {object} options Keychain options, iOS only
|
||||
* @return {Promise} Resolves to `true` when successful
|
||||
*/
|
||||
export function setPasswordWithAuthentication(
|
||||
username: string,
|
||||
password: string,
|
||||
options?: SecureOptions
|
||||
): Promise {
|
||||
if (Platform.OS !== 'ios') {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`setPasswordWithAuthentication() is not supported on ${Platform.OS} yet`
|
||||
)
|
||||
);
|
||||
}
|
||||
return RNKeychainManager.setPasswordWithAuthentication(
|
||||
options,
|
||||
username,
|
||||
password
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches login combination for `service` - demands for authentication if necessary.
|
||||
* @param {string|object} serviceOrOptions Reverse domain name qualifier for the service, defaults to `bundleId` or an options object.
|
||||
* @return {Promise} Resolves to `{ service, username, password }` when successful
|
||||
*/
|
||||
export function getPasswordWithAuthentication(
|
||||
options?: SecureOptions
|
||||
): Promise {
|
||||
if (Platform.OS !== 'ios') {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`getPasswordWithAuthentication() is not supported on ${Platform.OS} yet`
|
||||
)
|
||||
);
|
||||
}
|
||||
return RNKeychainManager.getPasswordWithAuthentication(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the `username` and `password` combination for `server`.
|
||||
* @param {string} server URL to server.
|
||||
@ -185,7 +136,7 @@ export function resetInternetCredentials(
|
||||
return RNKeychainManager.resetInternetCredentialsForServer(server, options);
|
||||
}
|
||||
|
||||
function getOptionsArgument(serviceOrOptions?: string | KeychainOptions) {
|
||||
function getOptionsArgument(serviceOrOptions?: string | Options) {
|
||||
if (Platform.OS !== 'ios') {
|
||||
return typeof serviceOrOptions === 'object'
|
||||
? serviceOrOptions.service
|
||||
@ -206,7 +157,7 @@ function getOptionsArgument(serviceOrOptions?: string | KeychainOptions) {
|
||||
export function setGenericPassword(
|
||||
username: string,
|
||||
password: string,
|
||||
serviceOrOptions?: string | KeychainOptions
|
||||
serviceOrOptions?: string | Options
|
||||
): Promise {
|
||||
return RNKeychainManager.setGenericPasswordForOptions(
|
||||
getOptionsArgument(serviceOrOptions),
|
||||
@ -221,7 +172,7 @@ export function setGenericPassword(
|
||||
* @return {Promise} Resolves to `{ service, username, password }` when successful
|
||||
*/
|
||||
export function getGenericPassword(
|
||||
serviceOrOptions?: string | KeychainOptions
|
||||
serviceOrOptions?: string | Options
|
||||
): Promise {
|
||||
return RNKeychainManager.getGenericPasswordForOptions(
|
||||
getOptionsArgument(serviceOrOptions)
|
||||
@ -234,7 +185,7 @@ export function getGenericPassword(
|
||||
* @return {Promise} Resolves to `true` when successful
|
||||
*/
|
||||
export function resetGenericPassword(
|
||||
serviceOrOptions?: string | KeychainOptions
|
||||
serviceOrOptions?: string | Options
|
||||
): Promise {
|
||||
return RNKeychainManager.resetGenericPasswordForOptions(
|
||||
getOptionsArgument(serviceOrOptions)
|
||||
|
23
typings/react-native-keychain.d.ts
vendored
23
typings/react-native-keychain.d.ts
vendored
@ -11,7 +11,7 @@ declare module 'react-native-keychain' {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface SecureOptions {
|
||||
export interface Options {
|
||||
accessControl?: string;
|
||||
accessGroup?: string;
|
||||
authenticationPrompt?: string;
|
||||
@ -20,26 +20,17 @@ declare module 'react-native-keychain' {
|
||||
}
|
||||
|
||||
function canImplyAuthentication(
|
||||
options?: SecureOptions
|
||||
options?: Options
|
||||
): Promise<boolean>;
|
||||
|
||||
function getSupportedBiometryType(
|
||||
): Promise<string>;
|
||||
|
||||
function setSecurePassword(
|
||||
username: string,
|
||||
password: string,
|
||||
options?: SecureOptions
|
||||
): Promise<boolean>;
|
||||
|
||||
function getSecurePassword(
|
||||
options?: SecureOptions
|
||||
): Promise<boolean | {service: string, username: string, password: string}>;
|
||||
|
||||
function setInternetCredentials(
|
||||
server: string,
|
||||
username: string,
|
||||
password: string
|
||||
password: string,
|
||||
options?: Options
|
||||
): Promise<void>;
|
||||
|
||||
function getInternetCredentials(
|
||||
@ -53,15 +44,15 @@ declare module 'react-native-keychain' {
|
||||
function setGenericPassword(
|
||||
username: string,
|
||||
password: string,
|
||||
service?: string
|
||||
options?: Options
|
||||
): Promise<boolean>;
|
||||
|
||||
function getGenericPassword(
|
||||
service?: string
|
||||
options?: Options
|
||||
): Promise<boolean | {service: string, username: string, password: string}>;
|
||||
|
||||
function resetGenericPassword(
|
||||
service?: string
|
||||
options?: Options
|
||||
): Promise<boolean>
|
||||
|
||||
function requestSharedWebCredentials (
|
||||
|
Loading…
x
Reference in New Issue
Block a user