From c85a445a4cdb385960820587bd6d56a24004e659 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 15:02:01 -0500 Subject: [PATCH 1/8] Rewritten in Objective-C + improved tests --- ios/EncryptedStorage-Bridging-Header.h | 8 -- .../project.pbxproj | 14 +- ios/RNEncryptedStorage.h | 14 ++ ios/RNEncryptedStorage.m | 113 +++++++++++---- ios/RNEncryptedStorage.swift | 88 ------------ lib/EncryptedStorage.js | 15 +- lib/EncryptedStorage.test.js | 134 +++++++++--------- package.json | 2 +- react-native-encrypted-storage.podspec | 1 - 9 files changed, 184 insertions(+), 205 deletions(-) delete mode 100644 ios/EncryptedStorage-Bridging-Header.h create mode 100644 ios/RNEncryptedStorage.h delete mode 100644 ios/RNEncryptedStorage.swift diff --git a/ios/EncryptedStorage-Bridging-Header.h b/ios/EncryptedStorage-Bridging-Header.h deleted file mode 100644 index 5ef3d27..0000000 --- a/ios/EncryptedStorage-Bridging-Header.h +++ /dev/null @@ -1,8 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - -#import "React/RCTBridgeModule.h" -#import "React/RCTViewManager.h" -#import "React/RCTUIManager.h" -#import "React/RCTEventEmitter.h" diff --git a/ios/EncryptedStorage.xcodeproj/project.pbxproj b/ios/EncryptedStorage.xcodeproj/project.pbxproj index adeb52b..c367c98 100644 --- a/ios/EncryptedStorage.xcodeproj/project.pbxproj +++ b/ios/EncryptedStorage.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 1CAF8F2F23F383AD00FBCBB2 /* RNEncryptedStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAF8F2E23F383AD00FBCBB2 /* RNEncryptedStorage.swift */; }; B3E7B58A1CC2AC0600A0062D /* RNEncryptedStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNEncryptedStorage.m */; }; /* End PBXBuildFile section */ @@ -25,8 +24,7 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libEncryptedStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEncryptedStorage.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 1CAF8F2D23F383AC00FBCBB2 /* EncryptedStorage-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "EncryptedStorage-Bridging-Header.h"; sourceTree = ""; }; - 1CAF8F2E23F383AD00FBCBB2 /* RNEncryptedStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNEncryptedStorage.swift; sourceTree = ""; }; + 1C15BF842407BE0E004846F8 /* RNEncryptedStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNEncryptedStorage.h; sourceTree = ""; }; B3E7B5891CC2AC0600A0062D /* RNEncryptedStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNEncryptedStorage.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -52,10 +50,9 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 1CAF8F2E23F383AD00FBCBB2 /* RNEncryptedStorage.swift */, + 1C15BF842407BE0E004846F8 /* RNEncryptedStorage.h */, B3E7B5891CC2AC0600A0062D /* RNEncryptedStorage.m */, 134814211AA4EA7D00B7C361 /* Products */, - 1CAF8F2D23F383AC00FBCBB2 /* EncryptedStorage-Bridging-Header.h */, ); sourceTree = ""; }; @@ -117,7 +114,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1CAF8F2F23F383AD00FBCBB2 /* RNEncryptedStorage.swift in Sources */, B3E7B58A1CC2AC0600A0062D /* RNEncryptedStorage.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -225,8 +221,9 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../React/**", "$(SRCROOT)/../../react-native/React/**", + "$(SRCROOT)/../../react-native/Libraries\n$(SRCROOT)/../../react-native/Libraries\n$(SRCROOT)/../../react-native/Libraries/**", ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; @@ -246,8 +243,9 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../React/**", "$(SRCROOT)/../../react-native/React/**", + "$(SRCROOT)/../../react-native/Libraries\n$(SRCROOT)/../../react-native/Libraries\n$(SRCROOT)/../../react-native/Libraries/**", ); LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; diff --git a/ios/RNEncryptedStorage.h b/ios/RNEncryptedStorage.h new file mode 100644 index 0000000..3c691c3 --- /dev/null +++ b/ios/RNEncryptedStorage.h @@ -0,0 +1,14 @@ +// +// RNEncryptedStorage.h +// EncryptedStorage +// +// Created by Yanick Bélanger on 2020-02-27. +// Copyright © 2020 Facebook. All rights reserved. +// + +#import +#import + +@interface RNEncryptedStorage : NSObject + +@end diff --git a/ios/RNEncryptedStorage.m b/ios/RNEncryptedStorage.m index f594ca6..ddf7ade 100644 --- a/ios/RNEncryptedStorage.m +++ b/ios/RNEncryptedStorage.m @@ -6,29 +6,94 @@ // Copyright © 2020 Facebook. All rights reserved. // -#import -#import "React/RCTBridgeModule.h" - -@interface RCT_EXTERN_MODULE(RNEncryptedStorage, NSObject) - -RCT_EXTERN_METHOD( - setItem: (NSString *)key - value: (NSString *)value - resolver: (RCTPromiseResolveBlock)resolve - rejecter: (RCTPromiseRejectBlock)reject -); - -RCT_EXTERN_METHOD( - getItem: (NSString *)key - resolver: (RCTPromiseResolveBlock)resolve - rejecter: (RCTPromiseRejectBlock)reject -); - -RCT_EXTERN_METHOD( - removeItem: (NSString *)key - resolver: (RCTPromiseResolveBlock)resolve - rejecter: (RCTPromiseRejectBlock)reject -); +#import "RNEncryptedStorage.h" +#import +#import -@end +@implementation RNEncryptedStorage + ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(setItem:(NSString *)key withValue:(NSString *)value resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +{ + NSData* dataFromValue = [value dataUsingEncoding:NSUTF8StringEncoding]; + + if (dataFromValue == nil) { + NSError* error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier] code:0 userInfo: nil]; + return reject(@"parse_error", @"An error occured while parsing value", error); + } + + // Prepares the insert query structure + NSDictionary* storeQuery = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : key, + (__bridge id)kSecValueData : dataFromValue + }; + + // Deletes the existing item prior to inserting the new one + SecItemDelete((__bridge CFDictionaryRef)storeQuery); + + OSStatus insertStatus = SecItemAdd((__bridge CFDictionaryRef)storeQuery, nil); + + if (insertStatus == noErr) { + resolve(value); + } + + else { + NSError* error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier] code:1 userInfo: nil]; + reject(@"insert_error", @"An error occured while saving value", error); + } +} +RCT_EXPORT_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +{ + NSDictionary* getQuery = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : key, + (__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue, + (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne + }; + + CFTypeRef dataRef = NULL; + OSStatus getStatus = SecItemCopyMatching((__bridge CFDictionaryRef)getQuery, &dataRef); + + if (getStatus == errSecSuccess) { + NSString* storedValue = [[NSString alloc] initWithData: (__bridge NSData*)dataRef encoding: NSUTF8StringEncoding]; + resolve(storedValue); + } + + else if (getStatus == errSecItemNotFound) { + resolve(nil); + } + + else { + NSError* error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier] code:2 userInfo: nil]; + reject(@"retrieve_error", @"An error occured while retrieving value", error); + } +} + +RCT_EXPORT_METHOD(removeItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +{ + NSDictionary* removeQuery = @{ + (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrAccount : key, + (__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue + }; + + OSStatus removeStatus = SecItemDelete((__bridge CFDictionaryRef)removeQuery); + + if (removeStatus == noErr) { + resolve(key); + } + + else { + NSError* error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier] code:3 userInfo: nil]; + reject(@"remove_error", @"An error occured while removing", error); + } +} +@end diff --git a/ios/RNEncryptedStorage.swift b/ios/RNEncryptedStorage.swift deleted file mode 100644 index 0eb558c..0000000 --- a/ios/RNEncryptedStorage.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// RNEncryptedStorage.swift -// EncryptedStorage -// -// Created by Yanick Bélanger on 2020-02-11. -// Copyright © 2020 Facebook. All rights reserved. -// - -import Foundation -import Security - -@objc(RNEncryptedStorage) -class RNEncryptedStorage: NSObject { - - @objc - static func requiresMainQueueSetup() -> Bool { - return false; - } - - @objc func setItem(_ key : String, value : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { - guard let dataFromValue = value.data(using: .utf8, allowLossyConversion: false) else { - return reject("Error parsing value for key \(key)", nil, nil); - } - - // Prepares the insert query structure - let storeQuery : [String : Any] = [ - kSecClass as String : kSecClassGenericPassword, - kSecAttrAccount as String : key, - kSecValueData as String : dataFromValue - ]; - - // Deletes the existing item prior to inserting the new one - SecItemDelete(storeQuery as CFDictionary); - - let status = SecItemAdd(storeQuery as CFDictionary, nil); - - if (status == noErr) { - resolve(value); - } - - else { - reject("An error occured while saving \(key)", nil, nil); - } - } - - @objc - func getItem(_ key : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject : RCTPromiseRejectBlock) { - let retrieveQuery : [String : Any] = [ - kSecClass as String : kSecClassGenericPassword, - kSecAttrAccount as String : key, - kSecReturnData as String : kCFBooleanTrue!, - kSecMatchLimit as String : kSecMatchLimitOne - ]; - - var dataRef : AnyObject? = nil; - - let status = SecItemCopyMatching(retrieveQuery as CFDictionary, &dataRef); - - if (status == noErr) { - let stringValue = String(data : (dataRef as! Data?)!, encoding: .utf8)!; - resolve(stringValue); - } - - else { - reject("An error occured while retrieving \(key)", nil, nil); - } - } - - @objc - func removeItem(_ key : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject : RCTPromiseRejectBlock) { - let removeQuery : [String : Any] = [ - kSecClass as String : kSecClassGenericPassword, - kSecAttrAccount as String : key, - kSecReturnData as String : kCFBooleanTrue! - ]; - - let status = SecItemDelete(removeQuery as CFDictionary); - - if (status == noErr) { - resolve(key); - } - - else { - reject("An error occured while removing \(key)", nil, nil); - } - } -} - diff --git a/lib/EncryptedStorage.js b/lib/EncryptedStorage.js index c2f637b..0dc09d1 100644 --- a/lib/EncryptedStorage.js +++ b/lib/EncryptedStorage.js @@ -15,8 +15,8 @@ const EncryptedStorage = { return new Promise(async (resolve, reject) => { try { await RNEncryptedStorage.setItem(key, value); - cb && cb(null); - resolve(null); + cb && cb(undefined); + resolve(undefined); } catch (error) { cb && cb(error); reject(error); @@ -32,11 +32,11 @@ const EncryptedStorage = { return new Promise(async (resolve, reject) => { try { const value = await RNEncryptedStorage.getItem(key); - cb && cb(null, value); + cb && cb(undefined, value); resolve(value); } catch (error) { - cb && cb(error, null); - reject(error); + cb && cb(error, undefined); + reject(error); } }); }, @@ -49,9 +49,10 @@ const EncryptedStorage = { return new Promise(async (resolve, reject) => { try { await RNEncryptedStorage.removeItem(key); - cb && cb(null); - resolve(null); + cb && cb(undefined); + resolve(undefined); } catch (error) { + cb && cb(error); reject(error); } }); diff --git a/lib/EncryptedStorage.test.js b/lib/EncryptedStorage.test.js index f918616..4986b4e 100644 --- a/lib/EncryptedStorage.test.js +++ b/lib/EncryptedStorage.test.js @@ -1,128 +1,126 @@ -const EncryptedStorage = require('./EncryptedStorage'); -const { NativeModules } = require('react-native'); +const EncryptedStorage = require("./EncryptedStorage"); +const { NativeModules } = require("react-native"); const { RNEncryptedStorage } = NativeModules; -describe('lib/EncryptedStorage', () => { +describe("lib/EncryptedStorage", () => { afterEach(() => { jest.clearAllMocks(); }); - describe('using Promises', () => { - describe('setItem(key, value)', () => { - it('should return no errors if it could store the value', async () => { - const storeError = await EncryptedStorage.setItem('key', 'value'); - expect(storeError).toBe(null); + describe("using Promises", () => { + describe("setItem(key, value)", () => { + it("should return no errors if it could store the value", () => { + return expect(EncryptedStorage.setItem("key", "value")) + .resolves + .toBeUndefined(); }); - it('should reject with an error if it could not store the value', async () => { - RNEncryptedStorage.setItem.mockImplementationOnce(() => Promise.reject(new Error('Set error'))); - - try { - await EncryptedStorage.setItem('key', 'value'); - } catch (error) { - expect(error.message).toBe('Set error'); - } + it("should reject with an error if it could not store the value", () => { + RNEncryptedStorage.setItem.mockImplementationOnce(() => Promise.reject(new Error("Set error"))); + + return expect(EncryptedStorage.setItem("key", "value")) + .rejects + .toThrow("Set error"); }); }); - describe('getItem(key)', () => { - it('should return the value if it could be retrieved succesfully', async () => { - const item = await EncryptedStorage.getItem('key'); - expect(item).toEqual('{ "foo": 1 }'); + describe("getItem(key)", () => { + it("should return the value if it could be retrieved succesfully", () => { + return expect(EncryptedStorage.getItem("key")) + .resolves + .toEqual("{ \"foo\": 1 }"); }); - it('should return null if no value was found for that key', async () => { + it("should return null if no value was found for that key", () => { RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.resolve(undefined)); - const item = await EncryptedStorage.getItem('key'); - expect(item).toEqual(undefined); + return expect(EncryptedStorage.getItem("key")) + .resolves + .toBeUndefined(); }); - it('should reject with an error if it could not retrieve the value', async () => { + it("should reject with an error if it could not retrieve the value", () => { RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.reject(new Error("Get error"))); - try { - await EncryptedStorage.getItem('key'); - } catch (error) { - expect(error.message).toBe('Get error'); - } + return expect(EncryptedStorage.getItem("key")) + .rejects + .toThrow("Get error"); }); }); - describe('removeItem(key)', () => { - it('should return no error if it could removed the stored value', async () => { - const removeResult = await EncryptedStorage.removeItem('key'); - expect(removeResult).toBe(null); + describe("removeItem(key)", () => { + it("should return no error if it could removed the stored value", () => { + return expect(EncryptedStorage.removeItem("key")) + .resolves + .toBeUndefined(); }); - it('should throw an error if it could not retrieve the stored value', async () => { + it("should throw an error if it could not retrieve the stored value", async () => { RNEncryptedStorage.removeItem.mockImplementationOnce(() => Promise.reject(new Error("Remove error"))); - try { - await EncryptedStorage.removeItem('key'); - } catch (error) { - expect(error.message).toBe('Remove error'); - } + return expect(EncryptedStorage.removeItem("key")) + .rejects + .toThrow("Remove error"); }); }); }); - describe('using callbacks', () => { - describe('setItem(key, value)', () => { - it('should return no errors if it could store the value', () => { - EncryptedStorage.setItem('key', 'value', err => { - expect(err).toBe(null); + describe("using callbacks", () => { + describe("setItem(key, value)", () => { + it("should return no errors if it could store the value", () => { + EncryptedStorage.setItem("key", "value", error => { + expect(error).toBeUndefined(); }); }); - it('should reject with an error if it could not store the value', () => { - RNEncryptedStorage.setItem.mockImplementationOnce(() => Promise.reject(new Error('Set error'))); + it("should reject with an error if it could not store the value", () => { + RNEncryptedStorage.setItem.mockImplementationOnce(() => Promise.reject(new Error("Set error"))); - EncryptedStorage.setItem('key', 'value', err => { - expect(err.message).toBe('Set error'); + EncryptedStorage.setItem("key", "value", error => { + expect(error.message).toEqual("Set error"); }); }); }); - describe('getItem(key)', () => { - it('should return the value if it could be retrieved succesfully', () => { - EncryptedStorage.getItem('key', (err, value) => { - expect(err).toBe(null); - expect(value).toEqual('{ "foo": 1 }'); + describe("getItem(key)", () => { + it("should return the value if it could be retrieved succesfully", () => { + EncryptedStorage.getItem("key", (error, value) => { + expect(error).toBeUndefined(); + expect(value).toEqual("{ \"foo\": 1 }"); }); }); - it('should return null if no value was found for that key', () => { + it("should return null if no value was found for that key", () => { RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.resolve(undefined)); - EncryptedStorage.getItem('key', (err, value) => { - expect(err).toBe(null); - expect(value).toBe(undefined); + EncryptedStorage.getItem("key", (error, value) => { + expect(error).toBeUndefined(); + expect(value).toBeUndefined(); }); }); - it('should reject with an error if it could not retrieve the value', () => { + it("should reject with an error if it could not retrieve the value", () => { RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.reject(new Error("Get error"))); - EncryptedStorage.getItem('key', (err, value) => { - expect(err.message).toEqual('Get error'); - expect(value).toBe(undefined); + EncryptedStorage.getItem("key", (error, value) => { + expect(error.message).toEqual("Get error"); + expect(value).toBeUndefined(); }); }); }); - describe('removeItem(key)', () => { - it('should return no error if it could removed the stored value', () => { - EncryptedStorage.removeItem('key', err => { - expect(err).toBe(null); + describe("removeItem(key)", () => { + it("should return no error if it could remove the stored value", () => { + EncryptedStorage.removeItem("key", error => { + expect(error).toBeUndefined(); }); }); - it('should throw an error if it could not retrieve the stored value', () => { + it("should throw an error if it could not retrieve the stored value", () => { RNEncryptedStorage.removeItem.mockImplementationOnce(() => Promise.reject(new Error("Remove error"))); - EncryptedStorage.removeItem('key', err => { - expect(err.message).toBe('Remove error'); + EncryptedStorage.removeItem("key", error => { + expect(error.message).toEqual("Remove error"); }); }); }); diff --git a/package.json b/package.json index 234d16a..7488845 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-encrypted-storage", "title": "React Native Encrypted Storage", - "version": "2.0.0", + "version": "2.0.2", "description": "A React Native wrapper over SharedPreferences and Keychain to provide a secure alternative to Async Storage", "main": "./lib/index.js", "types": "./types/index.d.ts", diff --git a/react-native-encrypted-storage.podspec b/react-native-encrypted-storage.podspec index 67878d2..d6d8f13 100644 --- a/react-native-encrypted-storage.podspec +++ b/react-native-encrypted-storage.podspec @@ -9,7 +9,6 @@ Pod::Spec.new do |s| s.description = <<-DESC React Native wrapper around SharedPreferences and Keychain to provide a secure alternative to Async Storage DESC - s.swift_version = "5.0" s.homepage = "https://github.com/emeraldsanto/react-native-encrypted-storage" s.license = "MIT" s.authors = { "Yanick" => "yanick.belanger@yahoo.com" } From 791481132669174fcf6044394883a4a806c75bc5 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:14:02 -0500 Subject: [PATCH 2/8] Migrated to Java --- android/.classpath | 6 + android/.project | 8 +- android/build.gradle | 7 -- .../RNEncryptedStorageModule.java | 103 ++++++++++++++++++ .../RNEncryptedStorageModule.kt | 77 ------------- .../RNEncryptedStoragePackage.java | 26 +++++ .../RNEncryptedStoragePackage.kt | 21 ---- types/index.d.ts | 4 +- 8 files changed, 144 insertions(+), 108 deletions(-) create mode 100644 android/.classpath create mode 100644 android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java delete mode 100644 android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.kt create mode 100644 android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java delete mode 100644 android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.kt diff --git a/android/.classpath b/android/.classpath new file mode 100644 index 0000000..32d6691 --- /dev/null +++ b/android/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/.project b/android/.project index 0e0a1ba..675ef59 100644 --- a/android/.project +++ b/android/.project @@ -1,10 +1,15 @@ - android_ + android Project android_ created by Buildship. + + org.eclipse.jdt.core.javabuilder + + + org.eclipse.buildship.core.gradleprojectbuilder @@ -12,6 +17,7 @@ + org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature diff --git a/android/build.gradle b/android/build.gradle index d7cb44b..299685d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,12 +22,9 @@ def safeExtGet(prop, fallback) { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-android' apply plugin: 'maven' buildscript { - ext.kotlin_version = '1.3.61' // The Android Gradle plugin is only required when opening the android folder stand-alone. // This avoids unnecessary downloads and potential conflicts when the library is included as a // module dependency in an application project. @@ -44,9 +41,6 @@ buildscript { repositories { mavenCentral() } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } } apply plugin: 'com.android.library' @@ -85,7 +79,6 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // From node_modules implementation "androidx.security:security-crypto:1.0.0-alpha02" } diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java new file mode 100644 index 0000000..9dac78b --- /dev/null +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java @@ -0,0 +1,103 @@ +package com.emeraldsanto.encryptedstorage; + +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.util.Log; + +import androidx.security.crypto.EncryptedSharedPreferences; +import androidx.security.crypto.MasterKeys; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +public class RNEncryptedStorageModule extends ReactContextBaseJavaModule { + + private static String NATIVE_MODULE_NAME = "RNEncryptedStorage"; + private static String SHARED_PREFERENCES_FILENAME = "RN_ENCRYPTED_STORAGE_SHARED_PREF"; + + private SharedPreferences sharedPreferences; + + public RNEncryptedStorageModule(ReactApplicationContext context) { + super(context); + + try { + this.sharedPreferences = EncryptedSharedPreferences.create( + RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME, + MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), + context, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ); + } + + catch (Exception ex) { + Log.w(RNEncryptedStorageModule.NATIVE_MODULE_NAME, String.format("Could not initialize SharedPreferences - %s", ex.getLocalizedMessage())); + } + } + + @Override + public String getName() { + return RNEncryptedStorageModule.NATIVE_MODULE_NAME; + } + + @ReactMethod + public void setItem(String key, String value, Promise promise) { + if (this.sharedPreferences == null) { + promise.reject(new NullPointerException("Could not initialize SharedPreferences")); + return; + } + + SharedPreferences.Editor editor = this.sharedPreferences.edit(); + editor.putString(key, value); + boolean saved = editor.commit(); + + if (saved) { + promise.resolve(value); + } + + else { + promise.reject(new Exception(String.format("An error occured while saving %s", key))); + } + } + + @ReactMethod + public void getItem(String key, Promise promise) { + if (this.sharedPreferences == null) { + promise.reject(new NullPointerException("Could not initialize SharedPreferences")); + return; + } + + String value = this.sharedPreferences.getString(key, ""); + + if (value == null || value.isEmpty()) { + promise.reject(new Resources.NotFoundException(String.format("An error occured while retrieving %s", key))); + } + + else { + promise.resolve(value); + } + } + + @ReactMethod + public void removeItem(String key, Promise promise) { + if (this.sharedPreferences == null) { + promise.reject(new NullPointerException("Could not initialize SharedPreferences")); + return; + } + + SharedPreferences.Editor editor = this.sharedPreferences.edit(); + editor.remove(key); + boolean saved = editor.commit(); + + if (saved) { + promise.resolve(key); + } + + else { + promise.reject(new Exception(String.format("An error occured while removing %s", key))); + } + } +} diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.kt b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.kt deleted file mode 100644 index 1ecbdb6..0000000 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.emeraldsanto.encryptedstorage - -import android.content.SharedPreferences -import android.content.res.Resources -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKeys -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod - -class RNEncryptedStorageModule(context : ReactApplicationContext) : ReactContextBaseJavaModule(context) { - - companion object { - const val NATIVE_MODULE_NAME = "RNEncryptedStorage"; - const val SHARED_PREFERENCES_FILENAME = "RN_ENCRYPTED_STORAGE_SHARED_PREF"; - } - - private val sharedPreferences : SharedPreferences; - - init { - this.sharedPreferences = EncryptedSharedPreferences.create( - RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME, - MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), - context, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ); - } - - override fun getName(): String { - return RNEncryptedStorageModule.NATIVE_MODULE_NAME - } - - @ReactMethod - fun setItem(key : String, value : String, promise : Promise) { - val editor = this.sharedPreferences.edit(); - editor.putString(key, value); - val saved = editor.commit(); - - if (saved) { - promise.resolve(value); - } - - else { - promise.reject(Exception("An error occured while saving $key")); - } - } - - @ReactMethod - fun getItem(key : String, promise : Promise) { - val value = this.sharedPreferences.getString(key, ""); - - if (value.isNullOrEmpty()) { - promise.reject(Resources.NotFoundException("An error occured while retrieving $key")); - } - - else { - promise.resolve(value); - } - } - - @ReactMethod - fun removeItem(key : String, promise : Promise) { - val editor = this.sharedPreferences.edit(); - editor.remove(key); - val saved = editor.commit(); - - if (saved) { - promise.resolve(key); - } - - else { - promise.reject(Exception("An error occured while removing $key")); - } - } -} \ No newline at end of file diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java new file mode 100644 index 0000000..488d485 --- /dev/null +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java @@ -0,0 +1,26 @@ +package com.emeraldsanto.encryptedstorage; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RNEncryptedStoragePackage extends ReactPackage { + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new RNEncryptedStorageModule(reactContext)); + + return modules; + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.kt b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.kt deleted file mode 100644 index d6580b5..0000000 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.emeraldsanto.encryptedstorage - -import android.view.View -import com.facebook.react.ReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ReactShadowNode -import com.facebook.react.uimanager.ViewManager - -class RNEncryptedStoragePackage : ReactPackage { - - override fun createNativeModules(reactContext: ReactApplicationContext): MutableList { - return mutableListOf( - RNEncryptedStorageModule(reactContext) - ); - } - - override fun createViewManagers(reactContext: ReactApplicationContext): MutableList>> { - return mutableListOf(); - } -} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 53fca78..6386f2c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -8,13 +8,13 @@ declare module "react-native-encrypted-storage" { * @param {string} key - A string that will be associated to the value for later retrieval. * @param {Object} value - The data to store. */ - setItem(key : string, value : Object, callback? : (error? : Error) => void) : Promise; + setItem(key : string, value : string, callback? : (error? : Error) => void) : Promise; /** * Retrieves data from the disk, using SharedPreferences or KeyChain, depending on the platform and returns it. * @param {string} key - A string that is associated to a value. */ - getItem(key : string, callback? : (error? : Error, value? : string) => void) : Promise; + getItem(key : string, callback? : (error? : Error, value? : string) => void) : Promise; /** * Deletes data from the disk, using SharedPreferences or KeyChain, depending on the platform. From b8e61f20d1257d123a982909ecd0859c27f30db1 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:18:38 -0500 Subject: [PATCH 3/8] SingletonList --- .../encryptedstorage/RNEncryptedStoragePackage.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java index 488d485..fdefc47 100644 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java @@ -5,7 +5,6 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -13,10 +12,7 @@ public class RNEncryptedStoragePackage extends ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - modules.add(new RNEncryptedStorageModule(reactContext)); - - return modules; + return Collections.singletonList(new RNEncryptedStorageModule(reactContext)); } @Override From 2e80cf3c347f18b278f8e5885f77639b7bbe5c16 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:21:47 -0500 Subject: [PATCH 4/8] Extends -> Implements --- .../encryptedstorage/RNEncryptedStoragePackage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java index fdefc47..bf78574 100644 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStoragePackage.java @@ -8,7 +8,7 @@ import java.util.Collections; import java.util.List; -public class RNEncryptedStoragePackage extends ReactPackage { +public class RNEncryptedStoragePackage implements ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { From 94a36fee8b71832599840dfa504995fbd54126f5 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:26:19 -0500 Subject: [PATCH 5/8] Removed unnecessary check --- .../encryptedstorage/RNEncryptedStorageModule.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java index 9dac78b..cb48a6d 100644 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java @@ -72,13 +72,7 @@ public void getItem(String key, Promise promise) { String value = this.sharedPreferences.getString(key, ""); - if (value == null || value.isEmpty()) { - promise.reject(new Resources.NotFoundException(String.format("An error occured while retrieving %s", key))); - } - - else { - promise.resolve(value); - } + promise.resolve(value); } @ReactMethod From 0f7a55c4b81cfef9967b42fafa076b3e193daca9 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:28:48 -0500 Subject: [PATCH 6/8] Removed defValue --- .../emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java index cb48a6d..1251620 100644 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java @@ -70,7 +70,7 @@ public void getItem(String key, Promise promise) { return; } - String value = this.sharedPreferences.getString(key, ""); + String value = this.sharedPreferences.getString(key, null); promise.resolve(value); } From 90a11c0339a4225d51b0dd01aac9b03b043215c7 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:34:32 -0500 Subject: [PATCH 7/8] undefined vs null --- lib/EncryptedStorage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/EncryptedStorage.js b/lib/EncryptedStorage.js index 0dc09d1..10807fd 100644 --- a/lib/EncryptedStorage.js +++ b/lib/EncryptedStorage.js @@ -32,8 +32,8 @@ const EncryptedStorage = { return new Promise(async (resolve, reject) => { try { const value = await RNEncryptedStorage.getItem(key); - cb && cb(undefined, value); - resolve(value); + cb && cb(undefined, value || undefined); + resolve(value || undefined); } catch (error) { cb && cb(error, undefined); reject(error); From 1aaa5effcc27c13a4a2d932ad7dc7d8efdb2bb71 Mon Sep 17 00:00:00 2001 From: Yanick Date: Sun, 1 Mar 2020 16:39:25 -0500 Subject: [PATCH 8/8] Bumped version for release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7488845..a27fc8f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-encrypted-storage", "title": "React Native Encrypted Storage", - "version": "2.0.2", + "version": "2.1.0", "description": "A React Native wrapper over SharedPreferences and Keychain to provide a secure alternative to Async Storage", "main": "./lib/index.js", "types": "./types/index.d.ts",