Added generic password support.

This commit is contained in:
Joel Arvidsson 2015-05-29 18:25:56 +02:00
parent 24045a74dd
commit 75446cb734
3 changed files with 189 additions and 20 deletions

View File

@ -1,7 +1,7 @@
# react-native-keychain
Keychain Access for React Native
Currently functionality is limited to just storing internet passwords. More to come...
Currently functionality is limited to just storing internet and generic passwords. Wider exposure of the underlying API coming.
## Installation
@ -17,25 +17,46 @@ See `KeychainExample` for fully working project example.
```js
var Keychain = require('Keychain');
var server = 'http://facebook.com';
var username = 'zuck';
var password = 'poniesRgr8';
// Generic Password, service argument optional
Keychain
.setGenericPassword(username, password)
.then(function() {
console.log('Credentials saved successfully!');
});
Keychain
.getGenericPassword()
.then(function(credentials) {
console.log('Credentials successfully loaded for user ' + credentials.username);
});
Keychain
.resetGenericPassword()
.then(function() {
console.log('Credentials successfully deleted');
});
// Internet Password, server argument required
var server = 'http://facebook.com';
Keychain
.setInternetCredentials(server, username, password)
.then(function() {
console.log('Credentials saved successfully!')
console.log('Credentials saved successfully!');
});
Keychain
.getInternetCredentials(server)
.then(function(credentials) {
console.log('Credentials successfully loaded', credentials)
console.log('Credentials successfully loaded for user ' + credentials.username);
});
Keychain
.resetInternetCredentials(server)
.then(function(credentials) {
console.log('Credentials successfully deleted')
.then(function() {
console.log('Credentials successfully deleted');
});
```

View File

@ -71,17 +71,21 @@ NSDictionary * makeError(NSError *error)
}
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername:(NSString*)username withPassword:(NSString*)password callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(setGenericPasswordForService:(NSString*)service withUsername:(NSString*)username withPassword:(NSString*)password callback:(RCTResponseSenderBlock)callback){
if(service == nil) {
service = [[NSBundle mainBundle] bundleIdentifier];
}
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, nil];
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, nil];
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
// Create dictionary of parameters to add
NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, passwordData, kSecValueData, username, kSecAttrAccount, nil];
dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, passwordData, kSecValueData, username, kSecAttrAccount, nil];
// Try to save to keychain
osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
@ -89,16 +93,19 @@ RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
}
callback(@[[NSNull null]]);
}
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service callback:(RCTResponseSenderBlock)callback){
if(service == nil) {
service = [[NSBundle mainBundle] bundleIdentifier];
}
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Look up server in the keychain
NSDictionary* found = nil;
CFTypeRef foundTypeRef = NULL;
@ -119,7 +126,77 @@ RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server callback:(RC
NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
callback(@[[NSNull null], username, password]);
}
RCT_EXPORT_METHOD(resetGenericPasswordForService:(NSString*)service callback:(RCTResponseSenderBlock)callback){
if(service == nil) {
service = [[NSBundle mainBundle] bundleIdentifier];
}
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
}
callback(@[[NSNull null]]);
}
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername:(NSString*)username withPassword:(NSString*)password callback:(RCTResponseSenderBlock)callback){
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, nil];
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
// Create dictionary of parameters to add
NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, passwordData, kSecValueData, username, kSecAttrAccount, nil];
// Try to save to keychain
osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
}
callback(@[[NSNull null]]);
}
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server callback:(RCTResponseSenderBlock)callback){
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Look up server in the keychain
NSDictionary* found = nil;
CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) dict, (CFTypeRef*)&foundTypeRef);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
}
found = (__bridge NSDictionary*)(foundTypeRef);
if (!found) {
return callback(@[[NSNull null]]);
}
// Found
NSString* username = (NSString*) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
callback(@[[NSNull null], username, password]);
}
RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString*)server callback:(RCTResponseSenderBlock)callback){
@ -133,7 +210,7 @@ RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString*)server callback:(
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
}
callback(@[[NSNull null]]);
}

View File

@ -56,7 +56,8 @@ var Keychain = {
},
/**
* Deletes all keychain entries for `server` and calls `callback` with an `Error` if there is any.
* Deletes all internet password keychain entries for `server` and calls `callback` with an
* `Error` if there is any.
* Returns a `Promise` object.
*/
resetInternetCredentials: function(
@ -75,6 +76,76 @@ var Keychain = {
});
},
/**
* Saves the `username` and `password` combination for `service` (defaults to `bundleId`)
* and calls `callback` with an `Error` if there is any.
* Returns a `Promise` object.
*/
setGenericPassword: function(
username: string,
password: string,
service?: string,
callback?: ?(error: ?Error) => void
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.setGenericPasswordForService(service, username, password, function(err) {
callback && callback((err && convertError(err)) || null);
if (err) {
reject(convertError(err));
} else {
resolve();
}
});
});
},
/**
* Fetches login combination for `service` (defaults to `bundleId`) as an object with the format
* `{ username, password }` and passes the result to `callback`, along with an `Error` if
* there is any.
* Returns a `Promise` object.
*/
getGenericPassword: function(
service?: string,
callback?: ?(error: ?Error, result: ?string) => void
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.getGenericPasswordForService(service, function(err, username, password) {
err = convertError(err);
if(!err && arguments.length === 1) {
err = new Error('No keychain entry found' + (service ? ' for service "' + service + '"' : ''));
}
callback && callback((err && convertError(err)) || null, username, password);
if (err) {
reject(convertError(err));
} else {
resolve({ username, password });
}
});
});
},
/**
* Deletes all generic password keychain entries for `service` (defaults to `bundleId`) and calls
* `callback` with an `Error` if there is any.
* Returns a `Promise` object.
*/
resetGenericPassword: function(
service?: string,
callback?: ?(error: ?Error) => void
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.resetGenericPasswordForService(service, function(err) {
callback && callback((err && convertError(err)) || null);
if (err) {
reject(convertError(err));
} else {
resolve();
}
});
});
},
};
function convertError(err) {