From fe4642c3e09f1c088a4c5606a62ff94833347a25 Mon Sep 17 00:00:00 2001 From: Joel Arvidsson Date: Wed, 20 May 2015 20:39:52 +0200 Subject: [PATCH] Added first implementation. --- .editorconfig | 13 +++++ README.md | 40 +++++++++++++ RNKeychainManager/RNKeychainManager.m | 65 +++++++++++++++++++++ index.ios.js | 81 ++++++++++++++++++++++++++- 4 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/README.md b/README.md index 5f198a7..ee34b57 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ # react-native-keychain Keychain Access for React Native + +Currently functionality is limited to just storing internet passwords. More to come... + +## Installation + +* `$ npm install react-native-keychain` +* Right click on Libraries, select **Add files to "…"** and select `node_modules/react-native-keychain/RNKeychain.xcodeproj` +* Select your project and under **Build Phases** -> **Link Binary With Libraries**, press the + and select `libRNKeychain.a`. + + +## Usage + +```js +var Keychain = require('Keychain'); + +var server = 'http://facebook.com'; +var username = 'zuck'; +var password = 'poniesRgr8'; +Keychain + .setInternetCredentials(server, username, password) + .then(function() { + console.log('Credentials saved successfully!') + }); + +Keychain + .getInternetCredentials(server) + .then(function(credentials) { + console.log('Credentials successfully loaded', credentials) + }); + +Keychain + .resetInternetCredentials(server) + .then(function(credentials) { + console.log('Credentials successfully deleted') + }); + +``` + +## License +MIT © Joel Arvidsson 2015 diff --git a/RNKeychainManager/RNKeychainManager.m b/RNKeychainManager/RNKeychainManager.m index b922ed5..52112bd 100644 --- a/RNKeychainManager/RNKeychainManager.m +++ b/RNKeychainManager/RNKeychainManager.m @@ -17,4 +17,69 @@ RCT_EXPORT_MODULE(); +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) { + NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil]; + callback(@[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; + CFDictionaryRef foundCF; + OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) dict, (CFTypeRef*)&foundCF); + + found = (__bridge NSDictionary*)(foundCF); + 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]; + + if (osStatus) { + NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil]; + callback(@[error]); + } + + callback(@[[NSNull null], username, password]); + +} + +RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(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]; + + // Remove any old values from the keychain + OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict); + if (osStatus) { + NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil]; + callback(@[error]); + } + + callback(@[[NSNull null]]); + +} + @end diff --git a/index.ios.js b/index.ios.js index 2a08190..cd8cf1a 100644 --- a/index.ios.js +++ b/index.ios.js @@ -1,12 +1,89 @@ /** - * @providesModule KeychainIOS + * @providesModule Keychain */ 'use strict'; -var RNKeychainManager = require('NativeModules').RNKeychainManager; +var NativeModules = require('NativeModules'); +var RNKeychainManager = NativeModules.RNKeychainManager; var Keychain = { + /** + * Saves the `username` and `password` combination for `server` + * and calls `callback` with an `Error` if there is any. + * Returns a `Promise` object. + */ + setInternetCredentials: function( + server: string, + username: string, + password: string, + callback?: ?(error: ?Error) => void + ): Promise { + return new Promise((resolve, reject) => { + RNKeychainManager.setInternetCredentialsForServer(server, username, password, function(err) { + callback && callback((err && convertError(err)) || null); + if (err) { + reject(convertError(err)); + } else { + resolve(); + } + }); + }); + }, + + /** + * Fetches login combination for `server` 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. + */ + getInternetCredentials: function( + server: string, + callback?: ?(error: ?Error, result: ?string) => void + ): Promise { + return new Promise((resolve, reject) => { + RNKeychainManager.getInternetCredentialsForServer(server, function(err, username, password) { + err = convertError(err); + if(!err && arguments.length === 1) { + err = new Error('No keychain entry found for server "' + server + '"'); + } + callback && callback((err && convertError(err)) || null, username, password); + if (err) { + reject(convertError(err)); + } else { + resolve({ username, password }); + } + }); + }); + }, + + /** + * Deletes all keychain entries for `server` and calls `callback` with an `Error` if there is any. + * Returns a `Promise` object. + */ + resetInternetCredentials: function( + server: string, + callback?: ?(error: ?Error) => void + ): Promise { + return new Promise((resolve, reject) => { + RNKeychainManager.resetInternetCredentialsForServer(server, function(err) { + callback && callback((err && convertError(err)) || null); + if (err) { + reject(convertError(err)); + } else { + resolve(); + } + }); + }); + }, }; +function convertError(err) { + if (!err) { + return null; + } + var out = new Error(err.message); + out.key = err.key; + return out; +} + module.exports = Keychain;