react-native-nitro-storage 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +54 -5
- package/cpp/bindings/HybridStorage.cpp +167 -22
- package/cpp/bindings/HybridStorage.hpp +12 -1
- package/cpp/core/NativeStorageAdapter.hpp +3 -0
- package/ios/IOSStorageAdapterCpp.hpp +16 -0
- package/ios/IOSStorageAdapterCpp.mm +135 -11
- package/lib/commonjs/index.js +466 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +564 -270
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +466 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +564 -272
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +24 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/Storage.nitro.d.ts +2 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +38 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +40 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +1 -1
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +616 -296
- package/src/index.web.ts +728 -288
- package/src/internal.ts +28 -0
|
@@ -14,6 +14,7 @@ namespace margelo::nitro::NitroStorage {
|
|
|
14
14
|
|
|
15
15
|
namespace {
|
|
16
16
|
constexpr auto kBatchMissingSentinel = "__nitro_storage_batch_missing__::v1";
|
|
17
|
+
constexpr int kDefaultBiometricLevel = 2;
|
|
17
18
|
} // namespace
|
|
18
19
|
|
|
19
20
|
HybridStorage::HybridStorage()
|
|
@@ -74,7 +75,8 @@ void HybridStorage::set(const std::string& key, const std::string& value, double
|
|
|
74
75
|
}
|
|
75
76
|
break;
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
+
|
|
79
|
+
onKeySet(static_cast<int>(s), key);
|
|
78
80
|
notifyListeners(static_cast<int>(s), key, value);
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -143,7 +145,8 @@ void HybridStorage::remove(const std::string& key, double scope) {
|
|
|
143
145
|
}
|
|
144
146
|
break;
|
|
145
147
|
}
|
|
146
|
-
|
|
148
|
+
|
|
149
|
+
onKeyRemove(static_cast<int>(s), key);
|
|
147
150
|
notifyListeners(static_cast<int>(s), key, std::nullopt);
|
|
148
151
|
}
|
|
149
152
|
|
|
@@ -179,11 +182,56 @@ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
|
|
|
179
182
|
return keys;
|
|
180
183
|
}
|
|
181
184
|
case Scope::Disk:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
case Scope::Secure: {
|
|
186
|
+
const int scopeValue = static_cast<int>(s);
|
|
187
|
+
ensureKeyIndexHydrated(scopeValue);
|
|
188
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
189
|
+
auto indexIt = keyIndex_.find(scopeValue);
|
|
190
|
+
if (indexIt == keyIndex_.end()) {
|
|
191
|
+
return {};
|
|
192
|
+
}
|
|
193
|
+
return toVector(indexIt->second);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
std::vector<std::string> HybridStorage::getKeysByPrefix(const std::string& prefix, double scope) {
|
|
200
|
+
Scope s = toScope(scope);
|
|
201
|
+
if (prefix.empty()) {
|
|
202
|
+
return getAllKeys(scope);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
switch (s) {
|
|
206
|
+
case Scope::Memory: {
|
|
207
|
+
std::lock_guard<std::mutex> lock(memoryMutex_);
|
|
208
|
+
std::vector<std::string> keys;
|
|
209
|
+
keys.reserve(memoryStore_.size());
|
|
210
|
+
for (const auto& [key, _] : memoryStore_) {
|
|
211
|
+
if (key.rfind(prefix, 0) == 0) {
|
|
212
|
+
keys.push_back(key);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return keys;
|
|
216
|
+
}
|
|
217
|
+
case Scope::Disk:
|
|
218
|
+
case Scope::Secure: {
|
|
219
|
+
const int scopeValue = static_cast<int>(s);
|
|
220
|
+
ensureKeyIndexHydrated(scopeValue);
|
|
221
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
222
|
+
std::vector<std::string> keys;
|
|
223
|
+
auto indexIt = keyIndex_.find(scopeValue);
|
|
224
|
+
if (indexIt == keyIndex_.end()) {
|
|
225
|
+
return keys;
|
|
226
|
+
}
|
|
227
|
+
keys.reserve(indexIt->second.size());
|
|
228
|
+
for (const auto& key : indexIt->second) {
|
|
229
|
+
if (key.rfind(prefix, 0) == 0) {
|
|
230
|
+
keys.push_back(key);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return keys;
|
|
234
|
+
}
|
|
187
235
|
}
|
|
188
236
|
return {};
|
|
189
237
|
}
|
|
@@ -197,11 +245,16 @@ double HybridStorage::size(double scope) {
|
|
|
197
245
|
return static_cast<double>(memoryStore_.size());
|
|
198
246
|
}
|
|
199
247
|
case Scope::Disk:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
248
|
+
case Scope::Secure: {
|
|
249
|
+
const int scopeValue = static_cast<int>(s);
|
|
250
|
+
ensureKeyIndexHydrated(scopeValue);
|
|
251
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
252
|
+
auto indexIt = keyIndex_.find(scopeValue);
|
|
253
|
+
if (indexIt == keyIndex_.end()) {
|
|
254
|
+
return 0.0;
|
|
255
|
+
}
|
|
256
|
+
return static_cast<double>(indexIt->second.size());
|
|
257
|
+
}
|
|
205
258
|
}
|
|
206
259
|
return 0.0;
|
|
207
260
|
}
|
|
@@ -261,7 +314,8 @@ void HybridStorage::clear(double scope) {
|
|
|
261
314
|
}
|
|
262
315
|
break;
|
|
263
316
|
}
|
|
264
|
-
|
|
317
|
+
|
|
318
|
+
onScopeClear(static_cast<int>(s));
|
|
265
319
|
notifyListeners(static_cast<int>(s), "", std::nullopt);
|
|
266
320
|
}
|
|
267
321
|
|
|
@@ -303,6 +357,9 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
|
|
|
303
357
|
}
|
|
304
358
|
|
|
305
359
|
const auto scopeValue = static_cast<int>(s);
|
|
360
|
+
for (const auto& key : keys) {
|
|
361
|
+
onKeySet(scopeValue, key);
|
|
362
|
+
}
|
|
306
363
|
const auto listeners = copyListenersForScope(scopeValue);
|
|
307
364
|
for (size_t i = 0; i < keys.size(); ++i) {
|
|
308
365
|
notifyListeners(listeners, keys[i], values[i]);
|
|
@@ -399,6 +456,9 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
|
|
|
399
456
|
}
|
|
400
457
|
|
|
401
458
|
const auto scopeValue = static_cast<int>(s);
|
|
459
|
+
for (const auto& key : keys) {
|
|
460
|
+
onKeyRemove(scopeValue, key);
|
|
461
|
+
}
|
|
402
462
|
const auto listeners = copyListenersForScope(scopeValue);
|
|
403
463
|
for (const auto& key : keys) {
|
|
404
464
|
notifyListeners(listeners, key, std::nullopt);
|
|
@@ -410,14 +470,7 @@ void HybridStorage::removeByPrefix(const std::string& prefix, double scope) {
|
|
|
410
470
|
return;
|
|
411
471
|
}
|
|
412
472
|
|
|
413
|
-
const auto
|
|
414
|
-
std::vector<std::string> prefixedKeys;
|
|
415
|
-
prefixedKeys.reserve(keys.size());
|
|
416
|
-
for (const auto& key : keys) {
|
|
417
|
-
if (key.rfind(prefix, 0) == 0) {
|
|
418
|
-
prefixedKeys.push_back(key);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
473
|
+
const auto prefixedKeys = getKeysByPrefix(prefix, scope);
|
|
421
474
|
|
|
422
475
|
if (prefixedKeys.empty()) {
|
|
423
476
|
return;
|
|
@@ -446,9 +499,18 @@ void HybridStorage::setKeychainAccessGroup(const std::string& group) {
|
|
|
446
499
|
// --- Biometric ---
|
|
447
500
|
|
|
448
501
|
void HybridStorage::setSecureBiometric(const std::string& key, const std::string& value) {
|
|
502
|
+
setSecureBiometricWithLevel(key, value, kDefaultBiometricLevel);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
void HybridStorage::setSecureBiometricWithLevel(const std::string& key, const std::string& value, double level) {
|
|
449
506
|
ensureAdapter();
|
|
450
507
|
try {
|
|
451
|
-
nativeAdapter_->
|
|
508
|
+
nativeAdapter_->setSecureBiometricWithLevel(
|
|
509
|
+
key,
|
|
510
|
+
value,
|
|
511
|
+
static_cast<int>(level)
|
|
512
|
+
);
|
|
513
|
+
onKeySet(static_cast<int>(Scope::Secure), key);
|
|
452
514
|
notifyListeners(static_cast<int>(Scope::Secure), key, value);
|
|
453
515
|
} catch (const std::exception& e) {
|
|
454
516
|
throw std::runtime_error(std::string("NitroStorage: Biometric set failed: ") + e.what());
|
|
@@ -468,6 +530,7 @@ void HybridStorage::deleteSecureBiometric(const std::string& key) {
|
|
|
468
530
|
ensureAdapter();
|
|
469
531
|
try {
|
|
470
532
|
nativeAdapter_->deleteSecureBiometric(key);
|
|
533
|
+
onKeyRemove(static_cast<int>(Scope::Secure), key);
|
|
471
534
|
notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
|
|
472
535
|
} catch (const std::exception& e) {
|
|
473
536
|
throw std::runtime_error(std::string("NitroStorage: Biometric delete failed: ") + e.what());
|
|
@@ -483,6 +546,7 @@ void HybridStorage::clearSecureBiometric() {
|
|
|
483
546
|
ensureAdapter();
|
|
484
547
|
try {
|
|
485
548
|
nativeAdapter_->clearSecureBiometric();
|
|
549
|
+
onScopeClear(static_cast<int>(Scope::Secure));
|
|
486
550
|
notifyListeners(static_cast<int>(Scope::Secure), "", std::nullopt);
|
|
487
551
|
} catch (const std::exception& e) {
|
|
488
552
|
throw std::runtime_error(std::string("NitroStorage: Biometric clear failed: ") + e.what());
|
|
@@ -527,6 +591,87 @@ void HybridStorage::notifyListeners(
|
|
|
527
591
|
notifyListeners(listeners, key, value);
|
|
528
592
|
}
|
|
529
593
|
|
|
594
|
+
std::vector<std::string> HybridStorage::toVector(const std::unordered_set<std::string>& keys) {
|
|
595
|
+
std::vector<std::string> values;
|
|
596
|
+
values.reserve(keys.size());
|
|
597
|
+
for (const auto& key : keys) {
|
|
598
|
+
values.push_back(key);
|
|
599
|
+
}
|
|
600
|
+
return values;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
void HybridStorage::ensureKeyIndexHydrated(int scope) {
|
|
604
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
{
|
|
609
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
610
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
611
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
ensureAdapter();
|
|
617
|
+
std::vector<std::string> keys;
|
|
618
|
+
try {
|
|
619
|
+
if (scope == static_cast<int>(Scope::Disk)) {
|
|
620
|
+
keys = nativeAdapter_->getAllKeysDisk();
|
|
621
|
+
} else {
|
|
622
|
+
keys = nativeAdapter_->getAllKeysSecure();
|
|
623
|
+
}
|
|
624
|
+
} catch (const std::exception& e) {
|
|
625
|
+
throw std::runtime_error(std::string("NitroStorage: Key index hydration failed: ") + e.what());
|
|
626
|
+
} catch (...) {
|
|
627
|
+
throw std::runtime_error("NitroStorage: Key index hydration failed");
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
631
|
+
auto& index = keyIndex_[scope];
|
|
632
|
+
index.clear();
|
|
633
|
+
for (const auto& key : keys) {
|
|
634
|
+
index.insert(key);
|
|
635
|
+
}
|
|
636
|
+
keyIndexHydrated_[scope] = true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
void HybridStorage::onKeySet(int scope, const std::string& key) {
|
|
640
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
645
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
646
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
647
|
+
keyIndex_[scope].insert(key);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
void HybridStorage::onKeyRemove(int scope, const std::string& key) {
|
|
652
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
657
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
658
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
659
|
+
keyIndex_[scope].erase(key);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
void HybridStorage::onScopeClear(int scope) {
|
|
664
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
669
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
670
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
671
|
+
keyIndex_[scope].clear();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
530
675
|
void HybridStorage::ensureAdapter() const {
|
|
531
676
|
if (!nativeAdapter_) {
|
|
532
677
|
throw std::runtime_error("NitroStorage: Native adapter not initialized");
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#include <functional>
|
|
9
9
|
#include <memory>
|
|
10
10
|
#include <vector>
|
|
11
|
+
#include <unordered_set>
|
|
11
12
|
|
|
12
13
|
namespace margelo::nitro::NitroStorage {
|
|
13
14
|
|
|
@@ -31,6 +32,7 @@ public:
|
|
|
31
32
|
void clear(double scope) override;
|
|
32
33
|
bool has(const std::string& key, double scope) override;
|
|
33
34
|
std::vector<std::string> getAllKeys(double scope) override;
|
|
35
|
+
std::vector<std::string> getKeysByPrefix(const std::string& prefix, double scope) override;
|
|
34
36
|
double size(double scope) override;
|
|
35
37
|
void setBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values, double scope) override;
|
|
36
38
|
std::vector<std::string> getBatch(const std::vector<std::string>& keys, double scope) override;
|
|
@@ -44,6 +46,7 @@ public:
|
|
|
44
46
|
void setSecureWritesAsync(bool enabled) override;
|
|
45
47
|
void setKeychainAccessGroup(const std::string& group) override;
|
|
46
48
|
void setSecureBiometric(const std::string& key, const std::string& value) override;
|
|
49
|
+
void setSecureBiometricWithLevel(const std::string& key, const std::string& value, double level) override;
|
|
47
50
|
std::optional<std::string> getSecureBiometric(const std::string& key) override;
|
|
48
51
|
void deleteSecureBiometric(const std::string& key) override;
|
|
49
52
|
bool hasSecureBiometric(const std::string& key) override;
|
|
@@ -65,10 +68,13 @@ private:
|
|
|
65
68
|
std::mutex memoryMutex_;
|
|
66
69
|
|
|
67
70
|
std::shared_ptr<::NitroStorage::NativeStorageAdapter> nativeAdapter_;
|
|
68
|
-
|
|
71
|
+
|
|
69
72
|
HybridStorageMap<int, std::vector<Listener>> listeners_;
|
|
70
73
|
std::mutex listenersMutex_;
|
|
71
74
|
size_t nextListenerId_ = 0;
|
|
75
|
+
HybridStorageMap<int, std::unordered_set<std::string>> keyIndex_;
|
|
76
|
+
HybridStorageMap<int, bool> keyIndexHydrated_;
|
|
77
|
+
std::mutex keyIndexMutex_;
|
|
72
78
|
|
|
73
79
|
std::vector<Listener> copyListenersForScope(int scope);
|
|
74
80
|
void notifyListeners(
|
|
@@ -77,6 +83,11 @@ private:
|
|
|
77
83
|
const std::optional<std::string>& value
|
|
78
84
|
);
|
|
79
85
|
void notifyListeners(int scope, const std::string& key, const std::optional<std::string>& value);
|
|
86
|
+
std::vector<std::string> toVector(const std::unordered_set<std::string>& keys);
|
|
87
|
+
void ensureKeyIndexHydrated(int scope);
|
|
88
|
+
void onKeySet(int scope, const std::string& key);
|
|
89
|
+
void onKeyRemove(int scope, const std::string& key);
|
|
90
|
+
void onScopeClear(int scope);
|
|
80
91
|
void ensureAdapter() const;
|
|
81
92
|
Scope toScope(double scopeValue);
|
|
82
93
|
};
|
|
@@ -15,6 +15,7 @@ public:
|
|
|
15
15
|
virtual void deleteDisk(const std::string& key) = 0;
|
|
16
16
|
virtual bool hasDisk(const std::string& key) = 0;
|
|
17
17
|
virtual std::vector<std::string> getAllKeysDisk() = 0;
|
|
18
|
+
virtual std::vector<std::string> getKeysByPrefixDisk(const std::string& prefix) = 0;
|
|
18
19
|
virtual size_t sizeDisk() = 0;
|
|
19
20
|
virtual void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) = 0;
|
|
20
21
|
virtual std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) = 0;
|
|
@@ -25,6 +26,7 @@ public:
|
|
|
25
26
|
virtual void deleteSecure(const std::string& key) = 0;
|
|
26
27
|
virtual bool hasSecure(const std::string& key) = 0;
|
|
27
28
|
virtual std::vector<std::string> getAllKeysSecure() = 0;
|
|
29
|
+
virtual std::vector<std::string> getKeysByPrefixSecure(const std::string& prefix) = 0;
|
|
28
30
|
virtual size_t sizeSecure() = 0;
|
|
29
31
|
virtual void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) = 0;
|
|
30
32
|
virtual std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) = 0;
|
|
@@ -38,6 +40,7 @@ public:
|
|
|
38
40
|
virtual void setKeychainAccessGroup(const std::string& group) = 0;
|
|
39
41
|
|
|
40
42
|
virtual void setSecureBiometric(const std::string& key, const std::string& value) = 0;
|
|
43
|
+
virtual void setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) = 0;
|
|
41
44
|
virtual std::optional<std::string> getSecureBiometric(const std::string& key) = 0;
|
|
42
45
|
virtual void deleteSecureBiometric(const std::string& key) = 0;
|
|
43
46
|
virtual bool hasSecureBiometric(const std::string& key) = 0;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include "../core/NativeStorageAdapter.hpp"
|
|
4
|
+
#include <mutex>
|
|
5
|
+
#include <unordered_set>
|
|
4
6
|
|
|
5
7
|
namespace NitroStorage {
|
|
6
8
|
|
|
@@ -14,6 +16,7 @@ public:
|
|
|
14
16
|
void deleteDisk(const std::string& key) override;
|
|
15
17
|
bool hasDisk(const std::string& key) override;
|
|
16
18
|
std::vector<std::string> getAllKeysDisk() override;
|
|
19
|
+
std::vector<std::string> getKeysByPrefixDisk(const std::string& prefix) override;
|
|
17
20
|
size_t sizeDisk() override;
|
|
18
21
|
void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
|
|
19
22
|
std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) override;
|
|
@@ -24,6 +27,7 @@ public:
|
|
|
24
27
|
void deleteSecure(const std::string& key) override;
|
|
25
28
|
bool hasSecure(const std::string& key) override;
|
|
26
29
|
std::vector<std::string> getAllKeysSecure() override;
|
|
30
|
+
std::vector<std::string> getKeysByPrefixSecure(const std::string& prefix) override;
|
|
27
31
|
size_t sizeSecure() override;
|
|
28
32
|
void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
|
|
29
33
|
std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) override;
|
|
@@ -37,6 +41,7 @@ public:
|
|
|
37
41
|
void setKeychainAccessGroup(const std::string& group) override;
|
|
38
42
|
|
|
39
43
|
void setSecureBiometric(const std::string& key, const std::string& value) override;
|
|
44
|
+
void setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) override;
|
|
40
45
|
std::optional<std::string> getSecureBiometric(const std::string& key) override;
|
|
41
46
|
void deleteSecureBiometric(const std::string& key) override;
|
|
42
47
|
bool hasSecureBiometric(const std::string& key) override;
|
|
@@ -45,6 +50,17 @@ public:
|
|
|
45
50
|
private:
|
|
46
51
|
int accessControlLevel_ = 0;
|
|
47
52
|
std::string keychainAccessGroup_;
|
|
53
|
+
mutable std::mutex secureKeysMutex_;
|
|
54
|
+
std::unordered_set<std::string> secureKeysCache_;
|
|
55
|
+
std::unordered_set<std::string> biometricKeysCache_;
|
|
56
|
+
bool secureKeyCacheHydrated_ = false;
|
|
57
|
+
|
|
58
|
+
void ensureSecureKeyCacheHydrated();
|
|
59
|
+
void markSecureKeySet(const std::string& key);
|
|
60
|
+
void markSecureKeyRemoved(const std::string& key);
|
|
61
|
+
void markBiometricKeySet(const std::string& key);
|
|
62
|
+
void markBiometricKeyRemoved(const std::string& key);
|
|
63
|
+
void clearSecureKeyCache();
|
|
48
64
|
};
|
|
49
65
|
|
|
50
66
|
} // namespace NitroStorage
|
|
@@ -79,6 +79,18 @@ std::vector<std::string> IOSStorageAdapterCpp::getAllKeysDisk() {
|
|
|
79
79
|
return keys;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixDisk(const std::string& prefix) {
|
|
83
|
+
const auto keys = getAllKeysDisk();
|
|
84
|
+
std::vector<std::string> filtered;
|
|
85
|
+
filtered.reserve(keys.size());
|
|
86
|
+
for (const auto& key : keys) {
|
|
87
|
+
if (key.rfind(prefix, 0) == 0) {
|
|
88
|
+
filtered.push_back(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return filtered;
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
size_t IOSStorageAdapterCpp::sizeDisk() {
|
|
83
95
|
return [NitroDiskDefaults() dictionaryRepresentation].count;
|
|
84
96
|
}
|
|
@@ -177,11 +189,27 @@ void IOSStorageAdapterCpp::setSecure(const std::string& key, const std::string&
|
|
|
177
189
|
|
|
178
190
|
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateAttributes);
|
|
179
191
|
|
|
192
|
+
if (status == errSecSuccess) {
|
|
193
|
+
markSecureKeySet(key);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
180
197
|
if (status == errSecItemNotFound) {
|
|
181
198
|
query[(__bridge id)kSecValueData] = data;
|
|
182
199
|
query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessControlAttr(accessControlLevel_);
|
|
183
|
-
SecItemAdd((__bridge CFDictionaryRef)query, NULL);
|
|
200
|
+
const OSStatus addStatus = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
|
|
201
|
+
if (addStatus != errSecSuccess) {
|
|
202
|
+
throw std::runtime_error(
|
|
203
|
+
"NitroStorage: Secure set failed with status " + std::to_string(addStatus)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
markSecureKeySet(key);
|
|
207
|
+
return;
|
|
184
208
|
}
|
|
209
|
+
|
|
210
|
+
throw std::runtime_error(
|
|
211
|
+
"NitroStorage: Secure set failed with status " + std::to_string(status)
|
|
212
|
+
);
|
|
185
213
|
}
|
|
186
214
|
|
|
187
215
|
std::optional<std::string> IOSStorageAdapterCpp::getSecure(const std::string& key) {
|
|
@@ -207,6 +235,8 @@ void IOSStorageAdapterCpp::deleteSecure(const std::string& key) {
|
|
|
207
235
|
SecItemDelete((__bridge CFDictionaryRef)secureQuery);
|
|
208
236
|
NSMutableDictionary* biometricQuery = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
|
|
209
237
|
SecItemDelete((__bridge CFDictionaryRef)biometricQuery);
|
|
238
|
+
markSecureKeyRemoved(key);
|
|
239
|
+
markBiometricKeyRemoved(key);
|
|
210
240
|
}
|
|
211
241
|
|
|
212
242
|
bool IOSStorageAdapterCpp::hasSecure(const std::string& key) {
|
|
@@ -221,20 +251,36 @@ bool IOSStorageAdapterCpp::hasSecure(const std::string& key) {
|
|
|
221
251
|
}
|
|
222
252
|
|
|
223
253
|
std::vector<std::string> IOSStorageAdapterCpp::getAllKeysSecure() {
|
|
224
|
-
|
|
225
|
-
std::
|
|
226
|
-
std::unordered_set<std::string>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
254
|
+
ensureSecureKeyCacheHydrated();
|
|
255
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
256
|
+
std::unordered_set<std::string> combined = secureKeysCache_;
|
|
257
|
+
combined.insert(biometricKeysCache_.begin(), biometricKeysCache_.end());
|
|
258
|
+
std::vector<std::string> keys;
|
|
259
|
+
keys.reserve(combined.size());
|
|
260
|
+
for (const auto& key : combined) {
|
|
261
|
+
keys.push_back(key);
|
|
232
262
|
}
|
|
233
263
|
return keys;
|
|
234
264
|
}
|
|
235
265
|
|
|
266
|
+
std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixSecure(const std::string& prefix) {
|
|
267
|
+
const auto keys = getAllKeysSecure();
|
|
268
|
+
std::vector<std::string> filtered;
|
|
269
|
+
filtered.reserve(keys.size());
|
|
270
|
+
for (const auto& key : keys) {
|
|
271
|
+
if (key.rfind(prefix, 0) == 0) {
|
|
272
|
+
filtered.push_back(key);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return filtered;
|
|
276
|
+
}
|
|
277
|
+
|
|
236
278
|
size_t IOSStorageAdapterCpp::sizeSecure() {
|
|
237
|
-
|
|
279
|
+
ensureSecureKeyCacheHydrated();
|
|
280
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
281
|
+
std::unordered_set<std::string> combined = secureKeysCache_;
|
|
282
|
+
combined.insert(biometricKeysCache_.begin(), biometricKeysCache_.end());
|
|
283
|
+
return combined.size();
|
|
238
284
|
}
|
|
239
285
|
|
|
240
286
|
void IOSStorageAdapterCpp::setSecureBatch(
|
|
@@ -282,6 +328,7 @@ void IOSStorageAdapterCpp::clearSecure() {
|
|
|
282
328
|
biometricQuery[(__bridge id)kSecAttrAccessGroup] = group;
|
|
283
329
|
}
|
|
284
330
|
SecItemDelete((__bridge CFDictionaryRef)biometricQuery);
|
|
331
|
+
clearSecureKeyCache();
|
|
285
332
|
}
|
|
286
333
|
|
|
287
334
|
// --- Configuration ---
|
|
@@ -296,11 +343,21 @@ void IOSStorageAdapterCpp::setSecureWritesAsync(bool /*enabled*/) {
|
|
|
296
343
|
|
|
297
344
|
void IOSStorageAdapterCpp::setKeychainAccessGroup(const std::string& group) {
|
|
298
345
|
keychainAccessGroup_ = group;
|
|
346
|
+
clearSecureKeyCache();
|
|
299
347
|
}
|
|
300
348
|
|
|
301
349
|
// --- Biometric (separate Keychain service with biometric ACL) ---
|
|
302
350
|
|
|
303
351
|
void IOSStorageAdapterCpp::setSecureBiometric(const std::string& key, const std::string& value) {
|
|
352
|
+
setSecureBiometricWithLevel(key, value, 2);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
void IOSStorageAdapterCpp::setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) {
|
|
356
|
+
if (level == 0) {
|
|
357
|
+
setSecure(key, value);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
304
361
|
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
305
362
|
NSData* data = [[NSString stringWithUTF8String:value.c_str()] dataUsingEncoding:NSUTF8StringEncoding];
|
|
306
363
|
NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
|
|
@@ -310,10 +367,14 @@ void IOSStorageAdapterCpp::setSecureBiometric(const std::string& key, const std:
|
|
|
310
367
|
SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
|
|
311
368
|
|
|
312
369
|
CFErrorRef error = NULL;
|
|
370
|
+
SecAccessControlCreateFlags flags = kSecAccessControlBiometryCurrentSet;
|
|
371
|
+
if (level == 1) {
|
|
372
|
+
flags = kSecAccessControlUserPresence;
|
|
373
|
+
}
|
|
313
374
|
SecAccessControlRef access = SecAccessControlCreateWithFlags(
|
|
314
375
|
kCFAllocatorDefault,
|
|
315
376
|
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
|
|
316
|
-
|
|
377
|
+
flags,
|
|
317
378
|
&error
|
|
318
379
|
);
|
|
319
380
|
|
|
@@ -330,6 +391,7 @@ void IOSStorageAdapterCpp::setSecureBiometric(const std::string& key, const std:
|
|
|
330
391
|
if (status != errSecSuccess) {
|
|
331
392
|
throw std::runtime_error("NitroStorage: Biometric set failed with status " + std::to_string(status));
|
|
332
393
|
}
|
|
394
|
+
markBiometricKeySet(key);
|
|
333
395
|
}
|
|
334
396
|
|
|
335
397
|
std::optional<std::string> IOSStorageAdapterCpp::getSecureBiometric(const std::string& key) {
|
|
@@ -357,6 +419,7 @@ void IOSStorageAdapterCpp::deleteSecureBiometric(const std::string& key) {
|
|
|
357
419
|
NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
|
|
358
420
|
NSMutableDictionary* query = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
|
|
359
421
|
SecItemDelete((__bridge CFDictionaryRef)query);
|
|
422
|
+
markBiometricKeyRemoved(key);
|
|
360
423
|
}
|
|
361
424
|
|
|
362
425
|
bool IOSStorageAdapterCpp::hasSecureBiometric(const std::string& key) {
|
|
@@ -376,6 +439,67 @@ void IOSStorageAdapterCpp::clearSecureBiometric() {
|
|
|
376
439
|
query[(__bridge id)kSecAttrAccessGroup] = group;
|
|
377
440
|
}
|
|
378
441
|
SecItemDelete((__bridge CFDictionaryRef)query);
|
|
442
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
443
|
+
biometricKeysCache_.clear();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
void IOSStorageAdapterCpp::ensureSecureKeyCacheHydrated() {
|
|
447
|
+
{
|
|
448
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
449
|
+
if (secureKeyCacheHydrated_) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
|
|
455
|
+
const std::vector<std::string> secureKeys = keychainAccountsForService(kKeychainService, group);
|
|
456
|
+
const std::vector<std::string> biometricKeys = keychainAccountsForService(kBiometricKeychainService, group);
|
|
457
|
+
|
|
458
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
459
|
+
secureKeysCache_.clear();
|
|
460
|
+
biometricKeysCache_.clear();
|
|
461
|
+
secureKeysCache_.insert(secureKeys.begin(), secureKeys.end());
|
|
462
|
+
biometricKeysCache_.insert(biometricKeys.begin(), biometricKeys.end());
|
|
463
|
+
secureKeyCacheHydrated_ = true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
void IOSStorageAdapterCpp::markSecureKeySet(const std::string& key) {
|
|
467
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
468
|
+
if (!secureKeyCacheHydrated_) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
secureKeysCache_.insert(key);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
void IOSStorageAdapterCpp::markSecureKeyRemoved(const std::string& key) {
|
|
475
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
476
|
+
if (!secureKeyCacheHydrated_) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
secureKeysCache_.erase(key);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
void IOSStorageAdapterCpp::markBiometricKeySet(const std::string& key) {
|
|
483
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
484
|
+
if (!secureKeyCacheHydrated_) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
biometricKeysCache_.insert(key);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
void IOSStorageAdapterCpp::markBiometricKeyRemoved(const std::string& key) {
|
|
491
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
492
|
+
if (!secureKeyCacheHydrated_) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
biometricKeysCache_.erase(key);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
void IOSStorageAdapterCpp::clearSecureKeyCache() {
|
|
499
|
+
std::lock_guard<std::mutex> lock(secureKeysMutex_);
|
|
500
|
+
secureKeysCache_.clear();
|
|
501
|
+
biometricKeysCache_.clear();
|
|
502
|
+
secureKeyCacheHydrated_ = false;
|
|
379
503
|
}
|
|
380
504
|
|
|
381
505
|
} // namespace NitroStorage
|