├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmignore
├── LICENSE.md
├── README.md
├── RNSecureStorage.podspec
├── android
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── taluttasgiran
│ └── rnsecurestorage
│ ├── PreferencesStorage.java
│ ├── RNSecureStorageModule.java
│ ├── RNSecureStoragePackage.java
│ ├── SecurityLevel.java
│ └── secureStorage
│ └── SecureStorage.java
├── index.js
├── ios
├── RNSecureStorage-Bridging-Header.h
├── RNSecureStorage.m
├── RNSecureStorage.swift
├── RNSecureStorage.xcodeproj
│ └── project.pbxproj
├── RNSecureStorage.xcworkspace
│ └── contents.xcworkspacedata
└── RNSecureStorageHelper.swift
├── package.json
├── rn-secure-storage.d.ts
└── sample
├── .bundle
├── bin
│ ├── bundle
│ ├── fuzzy_match
│ ├── httpclient
│ ├── pod
│ ├── sandbox-pod
│ └── xcodeproj
└── config
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── .watchmanconfig
├── App.tsx
├── Gemfile
├── Gemfile.lock
├── README.md
├── __tests__
└── App.test.tsx
├── android
├── app
│ ├── build.gradle
│ ├── debug.keystore
│ ├── proguard-rules.pro
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── sample
│ │ │ ├── MainActivity.kt
│ │ │ └── MainApplication.kt
│ │ └── res
│ │ ├── drawable
│ │ └── rn_edit_text_material.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── strings.xml
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── app.json
├── babel.config.js
├── index.js
├── ios
├── .xcode.env
├── Podfile
├── Podfile.lock
├── sample.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── sample.xcscheme
├── sample.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── sample
│ ├── AppDelegate.h
│ ├── AppDelegate.mm
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Info.plist
│ ├── LaunchScreen.storyboard
│ └── main.m
└── sampleTests
│ ├── Info.plist
│ └── sampleTests.m
├── jest.config.js
├── metro.config.js
├── package.json
├── tsconfig.json
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: talut
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.buymeacoffee.com/talut']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # OSX
3 | #
4 | .DS_Store
5 |
6 | # node.js
7 | #
8 | node_modules/
9 | npm-debug.log
10 | yarn-error.log
11 |
12 |
13 | # Xcode
14 | #
15 | build/
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata
25 | *.xccheckout
26 | *.moved-aside
27 | DerivedData
28 | *.hmap
29 | *.ipa
30 | *.xcuserstate
31 | project.xcworkspace
32 |
33 |
34 | # Android/IntelliJ
35 | #
36 | build/
37 | .idea
38 | .gradle
39 | local.properties
40 | *.iml
41 |
42 | # BUCK
43 | buck-out/
44 | \.buckd/
45 | *.keystore
46 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | ._*
3 | .DS_Store
4 | .git
5 | .hg
6 | .npmrc
7 | .lock-wscript
8 | .svn
9 | .wafpickle-*
10 | config.gypi
11 | CVS
12 | npm-debug.log
13 | example
14 | sample
15 | .idea
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Talut TASGIRAN
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RNSecureStorage
2 |
3 | [](https://badge.fury.io/js/rn-secure-storage)
4 | [](https://img.shields.io/npm/dm/rn-secure-storage.svg?maxAge=2592000)
5 | 
6 |
7 | Secure Storage for React Native (Android & iOS) - Keychain & Keystore
8 |
9 | #### If you like this library, please consider supporting my work by buying me a coffee
10 |
11 | [](https://buymeacoff.ee/talut)
12 |
13 | ### Thanks for using this library
14 |
15 | Please read my disclaimer about maintaining this library [here](#disclaimer)
16 |
17 | For Android [I need your reviews](#i-need-your-reviews)
18 |
19 | ## Getting Started
20 |
21 | **With NPM**
22 |
23 | ```
24 | npm install --save rn-secure-storage
25 | ```
26 |
27 | **With YARN**
28 |
29 | ```
30 | yarn add rn-secure-storage
31 | ```
32 |
33 | ### What's changed in v3.0.1
34 | - Return type for multiGet changed. https://github.com/talut/rn-secure-storage/pull/79
35 |
36 | ### What's changed in v3.0.0
37 |
38 | - Rewritten Android module with enhanced security features.
39 | - Android minSdkVersion is now 23 (Android 6.0 Marshmallow)
40 | - iOS module redeveloped using Swift and updated APIs.
41 | - Comprehensive renaming and expansion of APIs.
42 | - Modifications to the return types of some APIs.
43 | - Added `clear` for comprehensive data clearance.
44 | - Introduced `getAllKeys` for retrieving all stored keys.
45 | - Implemented `multiSet` for setting multiple values simultaneously.
46 | - New `multiGet` feature for fetching multiple values at once.
47 | - `multiRemove` added for bulk deletion of items.
48 | - `getSupportedBiometryType` introduced for iOS (supports biometric authentication types).
49 |
50 | ### API
51 |
52 | - [setItem](#setitem)
53 | - [getItem](#getitem)
54 | - [removeItem](#removeitem)
55 | - [exists](#exists)
56 | - [getAllKeys](#getallkeys)
57 | - [clear](#clear)
58 | - [multiSet](#multiset)
59 | - [multiGet](#multiget)
60 | - [multiRemove](#multiremove)
61 |
62 | ### setItem
63 |
64 | To set the item, you need to pass the key and value as parameters. You can also pass the options as a third parameter. If the key exists,
65 | the value will be updated, otherwise, it will be created. You can use the `exists` method to check if the key exists. You can also pass
66 | the `accessible` option to set the accessibility of the keychain item (only for IOS).
67 |
68 | ```js
69 | import RNSecureStorage, {ACCESSIBLE} from 'rn-secure-storage';
70 |
71 | RNSecureStorage.setItem("key", "value", {accessible: ACCESSIBLE.WHEN_UNLOCKED}).then((res) => {
72 | console.log(res);
73 | }).catch((err) => {
74 | console.log(err);
75 | });
76 | ```
77 |
78 | ### getItem
79 |
80 | To get the item, you need to pass the key as a parameter. If the key exists, the value will be returned, otherwise, it will return an error.
81 |
82 | ```js
83 | RNSecureStorage.getItem("key").then((res) => {
84 | console.log(res);
85 | }).catch((err) => {
86 | console.log(err);
87 | });
88 | ```
89 |
90 | ### removeItem
91 |
92 | To remove the item, you need to pass the key as a parameter. If the key exists, the value will be removed, otherwise, it will return an
93 | error.
94 |
95 | ```js
96 | RNSecureStorage.removeItem("key").then((res) => {
97 | console.log(res);
98 | }).catch((err) => {
99 | console.log(err);
100 | });
101 | ```
102 |
103 | ### exists
104 |
105 | To check if the item exists, you need to pass the key as a parameter. If the key exists it will return `true`, otherwise, it will
106 | return `false`. Mostly you don't need to use this method because `getItem` will return an error if the key doesn't exist. But for
107 | performance you can just check if the key exists before calling `getItem`.
108 |
109 | ```js
110 | RNSecureStorage.exist("key").then((res) => {
111 | console.log(res ? "exists" : "does not exist");
112 | }).catch((err) => {
113 | console.log(err);
114 | });
115 | ```
116 |
117 | ### getAllKeys
118 |
119 | To get all keys, you need to call `getAllKeys` method. It will return an array of keys.
120 |
121 | ```js
122 | RNSecureStorage.getAllKeys().then((res) => {
123 | console.log(res);
124 | }).catch((err) => {
125 | console.log(err);
126 | });
127 | ```
128 |
129 | ### clear
130 |
131 | To clear all data, you need to call `clear` method. It will return `true` if the operation is successful. Otherwise, it will return
132 | remaining keys.
133 |
134 | ```js
135 | RNSecureStorage.clear().then((res) => {
136 | console.log(res);
137 | }).catch((err) => {
138 | console.log(err);
139 | });
140 | ```
141 |
142 | ### multiSet
143 |
144 | Multi set is a new feature that allows you to set multiple values simultaneously. You need to pass an object as the first parameter
145 |
146 | ```js
147 | import RNSecureStorage, {ACCESSIBLE} from 'rn-secure-storage';
148 |
149 | const items = {"key_1": "multi key 1", "key_2": "multi key 2"};
150 | RNSecureStorage.multiSet(items, {accessible: ACCESSIBLE.WHEN_UNLOCKED}).then((res) => {
151 | console.log(res);
152 | }).catch((err) => {
153 | console.log(err);
154 | });
155 | ```
156 |
157 | ### multiGet
158 |
159 | With multi get you can fetch multiple values at once. You need to pass an array of keys as the first parameter.
160 |
161 | ```js
162 | RNSecureStorage.multiGet(["key_1", "key_2"]).then((res) => {
163 | console.log(res);
164 | }).catch((err) => {
165 | console.log(err);
166 | });
167 | ```
168 |
169 | ### multiRemove
170 |
171 | With multi remove you can delete multiple values at once. You need to pass an array of keys as the first parameter. If your keys are
172 | removed/not found, it will return an array of keys that are not removed/found.
173 |
174 | ```js
175 | RNSecureStorage.multiRemove(["key_1", "key_2"]).then((res) => {
176 | console.log(res);
177 | }).catch((err) => {
178 | console.log(err);
179 | });
180 | ```
181 |
182 | ## Options
183 |
184 | | Key | Platform | Description | Default |
185 | |------------------|----------|--------------------------------------------------------------------------------------------------|---------------------------------------|
186 | | **`accessible`** | iOS only | This indicates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. | *`Keychain.ACCESSIBLE.WHEN_UNLOCKED`* |
187 |
188 | ### `Keychain.ACCESSIBLE` enum
189 |
190 | | Key | Description |
191 | |-------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
192 | | **`WHEN_UNLOCKED`** | The data in the keychain item can be accessed only while the device is unlocked by the user. |
193 | | **`AFTER_FIRST_UNLOCK`** | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. |
194 | | **`ALWAYS`** | The data in the keychain item can always be accessed regardless of whether the device is locked. |
195 | | **`WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`** | The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device. Items with this attribute never migrate to a new device. |
196 | | **`WHEN_UNLOCKED_THIS_DEVICE_ONLY`** | The data in the keychain item can be accessed only while the device is unlocked by the user. Items with this attribute do not migrate to a new device. |
197 | | **`AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`** | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. Items with this attribute never migrate to a new device. |
198 | | **`ALWAYS_THIS_DEVICE_ONLY`** | The data in the keychain item can always be accessed regardless of whether the device is locked. Items with this attribute never migrate to a new device. |
199 |
200 | #### You can also check out sample project for more details
201 |
202 | ## License
203 |
204 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
205 |
206 | ### I need your reviews
207 |
208 | I have rewritten the Android module with enhanced security features. I need your reviews. Please test the new version and let me know your
209 | thoughts. I will be happy to hear your suggestions and comments. I'm planning to release the new version to handle biometric authentication
210 | on Android.
211 |
212 | ### Connect with me
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 | ### Disclaimer
223 |
224 | As an open-source enthusiast and developer, I originally created this library for professional use. However, with recent changes in my
225 | career, my focus has shifted away from React Native, limiting my involvement to personal projects. Consequently, my time to address issues
226 | and review pull requests for this library has become restricted. I remain committed to maintaining and improving this library, but my
227 | response times might be longer. I greatly appreciate your patience and understanding.
228 |
229 | The beauty of open-source is in collaboration. If you find this library useful and would like to contribute, whether through code,
230 | addressing issues, or even buying a coffee to show support, it would be immensely appreciated. Together, we can ensure the continued
231 | development and enhancement of this library.
232 |
233 | [](https://www.buymeacoffee.com/talut)
234 |
--------------------------------------------------------------------------------
/RNSecureStorage.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = "RNSecureStorage"
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.license = package['license']
10 |
11 | s.authors = package['author']
12 | s.homepage = package['homepage']
13 | s.platforms = { :ios => "10.0", :tvos => "9.2", :osx => "10.14" }
14 |
15 | s.source = { :git => "https://github.com/talut/rn-secure-storage.git", :tag => "v#{s.version}" }
16 | s.source_files = "ios/**/*.{h,m,swift}"
17 | s.dependency 'React'
18 | end
19 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | // The Android Gradle plugin is only required when opening the android folder stand-alone.
3 | // This avoids unnecessary downloads and potential conflicts when the library is included as a
4 | // module dependency in an application project.
5 | if (project == rootProject) {
6 | repositories {
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | classpath("com.android.tools.build:gradle")
12 | }
13 | }
14 | }
15 |
16 | apply plugin: 'com.android.library'
17 |
18 | def safeExtGet(prop, fallback) {
19 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
20 | }
21 |
22 | android {
23 | compileSdkVersion safeExtGet('compileSdkVersion', 34)
24 | buildToolsVersion safeExtGet('buildToolsVersion', '34.0.0')
25 |
26 | defaultConfig {
27 | minSdkVersion safeExtGet('minSdkVersion', 23)
28 | targetSdkVersion safeExtGet('targetSdkVersion', 34)
29 | versionCode 4
30 | versionName "3.0.0"
31 | }
32 |
33 | lintOptions {
34 | abortOnError false
35 | }
36 |
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_1_8
39 | targetCompatibility JavaVersion.VERSION_1_8
40 | }
41 | }
42 |
43 | repositories {
44 | mavenCentral()
45 | }
46 |
47 | dependencies {
48 | implementation "com.facebook.react:react-native:+"
49 | implementation 'androidx.appcompat:appcompat:1.6.1'
50 | }
51 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taluttasgiran/rnsecurestorage/PreferencesStorage.java:
--------------------------------------------------------------------------------
1 | package com.taluttasgiran.rnsecurestorage;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.util.Base64;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 |
10 | import com.facebook.react.bridge.ReactApplicationContext;
11 |
12 | import org.json.JSONArray;
13 |
14 | import java.nio.charset.StandardCharsets;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.Locale;
18 |
19 | /**
20 | * Shared preferences storage.
21 | *
22 | * This class is used to store encrypted values in shared preferences.
23 | * It is used to store the encryption key and the initialization vector.
24 | * The key and the initialization vector are used to encrypt and decrypt the values.
25 | *
26 | */
27 | public class PreferencesStorage {
28 | public static final String RN_SECURE_STORAGE = "RN_SECURE_STORAGE";
29 |
30 | @NonNull
31 | private final SharedPreferences prefs;
32 |
33 | public PreferencesStorage(@NonNull final ReactApplicationContext reactContext) {
34 | String prefsName = reactContext.getPackageName() + "." + RN_SECURE_STORAGE;
35 | String prefNameBase64 = Base64.encodeToString(prefsName.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
36 | String fileName = prefNameBase64.toLowerCase(Locale.ROOT).replaceAll("[^a-z]", "");
37 | this.prefs = reactContext.getSharedPreferences(fileName, Context.MODE_PRIVATE);
38 | }
39 |
40 |
41 | @Nullable
42 | public String getEncryptedEntry(@NonNull final String key) {
43 | return this.prefs.getString(key, null);
44 | }
45 |
46 | public boolean removeEntry(@NonNull final String key) {
47 | if (this.prefs.getString(key, null) != null) {
48 | prefs.edit().remove(key).apply();
49 | return true;
50 | } else {
51 | return false;
52 | }
53 | }
54 |
55 | public boolean clear() {
56 | return this.prefs.edit().clear().commit();
57 | }
58 |
59 | public boolean exist(@NonNull final String key) {
60 | return this.prefs.contains(key);
61 | }
62 |
63 | public void storeEncryptedEntry(@NonNull final String key, @NonNull final String encryptedValue) {
64 | prefs.edit().putString(key, encryptedValue).apply();
65 | }
66 |
67 |
68 | public JSONArray getAllStoredKeys() {
69 | List list = new ArrayList<>(this.prefs.getAll().keySet());
70 | return new JSONArray(list);
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taluttasgiran/rnsecurestorage/RNSecureStorageModule.java:
--------------------------------------------------------------------------------
1 | package com.taluttasgiran.rnsecurestorage;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.facebook.react.bridge.Promise;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
8 | import com.facebook.react.bridge.ReactMethod;
9 | import com.facebook.react.bridge.ReadableArray;
10 | import com.facebook.react.bridge.ReadableMap;
11 | import com.facebook.react.bridge.ReadableMapKeySetIterator;
12 | import com.facebook.react.bridge.WritableArray;
13 | import com.facebook.react.bridge.WritableMap;
14 | import com.facebook.react.bridge.WritableNativeArray;
15 | import com.facebook.react.bridge.WritableNativeMap;
16 | import com.taluttasgiran.rnsecurestorage.secureStorage.SecureStorage;
17 |
18 | public class RNSecureStorageModule extends ReactContextBaseJavaModule {
19 | public static final String RN_SECURE_STORAGE = "RNSecureStorage";
20 |
21 | /**
22 | * Known error codes.
23 | */
24 | @interface Errors {
25 | String KEY_REQUIRED = "KEY_REQUIRED";
26 | String NOT_FOUND = "NOT_FOUND";
27 | String VALUE_REQUIRED = "VALUE_REQUIRED";
28 | String UNKNOWN_ERROR = "UNKNOWN_ERROR";
29 | }
30 |
31 | /**
32 | * Name-to-instance lookup map.
33 | */
34 | private final SecureStorage secureStorage;
35 | /**
36 | * Shared preferences storage.
37 | */
38 | private final PreferencesStorage prefsStorage;
39 |
40 | /**
41 | * Default constructor.
42 | */
43 | public RNSecureStorageModule(@NonNull final ReactApplicationContext reactContext) {
44 | super(reactContext);
45 | prefsStorage = new PreferencesStorage(reactContext);
46 | try {
47 | secureStorage = new SecureStorage();
48 | } catch (Exception e) {
49 | throw new RuntimeException(e);
50 | }
51 | }
52 |
53 | /**
54 | * {@inheritDoc}
55 | */
56 | @Override
57 | @NonNull
58 | public String getName() {
59 | return RN_SECURE_STORAGE;
60 | }
61 |
62 | /**
63 | * Set a value.
64 | */
65 | @ReactMethod
66 | protected void setItem(@NonNull final String key, @NonNull final String value, @NonNull final Promise promise) {
67 | try {
68 | String encryptedValue = secureStorage.encrypt(value);
69 | prefsStorage.storeEncryptedEntry(key, encryptedValue);
70 | promise.resolve("Stored successfully");
71 | } catch (Exception e) {
72 | promise.reject("RNSecureStorage: An error occurred during key store", e);
73 | }
74 | }
75 |
76 | /**
77 | * Get a value from secure storage.
78 | */
79 | @ReactMethod
80 | protected void getItem(@NonNull final String key, @NonNull final Promise promise) {
81 | try {
82 | String encryptedValue = prefsStorage.getEncryptedEntry(key);
83 | if (encryptedValue != null) {
84 | promise.resolve(secureStorage.decrypt(encryptedValue));
85 | } else {
86 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: Value for " + key + " does not exist.");
87 | }
88 | } catch (Exception e) {
89 | promise.reject(e);
90 | }
91 | }
92 |
93 | /**
94 | * Checks if a key has been set.
95 | */
96 | @ReactMethod
97 | public void exist(String key, Promise promise) {
98 | try {
99 | promise.resolve(this.prefsStorage.exist(key));
100 | } catch (Exception e) {
101 | promise.reject(e);
102 | }
103 | }
104 |
105 | /**
106 | * Multiple key pair set for secure storage
107 | */
108 | @ReactMethod
109 | public void multiSet(ReadableMap keyValuePairs, Promise promise) {
110 | if (keyValuePairs.toHashMap().size() > 0) {
111 | ReadableMapKeySetIterator iterator = keyValuePairs.keySetIterator();
112 | while (iterator.hasNextKey()) {
113 | String key = iterator.nextKey();
114 | String value = keyValuePairs.getString(key);
115 | if (value == null) {
116 | promise.reject(Errors.VALUE_REQUIRED, "RNSecureStorage: You must provide a value to store this key: " + key);
117 | return;
118 | }
119 | setItem(key, value, promise);
120 | }
121 | promise.resolve("Stored successfully");
122 | } else {
123 | promise.reject(Errors.VALUE_REQUIRED, "RNSecureStorage: You must provide at least one key/value pair to store");
124 | }
125 | }
126 |
127 | /**
128 | * Get multiple values from secure storage.
129 | */
130 | @ReactMethod
131 | public void multiGet(ReadableArray keys, Promise promise) {
132 | WritableMap resultMap = new WritableNativeMap();
133 | if (keys.size() > 0) {
134 | final int size = keys.size();
135 | for (int i = 0; i < size; i++) {
136 | String key = keys.getString(i);
137 | String encryptedValue = prefsStorage.getEncryptedEntry(key);
138 | if (encryptedValue != null) {
139 | resultMap.putString(key, secureStorage.decrypt(encryptedValue));
140 | } else {
141 | resultMap.putString(key, null);
142 | }
143 | }
144 | promise.resolve(resultMap);
145 | } else {
146 | promise.reject(Errors.KEY_REQUIRED, "RNSecureStorage: You must provide at least one key to get");
147 | }
148 | }
149 |
150 | /**
151 | * Get all setted keys from secure storage.
152 | */
153 | @ReactMethod
154 | public void getAllKeys(Promise promise) {
155 | try {
156 | promise.resolve(String.valueOf(this.prefsStorage.getAllStoredKeys()));
157 | } catch (Exception e) {
158 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: There are no stored keys.");
159 | }
160 | }
161 |
162 | /**
163 | * Remove a value from secure storage.
164 | */
165 | @ReactMethod
166 | public void removeItem(String key, Promise promise) {
167 | try {
168 | if (this.prefsStorage.removeEntry(key)) {
169 | promise.resolve("Removed successfully");
170 | } else {
171 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: Value for key " + key + " does not exist.");
172 | }
173 | } catch (Exception e) {
174 | promise.reject(e);
175 | }
176 | }
177 |
178 | /**
179 | * Remove values from secure storage (On error will return unremoved keys)
180 | */
181 | @ReactMethod
182 | public void multiRemove(ReadableArray keys, Promise promise) {
183 | WritableArray unremovedKeys = new WritableNativeArray();
184 | if (keys.size() > 0) {
185 | final int size = keys.size();
186 | for (int i = 0; i < size; i++) {
187 | String key = keys.getString(i);
188 | if (!this.prefsStorage.removeEntry(key)) {
189 | unremovedKeys.pushString(key);
190 | }
191 | }
192 | if (unremovedKeys.size() > 0) {
193 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: Value for keys " + unremovedKeys + " does not exist.");
194 | } else {
195 | promise.resolve("Removed successfully");
196 | }
197 | } else {
198 | promise.reject(Errors.KEY_REQUIRED, "RNSecureStorage: You must provide at least one key to remove");
199 | }
200 | }
201 |
202 | /**
203 | * Removes whole RNSecureStorage data (On error will return unremoved keys)
204 | */
205 | @ReactMethod
206 | public void clear(Promise promise) {
207 | try {
208 | if (this.prefsStorage.clear()) {
209 | if (this.prefsStorage.getAllStoredKeys().isNull(0)) {
210 | promise.resolve(true);
211 | } else {
212 | promise.resolve(String.valueOf(this.prefsStorage.getAllStoredKeys()));
213 | }
214 | } else {
215 | promise.reject(Errors.UNKNOWN_ERROR, "RNSecureStorage: An error occurred during key remove");
216 | }
217 | } catch (Exception e) {
218 | promise.reject(e);
219 | }
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taluttasgiran/rnsecurestorage/RNSecureStoragePackage.java:
--------------------------------------------------------------------------------
1 | package com.taluttasgiran.rnsecurestorage;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.NativeModule;
5 | import com.facebook.react.bridge.ReactApplicationContext;
6 | import com.facebook.react.uimanager.ViewManager;
7 | import java.util.Collections;
8 | import java.util.List;
9 |
10 | public class RNSecureStoragePackage implements ReactPackage {
11 | @Override
12 | public List createNativeModules(ReactApplicationContext reactContext) {
13 | return Collections.singletonList(new RNSecureStorageModule(reactContext));
14 | }
15 |
16 | @Override
17 | public List createViewManagers(ReactApplicationContext reactContext) {
18 | return Collections.emptyList();
19 | }
20 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/taluttasgiran/rnsecurestorage/SecurityLevel.java:
--------------------------------------------------------------------------------
1 | package com.taluttasgiran.rnsecurestorage;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | /**
6 | * Minimal required level of the security implementation.
7 | */
8 | public enum SecurityLevel {
9 | /**
10 | * No security guarantees needed (default value); Credentials can be stored in FB Secure Storage
11 | */
12 | ANY,
13 | /**
14 | * Requires for the key to be stored in the Android Keystore, separate from the encrypted data.
15 | */
16 | SECURE_SOFTWARE,
17 | /**
18 | * Requires for the key to be stored on a secure hardware (Trusted Execution Environment or Secure Environment).
19 | */
20 | SECURE_HARDWARE;
21 |
22 | /**
23 | * Get JavaScript friendly name.
24 | */
25 | @NonNull
26 | public String jsName() {
27 | return String.format("SECURITY_LEVEL_%s", this.name());
28 | }
29 |
30 | public boolean satisfiesSafetyThreshold(@NonNull final SecurityLevel threshold) {
31 | return this.compareTo(threshold) >= 0;
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taluttasgiran/rnsecurestorage/secureStorage/SecureStorage.java:
--------------------------------------------------------------------------------
1 | package com.taluttasgiran.rnsecurestorage.secureStorage;
2 |
3 | import android.security.keystore.KeyGenParameterSpec;
4 | import android.security.keystore.KeyProperties;
5 | import android.util.Base64;
6 |
7 | import java.security.KeyStore;
8 | import java.security.SecureRandom;
9 |
10 | import javax.crypto.Cipher;
11 | import javax.crypto.KeyGenerator;
12 | import javax.crypto.SecretKey;
13 | import javax.crypto.spec.GCMParameterSpec;
14 |
15 | public class SecureStorage {
16 |
17 | private static final String KEY_ALIAS = "RNSecureStorage";
18 | private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
19 | private static final String TRANSFORMATION = "AES/GCM/NoPadding";
20 | private static final int GCM_IV_LENGTH = 12; // Bytes
21 | private static final int GCM_TAG_LENGTH = 16; // Bytes
22 |
23 | private KeyStore keyStore;
24 |
25 | public SecureStorage() {
26 | initKeyStore();
27 | }
28 |
29 | private void initKeyStore() throws RuntimeException {
30 | try {
31 | keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
32 | keyStore.load(null);
33 |
34 | if (!keyStore.containsAlias(KEY_ALIAS)) {
35 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
36 | keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,
37 | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
38 | .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
39 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
40 | .setRandomizedEncryptionRequired(false)
41 | .build());
42 | keyGenerator.generateKey();
43 | }
44 | } catch (Exception e) {
45 | throw new RuntimeException(e);
46 | }
47 | }
48 |
49 | public String encrypt(String input) {
50 | try {
51 | SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
52 | Cipher cipher = Cipher.getInstance(TRANSFORMATION);
53 | byte[] iv = new byte[GCM_IV_LENGTH];
54 | new SecureRandom().nextBytes(iv);
55 | cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(8 * GCM_TAG_LENGTH, iv));
56 | byte[] encryptedBytes = cipher.doFinal(input.getBytes());
57 | String encryptedBase64 = Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
58 | String ivBase64 = Base64.encodeToString(iv, Base64.DEFAULT);
59 | return ivBase64 + ":" + encryptedBase64;
60 | } catch (Exception e) {
61 | // Handle exceptions
62 | return null;
63 | }
64 | }
65 |
66 | public String decrypt(String encrypted) {
67 | try {
68 | String[] parts = encrypted.split(":");
69 | byte[] iv = Base64.decode(parts[0], Base64.DEFAULT);
70 | byte[] encryptedBytes = Base64.decode(parts[1], Base64.DEFAULT);
71 |
72 | SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
73 | Cipher cipher = Cipher.getInstance(TRANSFORMATION);
74 | cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(8 * GCM_TAG_LENGTH, iv));
75 | byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
76 | return new String(decryptedBytes);
77 | } catch (Exception e) {
78 | // Handle exceptions
79 | return null;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import {NativeModules, Platform} from "react-native";
2 |
3 | export const ACCESSIBLE = {
4 | WHEN_UNLOCKED: "AccessibleWhenUnlocked",
5 | AFTER_FIRST_UNLOCK: "AccessibleAfterFirstUnlock",
6 | ALWAYS: "AccessibleAlways",
7 | WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: "AccessibleWhenPasscodeSetThisDeviceOnly",
8 | WHEN_UNLOCKED_THIS_DEVICE_ONLY: "AccessibleWhenUnlockedThisDeviceOnly",
9 | AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: "AccessibleAfterFirstUnlockThisDeviceOnly",
10 | ALWAYS_THIS_DEVICE_ONLY: "AccessibleAlwaysThisDeviceOnly",
11 | };
12 | const {RNSecureStorage} = NativeModules;
13 |
14 | const set = RNSecureStorage.setItem;
15 | RNSecureStorage.setItem = (key, value, options = {}) => {
16 | if (Platform.OS === "android") {
17 | return set(key, value);
18 | }
19 | return set(key, value, options);
20 | };
21 |
22 | const multiSet = RNSecureStorage.multiSet;
23 | RNSecureStorage.multiSet = (keyValuePairs, options = {}) => {
24 | if (Platform.OS === "android") {
25 | return multiSet(keyValuePairs);
26 | }
27 | return multiSet(keyValuePairs, options);
28 | };
29 |
30 | const getAllKeys = RNSecureStorage.getAllKeys;
31 | RNSecureStorage.getAllKeys = () => {
32 | if (Platform.OS === "android") {
33 | return new Promise((resolve, reject) => {
34 | getAllKeys()
35 | .then((keys) => keys ? resolve(JSON.parse(keys)) : resolve(null))
36 | .catch((error) => reject(error));
37 | });
38 | }
39 | return getAllKeys();
40 | };
41 |
42 | export default RNSecureStorage;
43 |
--------------------------------------------------------------------------------
/ios/RNSecureStorage-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import
2 |
--------------------------------------------------------------------------------
/ios/RNSecureStorage.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface RCT_EXTERN_MODULE(RNSecureStorage, NSObject)
5 |
6 | /**
7 | * Set a value from secure storage.
8 | */
9 | RCT_EXTERN_METHOD(setItem:(NSString *)key value:(NSString *)value options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
10 |
11 | /**
12 | * Get a value from secure storage.
13 | */
14 | RCT_EXTERN_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
15 |
16 | /**
17 | * Checks if a key has been set.
18 | */
19 | RCT_EXTERN_METHOD(exist:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
20 |
21 | /**
22 | * Get all stored keys from secure storage.
23 | */
24 | RCT_EXTERN_METHOD(getAllKeys:(RCTPromiseResolveBlock *)resolver rejecter:(RCTPromiseRejectBlock)reject)
25 |
26 | /**
27 | * Multiple key pair set for secure storage
28 | */
29 | RCT_EXTERN_METHOD(multiSet:(NSDictionary *)keyValuePairs options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject)
30 |
31 | /**
32 | * Get multiple values from secure storage.
33 | */
34 | RCT_EXTERN_METHOD(multiGet:(NSArray *)keys resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject)
35 |
36 | /**
37 | * Remove a value from secure storage.
38 | */
39 | RCT_EXTERN_METHOD(removeItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
40 |
41 | /**
42 | * Remove values from secure storage
43 | */
44 | RCT_EXTERN_METHOD(multiRemove:(NSArray *)keys resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject)
45 |
46 | /**
47 | Remove all stored keys. (On error will return unremoved keys)
48 | */
49 | RCT_EXTERN_METHOD(clear:(RCTPromiseResolveBlock *)resolver rejecter:(RCTPromiseRejectBlock)reject)
50 |
51 | /**
52 | Get supported biometry type
53 | */
54 | RCT_EXTERN_METHOD(getSupportedBiometryType: (RCTPromiseResolveBlock *)resolver rejecter:(RCTPromiseRejectBlock *)reject)
55 |
56 |
57 | + (BOOL)requiresMainQueueSetup
58 | {
59 | return YES;
60 | }
61 | @end
62 |
--------------------------------------------------------------------------------
/ios/RNSecureStorage.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Security
3 | import LocalAuthentication
4 |
5 | public enum RNSecureErrors: Error {
6 | case KEY_NOT_STORED
7 | case KEY_REQUIRED
8 | case KEY_NOT_REMOVED
9 | case NOT_FOUND
10 | case UNKNOWN_ERROR
11 | }
12 |
13 | @objc(RNSecureStorage)
14 | class RNSecureStorage: NSObject {
15 | let helper = RNSecureStorageHelper.init()
16 |
17 |
18 | @objc(setItem:value:options:resolver:rejecter:)
19 | func setItem(_ key:String, value:String, options:[String:Any], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
20 | let accessible = options["accessible"] as! String
21 | let status = helper.createKeychainValue(key: key, value: value, accessible: accessible)
22 | if status {
23 | resolver("Key stored successfully")
24 | }else{
25 | rejecter("KEY_NOT_STORED","RNSecureStorage: An error occurred during key storage", RNSecureErrors.KEY_NOT_STORED)
26 | }
27 | }
28 |
29 | @objc(getItem:resolver:rejecter:)
30 | func getItem(_ key:String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
31 | let val = helper.getKeychainValue(key: key)
32 | if val == nil {
33 | rejecter("NOT_FOUND","RNSecureStorage: Key does not present", RNSecureErrors.NOT_FOUND)
34 | } else {
35 | let result = String(data: val!, encoding: .utf8)
36 | resolver(result)
37 | }
38 | }
39 |
40 | @objc(exist:resolver:rejecter:)
41 | func exist(_ key:String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
42 | let val = helper.keychainValueExist(key: key)
43 | resolver(val)
44 | }
45 |
46 | @objc(getAllKeys:rejecter:)
47 | func getAllKeys(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
48 |
49 | let keys = helper.getAllKeychainKeys()
50 |
51 | if keys.count > 0 {
52 | resolver(keys)
53 | }else{
54 | rejecter("NOT_FOUND","RNSecureStorage: There are no stored keys.", RNSecureErrors.NOT_FOUND)
55 | }
56 | }
57 |
58 | @objc(multiSet:options:resolver:rejecter:)
59 | func multiSet(_ keyValuePairs:[String:String], options:[String:Any], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
60 | let accessible = options["accessible"] as! String
61 | let status = helper.multiSetKeychainValues(keyValuePairs: keyValuePairs, accessible: accessible)
62 | if status {
63 | resolver("Key stored successfully")
64 | }else{
65 | rejecter("KEY_NOT_STORED","RNSecureStorage: An error occurred during key storage", RNSecureErrors.KEY_NOT_STORED)
66 | }
67 | }
68 |
69 | /*
70 | Multi get values by keys array.
71 | */
72 | @objc(multiGet:resolver:rejecter:)
73 | func multiGet(_ keys: [String], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
74 | do {
75 | let vals = try helper.multiGetKeychainValues(keys: keys)
76 | resolver(vals)
77 | } catch RNSecureErrors.KEY_REQUIRED {
78 | rejecter("KEY_REQUIRED", "You must provide at least one key to get", RNSecureErrors.KEY_REQUIRED)
79 | } catch {
80 | rejecter("UNKNOWN_ERROR", "An unknown error occurred", error)
81 | }
82 | }
83 |
84 | @objc(removeItem:resolver:rejecter:)
85 | func removeItem(_ key:String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
86 | let status = helper.removeKeychainValue(key: key)
87 | if status {
88 | resolver("Key removed successfully")
89 | }else{
90 | rejecter("KEY_NOT_REMOVED","RNSecureStorage: An error occurred during key remove", RNSecureErrors.KEY_NOT_REMOVED)
91 | }
92 | }
93 |
94 | /*
95 | Clear all given keys. (On error will return un-removed keys)
96 | */
97 | @objc(multiRemove:resolver:rejecter:)
98 | func multiRemove(_ keys:[String], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
99 | let unremovedKeys = helper.multiRemoveKeychainValue(keys: keys)
100 | if unremovedKeys.count>0 {
101 | resolver(unremovedKeys)
102 | }else{
103 | resolver("Keys removed successfully")
104 | }
105 | }
106 |
107 |
108 | /*
109 | Clear all stored keys. (On error will return unremoved keys)
110 | */
111 | @objc(clear:rejecter:)
112 | func clear(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){
113 | let keys = helper.clearAllStoredValues()
114 | if keys.count > 0 {
115 | resolver(keys)
116 | }else{
117 | resolver("All stored keys removed.")
118 | }
119 | }
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/ios/RNSecureStorage.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B3E7B58A1CC2AC0600A0062D /* RNSecureStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNSecureStorage.m */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXCopyFilesBuildPhase section */
14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = {
15 | isa = PBXCopyFilesBuildPhase;
16 | buildActionMask = 2147483647;
17 | dstPath = "include/$(PRODUCT_NAME)";
18 | dstSubfolderSpec = 16;
19 | files = (
20 | );
21 | runOnlyForDeploymentPostprocessing = 0;
22 | };
23 | /* End PBXCopyFilesBuildPhase section */
24 |
25 | /* Begin PBXFileReference section */
26 | 134814201AA4EA6300B7C361 /* libRNSecureStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNSecureStorage.a; sourceTree = BUILT_PRODUCTS_DIR; };
27 | B3E7B5881CC2AC0600A0062D /* RNSecureStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSecureStorage.h; sourceTree = ""; };
28 | B3E7B5891CC2AC0600A0062D /* RNSecureStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSecureStorage.m; sourceTree = ""; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | 58B511D81A9E6C8500147676 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | 134814211AA4EA7D00B7C361 /* Products */ = {
43 | isa = PBXGroup;
44 | children = (
45 | 134814201AA4EA6300B7C361 /* libRNSecureStorage.a */,
46 | );
47 | name = Products;
48 | sourceTree = "";
49 | };
50 | 58B511D21A9E6C8500147676 = {
51 | isa = PBXGroup;
52 | children = (
53 | B3E7B5881CC2AC0600A0062D /* RNSecureStorage.h */,
54 | B3E7B5891CC2AC0600A0062D /* RNSecureStorage.m */,
55 | 134814211AA4EA7D00B7C361 /* Products */,
56 | );
57 | sourceTree = "";
58 | };
59 | /* End PBXGroup section */
60 |
61 | /* Begin PBXNativeTarget section */
62 | 58B511DA1A9E6C8500147676 /* RNSecureStorage */ = {
63 | isa = PBXNativeTarget;
64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSecureStorage" */;
65 | buildPhases = (
66 | 58B511D71A9E6C8500147676 /* Sources */,
67 | 58B511D81A9E6C8500147676 /* Frameworks */,
68 | 58B511D91A9E6C8500147676 /* CopyFiles */,
69 | );
70 | buildRules = (
71 | );
72 | dependencies = (
73 | );
74 | name = RNSecureStorage;
75 | productName = RCTDataManager;
76 | productReference = 134814201AA4EA6300B7C361 /* libRNSecureStorage.a */;
77 | productType = "com.apple.product-type.library.static";
78 | };
79 | /* End PBXNativeTarget section */
80 |
81 | /* Begin PBXProject section */
82 | 58B511D31A9E6C8500147676 /* Project object */ = {
83 | isa = PBXProject;
84 | attributes = {
85 | LastUpgradeCheck = 0830;
86 | ORGANIZATIONNAME = Facebook;
87 | TargetAttributes = {
88 | 58B511DA1A9E6C8500147676 = {
89 | CreatedOnToolsVersion = 6.1.1;
90 | };
91 | };
92 | };
93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSecureStorage" */;
94 | compatibilityVersion = "Xcode 3.2";
95 | developmentRegion = English;
96 | hasScannedForEncodings = 0;
97 | knownRegions = (
98 | en,
99 | );
100 | mainGroup = 58B511D21A9E6C8500147676;
101 | productRefGroup = 58B511D21A9E6C8500147676;
102 | projectDirPath = "";
103 | projectRoot = "";
104 | targets = (
105 | 58B511DA1A9E6C8500147676 /* RNSecureStorage */,
106 | );
107 | };
108 | /* End PBXProject section */
109 |
110 | /* Begin PBXSourcesBuildPhase section */
111 | 58B511D71A9E6C8500147676 /* Sources */ = {
112 | isa = PBXSourcesBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | B3E7B58A1CC2AC0600A0062D /* RNSecureStorage.m in Sources */,
116 | );
117 | runOnlyForDeploymentPostprocessing = 0;
118 | };
119 | /* End PBXSourcesBuildPhase section */
120 |
121 | /* Begin XCBuildConfiguration section */
122 | 58B511ED1A9E6C8500147676 /* Debug */ = {
123 | isa = XCBuildConfiguration;
124 | buildSettings = {
125 | ALWAYS_SEARCH_USER_PATHS = NO;
126 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
127 | CLANG_CXX_LIBRARY = "libc++";
128 | CLANG_ENABLE_MODULES = YES;
129 | CLANG_ENABLE_OBJC_ARC = YES;
130 | CLANG_WARN_BOOL_CONVERSION = YES;
131 | CLANG_WARN_CONSTANT_CONVERSION = YES;
132 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
133 | CLANG_WARN_EMPTY_BODY = YES;
134 | CLANG_WARN_ENUM_CONVERSION = YES;
135 | CLANG_WARN_INFINITE_RECURSION = YES;
136 | CLANG_WARN_INT_CONVERSION = YES;
137 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
138 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
139 | CLANG_WARN_UNREACHABLE_CODE = YES;
140 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
141 | COPY_PHASE_STRIP = NO;
142 | ENABLE_STRICT_OBJC_MSGSEND = YES;
143 | ENABLE_TESTABILITY = YES;
144 | GCC_C_LANGUAGE_STANDARD = gnu99;
145 | GCC_DYNAMIC_NO_PIC = NO;
146 | GCC_NO_COMMON_BLOCKS = YES;
147 | GCC_OPTIMIZATION_LEVEL = 0;
148 | GCC_PREPROCESSOR_DEFINITIONS = (
149 | "DEBUG=1",
150 | "$(inherited)",
151 | );
152 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
153 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
154 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
155 | GCC_WARN_UNDECLARED_SELECTOR = YES;
156 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
157 | GCC_WARN_UNUSED_FUNCTION = YES;
158 | GCC_WARN_UNUSED_VARIABLE = YES;
159 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
160 | MTL_ENABLE_DEBUG_INFO = YES;
161 | ONLY_ACTIVE_ARCH = YES;
162 | SDKROOT = iphoneos;
163 | };
164 | name = Debug;
165 | };
166 | 58B511EE1A9E6C8500147676 /* Release */ = {
167 | isa = XCBuildConfiguration;
168 | buildSettings = {
169 | ALWAYS_SEARCH_USER_PATHS = NO;
170 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
171 | CLANG_CXX_LIBRARY = "libc++";
172 | CLANG_ENABLE_MODULES = YES;
173 | CLANG_ENABLE_OBJC_ARC = YES;
174 | CLANG_WARN_BOOL_CONVERSION = YES;
175 | CLANG_WARN_CONSTANT_CONVERSION = YES;
176 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
177 | CLANG_WARN_EMPTY_BODY = YES;
178 | CLANG_WARN_ENUM_CONVERSION = YES;
179 | CLANG_WARN_INFINITE_RECURSION = YES;
180 | CLANG_WARN_INT_CONVERSION = YES;
181 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
182 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
183 | CLANG_WARN_UNREACHABLE_CODE = YES;
184 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
185 | COPY_PHASE_STRIP = YES;
186 | ENABLE_NS_ASSERTIONS = NO;
187 | ENABLE_STRICT_OBJC_MSGSEND = YES;
188 | GCC_C_LANGUAGE_STANDARD = gnu99;
189 | GCC_NO_COMMON_BLOCKS = YES;
190 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
191 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
192 | GCC_WARN_UNDECLARED_SELECTOR = YES;
193 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
194 | GCC_WARN_UNUSED_FUNCTION = YES;
195 | GCC_WARN_UNUSED_VARIABLE = YES;
196 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
197 | MTL_ENABLE_DEBUG_INFO = NO;
198 | SDKROOT = iphoneos;
199 | VALIDATE_PRODUCT = YES;
200 | };
201 | name = Release;
202 | };
203 | 58B511F01A9E6C8500147676 /* Debug */ = {
204 | isa = XCBuildConfiguration;
205 | buildSettings = {
206 | HEADER_SEARCH_PATHS = (
207 | "$(inherited)",
208 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
209 | "$(SRCROOT)/../../../React/**",
210 | "$(SRCROOT)/../../react-native/React/**",
211 | );
212 | LIBRARY_SEARCH_PATHS = "$(inherited)";
213 | OTHER_LDFLAGS = "-ObjC";
214 | PRODUCT_NAME = RNSecureStorage;
215 | SKIP_INSTALL = YES;
216 | };
217 | name = Debug;
218 | };
219 | 58B511F11A9E6C8500147676 /* Release */ = {
220 | isa = XCBuildConfiguration;
221 | buildSettings = {
222 | HEADER_SEARCH_PATHS = (
223 | "$(inherited)",
224 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
225 | "$(SRCROOT)/../../../React/**",
226 | "$(SRCROOT)/../../react-native/React/**",
227 | );
228 | LIBRARY_SEARCH_PATHS = "$(inherited)";
229 | OTHER_LDFLAGS = "-ObjC";
230 | PRODUCT_NAME = RNSecureStorage;
231 | SKIP_INSTALL = YES;
232 | };
233 | name = Release;
234 | };
235 | /* End XCBuildConfiguration section */
236 |
237 | /* Begin XCConfigurationList section */
238 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSecureStorage" */ = {
239 | isa = XCConfigurationList;
240 | buildConfigurations = (
241 | 58B511ED1A9E6C8500147676 /* Debug */,
242 | 58B511EE1A9E6C8500147676 /* Release */,
243 | );
244 | defaultConfigurationIsVisible = 0;
245 | defaultConfigurationName = Release;
246 | };
247 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSecureStorage" */ = {
248 | isa = XCConfigurationList;
249 | buildConfigurations = (
250 | 58B511F01A9E6C8500147676 /* Debug */,
251 | 58B511F11A9E6C8500147676 /* Release */,
252 | );
253 | defaultConfigurationIsVisible = 0;
254 | defaultConfigurationName = Release;
255 | };
256 | /* End XCConfigurationList section */
257 | };
258 | rootObject = 58B511D31A9E6C8500147676 /* Project object */;
259 | }
260 |
--------------------------------------------------------------------------------
/ios/RNSecureStorage.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 |
3 |
5 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/RNSecureStorageHelper.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | class RNSecureStorageHelper {
4 | let appBundleName = Bundle.main.bundleIdentifier!
5 | enum KeychainError: Error {
6 | case keyRequired
7 | }
8 | /*
9 | Store an item in keychain.
10 | */
11 | func createKeychainValue(key: String, value: String, accessible:String) -> Bool {
12 | let keyData = appBundleName+"."+key
13 | let tag = keyData.data(using: .utf8)!
14 | let valData = value.data(using: .utf8)
15 | let query:[String : Any] = [
16 | kSecClass as String: kSecClassKey,
17 | kSecAttrApplicationTag as String : tag,
18 | kSecValueData as String : valData!,
19 | kSecAttrAccessible as String: self.accessibleValue(accessible: accessible)
20 | ]
21 |
22 | SecItemDelete(query as CFDictionary)
23 |
24 | let status = SecItemAdd(query as CFDictionary, nil)
25 |
26 | return status == errSecSuccess
27 |
28 | }
29 |
30 | /*
31 | Get value from keychain by key.
32 | */
33 | func getKeychainValue(key: String) -> Data? {
34 | let keyData = appBundleName+"."+key
35 | let tag = keyData.data(using: .utf8)!
36 |
37 | let query: [String : Any] = [
38 | kSecClass as String: kSecClassKey,
39 | kSecAttrApplicationTag as String: tag,
40 | kSecReturnData as String: kCFBooleanTrue!,
41 | ]
42 | var dataTypeRef: AnyObject? = nil
43 |
44 | let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
45 |
46 | if status == noErr {
47 | return dataTypeRef as! Data?
48 | } else {
49 | return nil
50 | }
51 |
52 | }
53 | /*
54 | Check if key exist in keychain.
55 | */
56 | func keychainValueExist(key: String) -> Bool {
57 | let keyData = appBundleName+"."+key
58 | let tag = keyData.data(using: .utf8)!
59 |
60 | let query: [String : Any] = [
61 | kSecClass as String: kSecClassKey,
62 | kSecAttrApplicationTag as String: tag,
63 | kSecReturnData as String: kCFBooleanTrue!,
64 | ]
65 |
66 | let status = SecItemCopyMatching(query as CFDictionary, nil)
67 |
68 | return status == errSecSuccess
69 |
70 | }
71 |
72 | /*
73 | Get all stored keys from keychain.
74 | */
75 | func getAllKeychainKeys() -> [String] {
76 | var keys = [String]()
77 | let query: [String: Any] = [
78 | kSecClass as String : kSecClassKey,
79 | kSecReturnData as String : kCFBooleanTrue!,
80 | kSecReturnAttributes as String : kCFBooleanTrue!,
81 | kSecMatchLimit as String: kSecMatchLimitAll
82 | ]
83 |
84 | var result: AnyObject? = nil
85 |
86 | let lastResultCode = withUnsafeMutablePointer(to: &result) {
87 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
88 | }
89 |
90 | if lastResultCode == noErr {
91 | let array = result as? Array>
92 |
93 | for item in array! {
94 | let key = item[kSecAttrApplicationTag as String]
95 | let replaceKey = String(data:key as! Data, encoding: .utf8)!
96 | let result = replaceKey.replacingOccurrences(of: appBundleName+".", with: "")
97 | keys.append(result)
98 | }
99 | }
100 |
101 | return keys
102 |
103 | }
104 |
105 | /*
106 | Set multiple values from keychain with keys
107 | */
108 | func multiSetKeychainValues(keyValuePairs: [String: String], accessible: String) -> Bool {
109 | var settedPairs = 0
110 | for (key, value) in keyValuePairs {
111 | let val = self.createKeychainValue(key: key, value: value, accessible: accessible)
112 | if val {
113 | settedPairs += 1
114 | }
115 | }
116 | return settedPairs == keyValuePairs.count
117 | }
118 |
119 |
120 | /*
121 | Get multiple values from keychain with keys
122 | */
123 | func multiGetKeychainValues(keys: [String]) throws -> [String: String?] {
124 | guard !keys.isEmpty else {
125 | throw RNSecureErrors.KEY_REQUIRED
126 | }
127 |
128 | var values = [String: String?]()
129 | for key in keys {
130 | if let val = self.getKeychainValue(key: key) {
131 | values[key] = String(data: val, encoding: .utf8)
132 | } else {
133 | values[key] = nil
134 | }
135 | }
136 | return values
137 | }
138 |
139 |
140 | /*
141 | Remove keychain item.
142 | */
143 | func removeKeychainValue(key: String) -> Bool {
144 | let keyData = appBundleName+"."+key
145 | let tag = keyData.data(using: .utf8)!
146 |
147 | let query: [String : Any] = [
148 | kSecClass as String: kSecClassKey,
149 | kSecAttrApplicationTag as String: tag,
150 | kSecReturnData as String: kCFBooleanTrue!,
151 | ]
152 |
153 | return SecItemDelete(query as CFDictionary) == errSecSuccess
154 | }
155 |
156 | /*
157 | Multi remove keychain item.
158 | */
159 | func multiRemoveKeychainValue(keys: [String]) -> [String] {
160 | var unremovedKeys = [String]()
161 | for key in keys {
162 | let val = self.removeKeychainValue(key: key)
163 | if !val {
164 | unremovedKeys.append(key)
165 | }
166 | }
167 | return unremovedKeys
168 | }
169 |
170 | /*
171 | Clear all stored keys
172 | */
173 | func clearAllStoredValues() -> [String] {
174 | var keys = [String]()
175 | var unremovedKeys = [String]()
176 | let query: [String: Any] = [
177 | kSecClass as String : kSecClassKey,
178 | kSecReturnData as String : kCFBooleanTrue!,
179 | kSecReturnAttributes as String : kCFBooleanTrue!,
180 | kSecMatchLimit as String: kSecMatchLimitAll
181 | ]
182 |
183 | var result: AnyObject? = nil
184 |
185 | let lastResultCode = withUnsafeMutablePointer(to: &result) {
186 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
187 | }
188 |
189 | if lastResultCode == noErr {
190 | let array = result as? Array>
191 |
192 | for item in array! {
193 | let key = item[kSecAttrApplicationTag as String]
194 | let replaceKey = String(data:key as! Data, encoding: .utf8)!
195 | let result = replaceKey.replacingOccurrences(of: appBundleName+".", with: "")
196 | keys.append(result)
197 | }
198 | }
199 |
200 | for key in keys {
201 | let status = self.removeKeychainValue(key: key)
202 | if !status {
203 | unremovedKeys.append(key)
204 | }
205 | }
206 | return unremovedKeys
207 | }
208 |
209 | func accessibleValue(accessible: String) -> CFString {
210 |
211 | let list = [
212 | "AccessibleWhenUnlocked": kSecAttrAccessibleWhenUnlocked,
213 | "AccessibleAfterFirstUnlock": kSecAttrAccessibleAfterFirstUnlock,
214 | "AccessibleAlways": kSecAttrAccessibleAlways,
215 | "AccessibleWhenPasscodeSetThisDeviceOnly": kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
216 | "AccessibleWhenUnlockedThisDeviceOnly": kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
217 | "AccessibleAfterFirstUnlockThisDeviceOnly": kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
218 | "AccessibleAlwaysThisDeviceOnly": kSecAttrAccessibleAlwaysThisDeviceOnly
219 | ]
220 |
221 | return list[accessible]!
222 |
223 | }
224 |
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rn-secure-storage",
3 | "version": "3.0.1",
4 | "description": "Secure Storage for React Native (Android & iOS) - Keychain & Keystore",
5 | "main": "index.js",
6 | "types": "rn-secure-storage.d.ts",
7 | "keywords": [
8 | "react-native",
9 | "ios",
10 | "android",
11 | "KeyChain service",
12 | "KeyStore service",
13 | "Device Security",
14 | "keystore",
15 | "keychain",
16 | "secure-preferences",
17 | "token",
18 | "react-token",
19 | "multiple values",
20 | "multiple keys"
21 | ],
22 | "author": "Talut TASGIRAN (https://talut.dev)",
23 | "license": "MIT",
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/talut/rn-secure-storage.git"
27 | },
28 | "bugs": {
29 | "url": "https://github.com/talut/rn-secure-storage/issues"
30 | },
31 | "homepage": "https://github.com/talut/rn-secure-storage#readme"
32 | }
33 |
--------------------------------------------------------------------------------
/rn-secure-storage.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for rn-secure-storage 3.0.0
2 | // Project: https://github.com/akiver/rn-secure-storage
3 | // Definitions by: Talut TASGIRAN
4 | // TypeScript Version: 3.9.6
5 |
6 | declare module "rn-secure-storage" {
7 | export enum ACCESSIBLE {
8 | /**
9 | * The data in the keychain item cannot be accessed after a restart until the device
10 | * has been unlocked once by the user.
11 | */
12 | AFTER_FIRST_UNLOCK = "AccessibleAfterFirstUnlock",
13 | /**
14 | * The data in the keychain item cannot be accessed after a restart until the device
15 | * has been unlocked once by the user.
16 | * Items with this attribute never migrate to a new device.
17 | */
18 | AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY = "AccessibleAfterFirstUnlockThisDeviceOnly",
19 | /**
20 | * The data in the keychain item can always be accessed regardless of whether
21 | * the device is locked.
22 | */
23 | ALWAYS = "AccessibleAlways",
24 | /**
25 | * The data in the keychain item can always be accessed regardless of whether the
26 | * device is locked.
27 | * Items with this attribute never migrate to a new device.
28 | */
29 | ALWAYS_THIS_DEVICE_ONLY = "AccessibleAlwaysThisDeviceOnly",
30 | /**
31 | * The data in the keychain can only be accessed when the device is unlocked.
32 | * Only available if a passcode is set on the device.
33 | * Items with this attribute never migrate to a new device.
34 | */
35 | WHEN_PASSCODE_SET_THIS_DEVICE_ONLY = "AccessibleWhenPasscodeSetThisDeviceOnly",
36 | /**
37 | * The data in the keychain item can be accessed only while the device is
38 | * unlocked by the user.
39 | * This is the default value.
40 | */
41 | WHEN_UNLOCKED = "AccessibleWhenUnlocked",
42 | /**
43 | * The data in the keychain item can be accessed only while the device is
44 | * unlocked by the user.
45 | * Items with this attribute do not migrate to a new device.
46 | */
47 | WHEN_UNLOCKED_THIS_DEVICE_ONLY = "AccessibleWhenUnlockedThisDeviceOnly",
48 | }
49 |
50 |
51 | type SetOptions = {
52 | /**
53 | * iOS ONLY!
54 | * This indicates when a keychain item is accessible, see possible values in RNSecureStorage.ACCESSIBLE.
55 | * Default: ACCESSIBLE.WHEN_UNLOCKED
56 | */
57 | accessible?: ACCESSIBLE,
58 | }
59 |
60 | const RNSecureStorage: RNSecureStorageStatic;
61 |
62 | export interface RNSecureStorageStatic {
63 | /**
64 | * Set a value.
65 | */
66 | setItem(key: string, value: string, options: SetOptions): Promise;
67 |
68 | /**
69 | * Get a value from secure storage.
70 | */
71 | getItem(key: string): Promise;
72 |
73 | /**
74 | * Checks if a key has been set.
75 | */
76 | exist(key: string): Promise;
77 |
78 | /**
79 | * Get all keys from secure storage.
80 | */
81 | getAllKeys(): Promise;
82 |
83 | /**
84 | * Multiple key pair set for secure storage
85 | */
86 | multiSet(pairs: { [key: string]: string }, options: SetOptions): Promise;
87 |
88 | /**
89 | * Get multiple values from secure storage.
90 | */
91 | multiGet(keys: string[]): Promise<{[key: string]: string} | null>;
92 |
93 | /**
94 | * Remove a value from secure storage.
95 | */
96 | removeItem(key: string): Promise;
97 |
98 | /**
99 | * Remove values from secure storage (On error will return unremoved keys)
100 | */
101 | multiRemove(keys: string[]): Promise;
102 |
103 | /**
104 | * Removes whole RNSecureStorage data (On error will return unremoved keys)
105 | */
106 | clear(): Promise;
107 | }
108 |
109 | export default RNSecureStorage;
110 | }
111 |
--------------------------------------------------------------------------------
/sample/.bundle/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_version
64 | @bundler_version ||=
65 | env_var_version || cli_arg_version ||
66 | lockfile_version
67 | end
68 |
69 | def bundler_requirement
70 | return "#{Gem::Requirement.default}.a" unless bundler_version
71 |
72 | bundler_gem_version = Gem::Version.new(bundler_version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/sample/.bundle/bin/fuzzy_match:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'fuzzy_match' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("fuzzy_match", "fuzzy_match")
30 |
--------------------------------------------------------------------------------
/sample/.bundle/bin/httpclient:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'httpclient' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("httpclient", "httpclient")
30 |
--------------------------------------------------------------------------------
/sample/.bundle/bin/pod:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'pod' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("cocoapods", "pod")
30 |
--------------------------------------------------------------------------------
/sample/.bundle/bin/sandbox-pod:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sandbox-pod' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("cocoapods", "sandbox-pod")
30 |
--------------------------------------------------------------------------------
/sample/.bundle/bin/xcodeproj:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'xcodeproj' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("xcodeproj", "xcodeproj")
30 |
--------------------------------------------------------------------------------
/sample/.bundle/config:
--------------------------------------------------------------------------------
1 | BUNDLE_PATH: "vendor/bundle"
2 | BUNDLE_FORCE_RUBY_PLATFORM: 1
3 |
--------------------------------------------------------------------------------
/sample/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ["@react-native-community", "prettier"],
4 | parser: "@typescript-eslint/parser",
5 | plugins: ["@typescript-eslint"],
6 | };
7 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | ios/.xcode.env.local
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 | *.hprof
33 | .cxx/
34 | *.keystore
35 | !debug.keystore
36 |
37 | # node.js
38 | #
39 | node_modules/
40 | npm-debug.log
41 | yarn-error.log
42 |
43 | # fastlane
44 | #
45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
46 | # screenshots whenever they are needed.
47 | # For more information about the recommended setup visit:
48 | # https://docs.fastlane.tools/best-practices/source-control/
49 |
50 | **/fastlane/report.xml
51 | **/fastlane/Preview.html
52 | **/fastlane/screenshots
53 | **/fastlane/test_output
54 |
55 | # Bundle artifact
56 | *.jsbundle
57 |
58 | # Ruby / CocoaPods
59 | /ios/Pods/
60 | /vendor/bundle/
61 |
62 | # Temporary files created by Metro to check the health of the file watcher
63 | .metro-health-check*
64 |
65 | # testing
66 | /coverage
67 |
--------------------------------------------------------------------------------
/sample/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: true,
3 | jsxBracketSameLine: true,
4 | singleQuote: false,
5 | doubleQuote: true,
6 | trailingComma: "all",
7 | arrowParens: "avoid",
8 | printWidth: 140,
9 | parser: "typescript",
10 | useTabs: true,
11 | };
12 |
--------------------------------------------------------------------------------
/sample/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/sample/App.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Sample React Native App
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | import React from "react";
9 | import { Animated, Button, SafeAreaView, StyleSheet, Text, View } from "react-native";
10 | import RNSecureStorage, { ACCESSIBLE } from "rn-secure-storage";
11 | import ScrollView = Animated.ScrollView;
12 |
13 |
14 | const options = {
15 | accessible: ACCESSIBLE.WHEN_UNLOCKED
16 | };
17 |
18 | function App(): React.JSX.Element {
19 |
20 |
21 | const [message, setMessage] = React.useState(null);
22 | const [error, setError] = React.useState(null);
23 | const setItem = () => {
24 | RNSecureStorage.setItem("idToken", "sdoi34y5o34webfld,v sv", {
25 | accessible: ACCESSIBLE.WHEN_UNLOCKED
26 | }).then(res => {
27 | setMessage(res);
28 | setError(null);
29 | }).catch(err => {
30 | setError(err);
31 | setMessage(null);
32 | }
33 | );
34 | };
35 |
36 | const getItem = () => {
37 | RNSecureStorage.getItem("idToken").then(res => {
38 | setMessage(res);
39 | setError(null);
40 | }).catch(err => {
41 | setError(err);
42 | setMessage(null);
43 | }
44 | );
45 | };
46 |
47 | const removeItem = () => {
48 | RNSecureStorage.removeItem("idToken").then(res => {
49 | setMessage(res);
50 | setError(null);
51 | }).catch(err => {
52 | setError(err);
53 | setMessage(null);
54 | }
55 | );
56 | };
57 |
58 | const getAllKeys = () => {
59 | RNSecureStorage.getAllKeys().then(res => {
60 | if (res) setMessage(res.join(", "));
61 | setError(null);
62 | }).catch(err => {
63 | setError(err);
64 | setMessage(null);
65 | }
66 | );
67 | };
68 |
69 | const exist = () => {
70 | RNSecureStorage.exist("idToken").then(res => {
71 | setMessage(res ? "Exists" : "Not exists");
72 | setError(null);
73 | }).catch(err => {
74 | setError(err);
75 | setMessage(null);
76 | }
77 | );
78 | };
79 |
80 | const clear = () => {
81 | RNSecureStorage.clear().then(res => {
82 | setMessage(res);
83 | setError(null);
84 | }).catch(err => {
85 | setError(err);
86 | setMessage(null);
87 | }
88 | );
89 | };
90 |
91 |
92 | const multiSet = () => {
93 | const items = {
94 | "multikey1": "multikey1 value",
95 | "multikey2": "multiksdfklhds,v xo4yrotrhukjsbngey2 value"
96 | };
97 |
98 |
99 | RNSecureStorage.multiSet(items, {
100 | accessible: ACCESSIBLE.WHEN_UNLOCKED
101 | }).then(res => {
102 | if (res) setMessage(JSON.stringify(res));
103 | setError(null);
104 | }).catch(err => {
105 | setError(err);
106 | setMessage(null);
107 | });
108 | };
109 |
110 | const multiGet = () => {
111 | RNSecureStorage.multiGet(["multikey1", "multikey2", "multikey3"]).then(res => {
112 | if (res) setMessage(JSON.stringify(res));
113 | setError(null);
114 | }).catch(err => {
115 | setError(err);
116 | setMessage(null);
117 | }
118 | );
119 | };
120 |
121 | const multiRemove = () => {
122 | RNSecureStorage.multiRemove(["multikey1", "multikey3"]).then(res => {
123 | console.log(res);
124 | setMessage(res);
125 | setError(null);
126 | }
127 | ).catch(err => {
128 | console.log("err", err);
129 | setError(err);
130 | setMessage(null);
131 | });
132 | };
133 |
134 |
135 | return (
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | {message &&
176 | Message: {message}
177 | }
178 |
179 | {error &&
182 | Error: {JSON.stringify(error)}
183 | }
184 |
185 |
186 |
187 |
188 | );
189 | }
190 |
191 | const styles = StyleSheet.create({
192 | container: {
193 | backgroundColor: "#e1e1e1",
194 | flex: 1
195 | },
196 | content: {
197 | margin: 16,
198 | flex: 1,
199 | flexDirection: "row",
200 | flexWrap: "wrap",
201 | justifyContent: "space-between"
202 |
203 | },
204 | inputArea: {
205 | width: "48%",
206 | backgroundColor: "#efefef",
207 | padding: 16,
208 | borderRadius: 6,
209 | shadowColor: "#333",
210 | shadowOffset: {
211 | width: 2,
212 | height: 2
213 | },
214 | shadowOpacity: .1,
215 | shadowRadius: 4,
216 | elevation: 5,
217 | marginBottom: 16
218 | },
219 | input: {
220 | borderWidth: 1,
221 | borderColor: "black",
222 | height: 40,
223 | margin: 12,
224 | padding: 10,
225 | borderRadius: 6
226 | },
227 | value: {
228 | fontWeight: "bold",
229 | fontSize: 16,
230 | margin: 12
231 | },
232 | err: {
233 | fontWeight: "bold",
234 | fontSize: 16,
235 | margin: 12,
236 | color: "red"
237 | }
238 | });
239 |
240 | export default App;
241 |
--------------------------------------------------------------------------------
/sample/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | gem 'cocoapods', '~> 1.13'
7 | gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'
8 |
--------------------------------------------------------------------------------
/sample/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.6)
5 | rexml
6 | activesupport (7.0.8)
7 | concurrent-ruby (~> 1.0, >= 1.0.2)
8 | i18n (>= 1.6, < 2)
9 | minitest (>= 5.1)
10 | tzinfo (~> 2.0)
11 | addressable (2.8.6)
12 | public_suffix (>= 2.0.2, < 6.0)
13 | algoliasearch (1.27.5)
14 | httpclient (~> 2.8, >= 2.8.3)
15 | json (>= 1.5.1)
16 | atomos (0.1.3)
17 | claide (1.1.0)
18 | cocoapods (1.14.3)
19 | addressable (~> 2.8)
20 | claide (>= 1.0.2, < 2.0)
21 | cocoapods-core (= 1.14.3)
22 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
23 | cocoapods-downloader (>= 2.1, < 3.0)
24 | cocoapods-plugins (>= 1.0.0, < 2.0)
25 | cocoapods-search (>= 1.0.0, < 2.0)
26 | cocoapods-trunk (>= 1.6.0, < 2.0)
27 | cocoapods-try (>= 1.1.0, < 2.0)
28 | colored2 (~> 3.1)
29 | escape (~> 0.0.4)
30 | fourflusher (>= 2.3.0, < 3.0)
31 | gh_inspector (~> 1.0)
32 | molinillo (~> 0.8.0)
33 | nap (~> 1.0)
34 | ruby-macho (>= 2.3.0, < 3.0)
35 | xcodeproj (>= 1.23.0, < 2.0)
36 | cocoapods-core (1.14.3)
37 | activesupport (>= 5.0, < 8)
38 | addressable (~> 2.8)
39 | algoliasearch (~> 1.0)
40 | concurrent-ruby (~> 1.1)
41 | fuzzy_match (~> 2.0.4)
42 | nap (~> 1.0)
43 | netrc (~> 0.11)
44 | public_suffix (~> 4.0)
45 | typhoeus (~> 1.0)
46 | cocoapods-deintegrate (1.0.5)
47 | cocoapods-downloader (2.1)
48 | cocoapods-plugins (1.0.0)
49 | nap
50 | cocoapods-search (1.0.1)
51 | cocoapods-trunk (1.6.0)
52 | nap (>= 0.8, < 2.0)
53 | netrc (~> 0.11)
54 | cocoapods-try (1.2.0)
55 | colored2 (3.1.2)
56 | concurrent-ruby (1.2.2)
57 | escape (0.0.4)
58 | ethon (0.16.0)
59 | ffi (>= 1.15.0)
60 | ffi (1.16.3)
61 | fourflusher (2.3.1)
62 | fuzzy_match (2.0.4)
63 | gh_inspector (1.1.3)
64 | httpclient (2.8.3)
65 | i18n (1.14.1)
66 | concurrent-ruby (~> 1.0)
67 | json (2.7.1)
68 | minitest (5.20.0)
69 | molinillo (0.8.0)
70 | nanaimo (0.3.0)
71 | nap (1.1.0)
72 | netrc (0.11.0)
73 | public_suffix (4.0.7)
74 | rexml (3.2.6)
75 | ruby-macho (2.5.1)
76 | typhoeus (1.4.1)
77 | ethon (>= 0.9.0)
78 | tzinfo (2.0.6)
79 | concurrent-ruby (~> 1.0)
80 | xcodeproj (1.23.0)
81 | CFPropertyList (>= 2.3.3, < 4.0)
82 | atomos (~> 0.1.3)
83 | claide (>= 1.0.2, < 2.0)
84 | colored2 (~> 3.1)
85 | nanaimo (~> 0.3.0)
86 | rexml (~> 3.2.4)
87 |
88 | PLATFORMS
89 | ruby
90 |
91 | DEPENDENCIES
92 | activesupport (>= 6.1.7.3, < 7.1.0)
93 | cocoapods (~> 1.13)
94 |
95 | RUBY VERSION
96 | ruby 2.7.6p219
97 |
98 | BUNDLED WITH
99 | 2.1.4
100 |
--------------------------------------------------------------------------------
/sample/README.md:
--------------------------------------------------------------------------------
1 | # RNSecureStorage
2 |
3 | [](https://badge.fury.io/js/rn-secure-storage)
4 | [](https://img.shields.io/npm/dm/rn-secure-storage.svg?maxAge=2592000)
5 | [](https://img.shields.io/npm/dt/rn-secure-storage.svg?maxAge=2592000)
6 | 
7 | Secure Storage for React Native (Android & iOS) - Keychain & Keystore
8 |
9 | ### Thanks for using this library
10 |
11 | Please read my disclaimer about maintaining this library [here](#disclaimer)
12 |
13 | #### If you like this library, please consider supporting my work by buying me a coffee
14 |
15 | [](https://buymeacoff.ee/talut)
16 |
17 | ## Getting Started
18 |
19 | **With NPM**
20 |
21 | ```
22 | npm install --save rn-secure-storage
23 | ```
24 |
25 | **With YARN**
26 |
27 | ```
28 | yarn add rn-secure-storage
29 | ```
30 |
31 | ### What's changed in v3.0.0
32 |
33 | - Rewritten Android module with enhanced security features. [I need your reviews](#ineedyourreviews)
34 | - Android minSdkVersion is now 23 (Android 6.0 Marshmallow)
35 | - iOS module redeveloped using Swift and updated APIs.
36 | - Comprehensive renaming and expansion of APIs.
37 | - Modifications to the return types of some APIs.
38 | - Added `clear` for comprehensive data clearance.
39 | - Introduced `getAllKeys` for retrieving all stored keys.
40 | - Implemented `multiSet` for setting multiple values simultaneously.
41 | - New `multiGet` feature for fetching multiple values at once.
42 | - `multiRemove` added for bulk deletion of items.
43 | - `getSupportedBiometryType` introduced for iOS (supports biometric authentication types).
44 |
45 | ### API
46 |
47 | - [setItem](#setitem)
48 | - [getItem](#getitem)
49 | - [removeItem](#removeitem)
50 | - [exists](#exists)
51 | - [getAllKeys](#getallkeys)
52 | - [clear](#clear)
53 | - [multiSet](#multiset)
54 | - [multiGet](#multiget)
55 | - [multiRemove](#multiremove)
56 | - [getSupportedBiometryType](#getsupportedbiometrytype)
57 |
58 | ### setItem
59 |
60 | ```js
61 | import RNSecureStorage, {ACCESSIBLE} from 'rn-secure-storage';
62 |
63 | RNSecureStorage.setItem("key", "value", {accessible: ACCESSIBLE.WHEN_UNLOCKED})
64 | ```
65 |
66 | ### getItem
67 |
68 | ```js
69 | RNSecureStorage.getItem("key")
70 | ```
71 |
72 | ### removeItem
73 |
74 | ```js
75 | RNSecureStorage.removeItem("key")
76 | ```
77 |
78 | ### exists
79 |
80 | ```js
81 | RNSecureStorage.exist("key")
82 | ```
83 |
84 | ### getAllKeys
85 |
86 | ```js
87 | RNSecureStorage.getAllKeys()
88 | ```
89 |
90 | ### clear
91 |
92 | ```js
93 | RNSecureStorage.clear()
94 | ```
95 |
96 | ### multiSet
97 |
98 | ```js
99 | import RNSecureStorage, {ACCESSIBLE} from 'rn-secure-storage';
100 |
101 | const items = {"key_1": "multi key 1", "key_2": "multi key 2"};
102 | RNSecureStorage.multiSet(items, {accessible: ACCESSIBLE.WHEN_UNLOCKED})
103 | ```
104 |
105 | ### multiGet
106 |
107 | ```js
108 | RNSecureStorage.multiGet(["key_1", "key_2"])
109 | ```
110 |
111 | ### multiRemove
112 |
113 | ```js
114 | RNSecureStorage.multiRemove(["key_1", "key_2"])
115 | ```
116 |
117 | ### getSupportedBiometryType
118 |
119 | ```js
120 | RNSecureStorage.getSupportedBiometryType()
121 | ```
122 |
123 | ## Options
124 |
125 | | Key | Platform | Description | Default |
126 | |------------------|----------|--------------------------------------------------------------------------------------------------|---------------------------------------|
127 | | **`accessible`** | iOS only | This indicates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. | *`Keychain.ACCESSIBLE.WHEN_UNLOCKED`* |
128 |
129 | ### `Keychain.ACCESSIBLE` enum
130 |
131 | | Key | Description |
132 | |-------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
133 | | **`WHEN_UNLOCKED`** | The data in the keychain item can be accessed only while the device is unlocked by the user. |
134 | | **`AFTER_FIRST_UNLOCK`** | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. |
135 | | **`ALWAYS`** | The data in the keychain item can always be accessed regardless of whether the device is locked. |
136 | | **`WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`** | The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device. Items with this attribute never migrate to a new device. |
137 | | **`WHEN_UNLOCKED_THIS_DEVICE_ONLY`** | The data in the keychain item can be accessed only while the device is unlocked by the user. Items with this attribute do not migrate to a new device. |
138 | | **`AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`** | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. Items with this attribute never migrate to a new device. |
139 | | **`ALWAYS_THIS_DEVICE_ONLY`** | The data in the keychain item can always be accessed regardless of whether the device is locked. Items with this attribute never migrate to a new device. |
140 |
141 | #### You can also check out sample project for more details
142 |
143 | ## License
144 |
145 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
146 |
147 | ### I need your reviews
148 |
149 | I have rewritten the Android module with enhanced security features. I need your reviews. Please test the new version and let me know your
150 | thoughts. I will be happy to hear your suggestions and comments. I'm planning to release the new version to handle biometric authentication
151 | on Android.
152 |
153 | ### Connect with me
154 |
155 |