@bantis/local-cipher 2.2.0 → 2.3.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 +85 -2
- package/dist/angular/StorageService.d.ts +22 -0
- package/dist/angular.d.ts +1 -0
- package/dist/angular.esm.js +506 -29
- package/dist/angular.esm.js.map +1 -1
- package/dist/angular.js +517 -28
- package/dist/angular.js.map +1 -1
- package/dist/core/EncryptionHelper.d.ts +1 -0
- package/dist/core/SecureCookie.d.ts +41 -0
- package/dist/core/SecureStorage.d.ts +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.esm.js +462 -29
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +472 -28
- package/dist/index.js.map +1 -1
- package/dist/managers/CookieManager.d.ts +17 -0
- package/dist/managers/PlainCookie.d.ts +14 -0
- package/dist/managers/PlainStorage.d.ts +13 -0
- package/dist/managers/StorageManager.d.ts +17 -0
- package/dist/managers/index.d.ts +17 -0
- package/dist/react/hooks.d.ts +12 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.esm.js +487 -37
- package/dist/react.esm.js.map +1 -1
- package/dist/react.js +498 -37
- package/dist/react.js.map +1 -1
- package/dist/types/index.d.ts +36 -0
- package/package.json +2 -2
package/dist/react.esm.js
CHANGED
|
@@ -19,6 +19,9 @@ const DEFAULT_CONFIG = {
|
|
|
19
19
|
compressionThreshold: 1024,
|
|
20
20
|
autoCleanup: true,
|
|
21
21
|
cleanupInterval: 60000,
|
|
22
|
+
enableCache: true,
|
|
23
|
+
verifyIntegrityOnRead: false,
|
|
24
|
+
storageEngine: typeof window !== 'undefined' ? window.localStorage : {},
|
|
22
25
|
},
|
|
23
26
|
debug: {
|
|
24
27
|
enabled: false,
|
|
@@ -46,6 +49,8 @@ class EncryptionHelper {
|
|
|
46
49
|
this.baseKey = '';
|
|
47
50
|
this.baseKeyPromise = null;
|
|
48
51
|
this.keyVersion = 1;
|
|
52
|
+
// Cache para optimización de rendimiento
|
|
53
|
+
this.keyNameCache = new Map();
|
|
49
54
|
this.config = { ...DEFAULT_CONFIG.encryption, ...config };
|
|
50
55
|
// Load key version from storage
|
|
51
56
|
const storedVersion = localStorage.getItem(EncryptionHelper.KEY_VERSION_KEY);
|
|
@@ -215,10 +220,16 @@ class EncryptionHelper {
|
|
|
215
220
|
* @returns Nombre encriptado con prefijo __enc_
|
|
216
221
|
*/
|
|
217
222
|
async encryptKey(keyName) {
|
|
223
|
+
// Optimización: devolver desde cache si existe
|
|
224
|
+
if (this.keyNameCache.has(keyName)) {
|
|
225
|
+
return this.keyNameCache.get(keyName);
|
|
226
|
+
}
|
|
218
227
|
const baseKey = await this.generateBaseKey();
|
|
219
228
|
const combined = keyName + baseKey;
|
|
220
229
|
const hash = await this.hashString(combined);
|
|
221
|
-
|
|
230
|
+
const encryptedKey = `__enc_${hash.substring(0, 16)}`;
|
|
231
|
+
this.keyNameCache.set(keyName, encryptedKey);
|
|
232
|
+
return encryptedKey;
|
|
222
233
|
}
|
|
223
234
|
/**
|
|
224
235
|
* Limpia todos los datos encriptados del localStorage
|
|
@@ -236,10 +247,11 @@ class EncryptionHelper {
|
|
|
236
247
|
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
237
248
|
// Eliminar salt
|
|
238
249
|
localStorage.removeItem(EncryptionHelper.SALT_STORAGE_KEY);
|
|
239
|
-
// Resetear clave en memoria
|
|
250
|
+
// Resetear clave en memoria y cache
|
|
240
251
|
this.key = null;
|
|
241
252
|
this.baseKey = '';
|
|
242
253
|
this.baseKeyPromise = null;
|
|
254
|
+
this.keyNameCache.clear();
|
|
243
255
|
}
|
|
244
256
|
/**
|
|
245
257
|
* Verifica si el navegador soporta Web Crypto API
|
|
@@ -679,6 +691,7 @@ function shouldCompress(data, threshold = 1024) {
|
|
|
679
691
|
class SecureStorage {
|
|
680
692
|
constructor(config) {
|
|
681
693
|
this.cleanupInterval = null;
|
|
694
|
+
this.memoryCache = new Map();
|
|
682
695
|
// Merge config with defaults
|
|
683
696
|
this.config = {
|
|
684
697
|
encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
|
|
@@ -760,8 +773,12 @@ class SecureStorage {
|
|
|
760
773
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
761
774
|
// Encrypt the value
|
|
762
775
|
const encryptedValue = await this.encryptionHelper.encrypt(serialized);
|
|
763
|
-
// Store in
|
|
764
|
-
|
|
776
|
+
// Store in storage
|
|
777
|
+
this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
|
|
778
|
+
// Update cache
|
|
779
|
+
if (this.config.storage.enableCache) {
|
|
780
|
+
this.memoryCache.set(key, { value });
|
|
781
|
+
}
|
|
765
782
|
this.logger.verbose(`Stored key: ${key}, compressed: ${compressed}, size: ${encryptedValue.length}`);
|
|
766
783
|
this.eventEmitter.emit('encrypted', { key, metadata: { compressed, size: encryptedValue.length } });
|
|
767
784
|
this.logger.timeEnd(`setItem:${key}`);
|
|
@@ -769,7 +786,7 @@ class SecureStorage {
|
|
|
769
786
|
catch (error) {
|
|
770
787
|
this.logger.error(`Error al guardar dato encriptado para ${key}:`, error);
|
|
771
788
|
this.eventEmitter.emit('error', { key, error: error });
|
|
772
|
-
|
|
789
|
+
this.config.storage.storageEngine.setItem(key, value);
|
|
773
790
|
}
|
|
774
791
|
}
|
|
775
792
|
/**
|
|
@@ -822,8 +839,12 @@ class SecureStorage {
|
|
|
822
839
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
823
840
|
// Encrypt the value
|
|
824
841
|
const encryptedValue = await this.encryptionHelper.encrypt(serialized);
|
|
825
|
-
// Store in
|
|
826
|
-
|
|
842
|
+
// Store in storage
|
|
843
|
+
this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
|
|
844
|
+
// Update cache
|
|
845
|
+
if (this.config.storage.enableCache) {
|
|
846
|
+
this.memoryCache.set(key, { value, expiresAt });
|
|
847
|
+
}
|
|
827
848
|
this.logger.verbose(`Stored key with expiry: ${key}, expiresAt: ${expiresAt}`);
|
|
828
849
|
this.eventEmitter.emit('encrypted', { key, metadata: { compressed, expiresAt } });
|
|
829
850
|
this.logger.timeEnd(`setItemWithExpiry:${key}`);
|
|
@@ -840,16 +861,31 @@ class SecureStorage {
|
|
|
840
861
|
async getItem(key) {
|
|
841
862
|
this.logger.time(`getItem:${key}`);
|
|
842
863
|
if (!EncryptionHelper.isSupported()) {
|
|
843
|
-
return
|
|
864
|
+
return this.config.storage.storageEngine.getItem(key);
|
|
844
865
|
}
|
|
845
866
|
try {
|
|
867
|
+
// Check memory cache first
|
|
868
|
+
if (this.config.storage.enableCache && this.memoryCache.has(key)) {
|
|
869
|
+
const cached = this.memoryCache.get(key);
|
|
870
|
+
if (cached.expiresAt && cached.expiresAt < Date.now()) {
|
|
871
|
+
this.logger.info(`Key expired in cache: ${key}`);
|
|
872
|
+
await this.removeItem(key);
|
|
873
|
+
this.eventEmitter.emit('expired', { key });
|
|
874
|
+
this.logger.timeEnd(`getItem:${key}`);
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
this.logger.debug(`Retrieved from cache: ${key}`);
|
|
878
|
+
this.eventEmitter.emit('decrypted', { key });
|
|
879
|
+
this.logger.timeEnd(`getItem:${key}`);
|
|
880
|
+
return cached.value;
|
|
881
|
+
}
|
|
846
882
|
// Encrypt the key
|
|
847
883
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
848
884
|
// Get encrypted value
|
|
849
|
-
let encryptedValue =
|
|
885
|
+
let encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
850
886
|
// Backward compatibility: try with plain key
|
|
851
887
|
if (!encryptedValue) {
|
|
852
|
-
encryptedValue =
|
|
888
|
+
encryptedValue = this.config.storage.storageEngine.getItem(key);
|
|
853
889
|
if (!encryptedValue) {
|
|
854
890
|
this.logger.timeEnd(`getItem:${key}`);
|
|
855
891
|
return null;
|
|
@@ -885,8 +921,8 @@ class SecureStorage {
|
|
|
885
921
|
this.logger.timeEnd(`getItem:${key}`);
|
|
886
922
|
return null;
|
|
887
923
|
}
|
|
888
|
-
// Verify integrity if checksum exists
|
|
889
|
-
if (storedValue.checksum) {
|
|
924
|
+
// Verify integrity if checksum exists and configured
|
|
925
|
+
if (storedValue.checksum && this.config.storage.verifyIntegrityOnRead) {
|
|
890
926
|
const calculatedChecksum = await this.calculateChecksum(storedValue.value);
|
|
891
927
|
if (calculatedChecksum !== storedValue.checksum) {
|
|
892
928
|
this.logger.warn(`Integrity check failed for key: ${key}`);
|
|
@@ -904,6 +940,9 @@ class SecureStorage {
|
|
|
904
940
|
finalValue = await decompress(new Uint8Array(compressedData));
|
|
905
941
|
this.eventEmitter.emit('decompressed', { key });
|
|
906
942
|
}
|
|
943
|
+
if (this.config.storage.enableCache) {
|
|
944
|
+
this.memoryCache.set(key, { value: finalValue, expiresAt: storedValue.expiresAt });
|
|
945
|
+
}
|
|
907
946
|
this.eventEmitter.emit('decrypted', { key });
|
|
908
947
|
this.logger.timeEnd(`getItem:${key}`);
|
|
909
948
|
return finalValue;
|
|
@@ -912,7 +951,7 @@ class SecureStorage {
|
|
|
912
951
|
this.logger.error(`Error al recuperar dato encriptado para ${key}:`, error);
|
|
913
952
|
this.eventEmitter.emit('error', { key, error: error });
|
|
914
953
|
// Fallback: try plain key
|
|
915
|
-
const fallback =
|
|
954
|
+
const fallback = this.config.storage.storageEngine.getItem(key);
|
|
916
955
|
this.logger.timeEnd(`getItem:${key}`);
|
|
917
956
|
return fallback;
|
|
918
957
|
}
|
|
@@ -922,19 +961,20 @@ class SecureStorage {
|
|
|
922
961
|
*/
|
|
923
962
|
async removeItem(key) {
|
|
924
963
|
if (!EncryptionHelper.isSupported()) {
|
|
925
|
-
|
|
964
|
+
this.config.storage.storageEngine.removeItem(key);
|
|
926
965
|
return;
|
|
927
966
|
}
|
|
928
967
|
try {
|
|
929
968
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
930
|
-
|
|
931
|
-
|
|
969
|
+
this.config.storage.storageEngine.removeItem(encryptedKey);
|
|
970
|
+
this.config.storage.storageEngine.removeItem(key); // Remove both versions
|
|
971
|
+
this.memoryCache.delete(key);
|
|
932
972
|
this.eventEmitter.emit('deleted', { key });
|
|
933
973
|
this.logger.info(`Removed key: ${key}`);
|
|
934
974
|
}
|
|
935
975
|
catch (error) {
|
|
936
976
|
this.logger.error(`Error al eliminar dato para ${key}:`, error);
|
|
937
|
-
|
|
977
|
+
this.config.storage.storageEngine.removeItem(key);
|
|
938
978
|
}
|
|
939
979
|
}
|
|
940
980
|
/**
|
|
@@ -949,6 +989,7 @@ class SecureStorage {
|
|
|
949
989
|
*/
|
|
950
990
|
clear() {
|
|
951
991
|
this.encryptionHelper.clearEncryptedData();
|
|
992
|
+
this.memoryCache.clear();
|
|
952
993
|
this.eventEmitter.emit('cleared', {});
|
|
953
994
|
this.logger.info('All encrypted data cleared');
|
|
954
995
|
}
|
|
@@ -959,21 +1000,28 @@ class SecureStorage {
|
|
|
959
1000
|
this.logger.info('Starting cleanup of expired items...');
|
|
960
1001
|
let cleanedCount = 0;
|
|
961
1002
|
const keysToCheck = [];
|
|
962
|
-
for (let i = 0; i <
|
|
963
|
-
const key =
|
|
1003
|
+
for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
|
|
1004
|
+
const key = this.config.storage.storageEngine.key(i);
|
|
964
1005
|
if (key && key.startsWith('__enc_')) {
|
|
965
1006
|
keysToCheck.push(key);
|
|
966
1007
|
}
|
|
967
1008
|
}
|
|
968
1009
|
for (const encryptedKey of keysToCheck) {
|
|
969
1010
|
try {
|
|
970
|
-
const encryptedValue =
|
|
1011
|
+
const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
971
1012
|
if (!encryptedValue)
|
|
972
1013
|
continue;
|
|
973
1014
|
const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
|
|
974
1015
|
const storedValue = JSON.parse(decrypted);
|
|
975
1016
|
if (storedValue.expiresAt && storedValue.expiresAt < Date.now()) {
|
|
976
|
-
|
|
1017
|
+
this.config.storage.storageEngine.removeItem(encryptedKey);
|
|
1018
|
+
// Encontrar la clave original en el cache y eliminarla
|
|
1019
|
+
for (const [cacheKey] of Array.from(this.memoryCache.entries())) {
|
|
1020
|
+
this.encryptionHelper.encryptKey(cacheKey).then(enc => {
|
|
1021
|
+
if (enc === encryptedKey)
|
|
1022
|
+
this.memoryCache.delete(cacheKey);
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
977
1025
|
cleanedCount++;
|
|
978
1026
|
this.eventEmitter.emit('expired', { key: encryptedKey });
|
|
979
1027
|
}
|
|
@@ -991,7 +1039,7 @@ class SecureStorage {
|
|
|
991
1039
|
async verifyIntegrity(key) {
|
|
992
1040
|
try {
|
|
993
1041
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
994
|
-
const encryptedValue =
|
|
1042
|
+
const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
995
1043
|
if (!encryptedValue)
|
|
996
1044
|
return false;
|
|
997
1045
|
const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
|
|
@@ -1014,7 +1062,7 @@ class SecureStorage {
|
|
|
1014
1062
|
async getIntegrityInfo(key) {
|
|
1015
1063
|
try {
|
|
1016
1064
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
1017
|
-
const encryptedValue =
|
|
1065
|
+
const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
1018
1066
|
if (!encryptedValue)
|
|
1019
1067
|
return null;
|
|
1020
1068
|
const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
|
|
@@ -1097,7 +1145,7 @@ class SecureStorage {
|
|
|
1097
1145
|
this.logger.info(`Iniciando migración de ${keys.length} claves...`);
|
|
1098
1146
|
for (const key of keys) {
|
|
1099
1147
|
try {
|
|
1100
|
-
const value =
|
|
1148
|
+
const value = this.config.storage.storageEngine.getItem(key);
|
|
1101
1149
|
if (value === null)
|
|
1102
1150
|
continue;
|
|
1103
1151
|
// Try to decrypt to check if already encrypted
|
|
@@ -1110,7 +1158,7 @@ class SecureStorage {
|
|
|
1110
1158
|
// Not encrypted, proceed with migration
|
|
1111
1159
|
}
|
|
1112
1160
|
await this.setItem(key, value);
|
|
1113
|
-
|
|
1161
|
+
this.config.storage.storageEngine.removeItem(key);
|
|
1114
1162
|
this.logger.info(`✓ ${key} migrado exitosamente`);
|
|
1115
1163
|
}
|
|
1116
1164
|
catch (error) {
|
|
@@ -1124,8 +1172,8 @@ class SecureStorage {
|
|
|
1124
1172
|
*/
|
|
1125
1173
|
getDebugInfo() {
|
|
1126
1174
|
const allKeys = [];
|
|
1127
|
-
for (let i = 0; i <
|
|
1128
|
-
const key =
|
|
1175
|
+
for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
|
|
1176
|
+
const key = this.config.storage.storageEngine.key(i);
|
|
1129
1177
|
if (key)
|
|
1130
1178
|
allKeys.push(key);
|
|
1131
1179
|
}
|
|
@@ -1164,6 +1212,194 @@ class SecureStorage {
|
|
|
1164
1212
|
}
|
|
1165
1213
|
SecureStorage.instance = null;
|
|
1166
1214
|
|
|
1215
|
+
/**
|
|
1216
|
+
* SecureCookie - API de alto nivel para almacenamiento en cookies cifradas
|
|
1217
|
+
* Soporta opciones de cookies incluyendo domininios/subdominios y compresión.
|
|
1218
|
+
*/
|
|
1219
|
+
class SecureCookie {
|
|
1220
|
+
constructor(config) {
|
|
1221
|
+
this.config = {
|
|
1222
|
+
encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
|
|
1223
|
+
cookieOptions: { path: '/', ...config?.cookieOptions },
|
|
1224
|
+
compression: config?.compression ?? true,
|
|
1225
|
+
debug: { ...DEFAULT_CONFIG.debug, ...config?.debug }
|
|
1226
|
+
};
|
|
1227
|
+
this.logger = new Logger(this.config.debug);
|
|
1228
|
+
this.encryptionHelper = new EncryptionHelper(this.config.encryption);
|
|
1229
|
+
this.logger.info('SecureCookie initialized', this.config);
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Obtiene la instancia singleton de SecureCookie
|
|
1233
|
+
*/
|
|
1234
|
+
static getInstance(config) {
|
|
1235
|
+
if (!SecureCookie.instance) {
|
|
1236
|
+
SecureCookie.instance = new SecureCookie(config);
|
|
1237
|
+
}
|
|
1238
|
+
return SecureCookie.instance;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Serializa las opciones de cookie en un string
|
|
1242
|
+
*/
|
|
1243
|
+
serializeOptions(options) {
|
|
1244
|
+
let cookieString = '';
|
|
1245
|
+
if (options.expires) {
|
|
1246
|
+
cookieString += `; expires=${options.expires.toUTCString()}`;
|
|
1247
|
+
}
|
|
1248
|
+
if (options.maxAge) {
|
|
1249
|
+
cookieString += `; max-age=${options.maxAge}`;
|
|
1250
|
+
}
|
|
1251
|
+
if (options.domain) {
|
|
1252
|
+
cookieString += `; domain=${options.domain}`;
|
|
1253
|
+
}
|
|
1254
|
+
if (options.path) {
|
|
1255
|
+
cookieString += `; path=${options.path}`;
|
|
1256
|
+
}
|
|
1257
|
+
if (options.secure) {
|
|
1258
|
+
cookieString += `; secure`;
|
|
1259
|
+
}
|
|
1260
|
+
if (options.sameSite) {
|
|
1261
|
+
cookieString += `; samesite=${options.sameSite}`;
|
|
1262
|
+
}
|
|
1263
|
+
return cookieString;
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Guarda un valor encriptado en una cookie
|
|
1267
|
+
*/
|
|
1268
|
+
async set(name, value, options) {
|
|
1269
|
+
this.logger.time(`cookie:set:${name}`);
|
|
1270
|
+
if (!EncryptionHelper.isSupported()) {
|
|
1271
|
+
this.logger.warn('Web Crypto API no soportada, guardando cookie sin cifrar');
|
|
1272
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1273
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1277
|
+
// Check compression (aggressively compress cookies to save space since max is ~4KB)
|
|
1278
|
+
const shouldCompressData = this.config.compression && shouldCompress(value, 200);
|
|
1279
|
+
let processedValue = value;
|
|
1280
|
+
let metadataPrefix = 'P_'; // P_ = Plain
|
|
1281
|
+
if (shouldCompressData) {
|
|
1282
|
+
this.logger.debug(`Compressing cookie value for: ${name}`);
|
|
1283
|
+
const compressedData = await compress(value);
|
|
1284
|
+
processedValue = this.encryptionHelper['arrayBufferToBase64'](compressedData.buffer);
|
|
1285
|
+
metadataPrefix = 'C_'; // C_ = Compressed
|
|
1286
|
+
}
|
|
1287
|
+
// Encrypt key name
|
|
1288
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(name);
|
|
1289
|
+
// Serialize and Encrypt Value
|
|
1290
|
+
// Adding metadata prefix to avoid bloating value with massive StoredValue JSON headers
|
|
1291
|
+
const encryptedData = await this.encryptionHelper.encrypt(processedValue);
|
|
1292
|
+
const finalValue = metadataPrefix + encryptedData;
|
|
1293
|
+
// Prepare Cookie string
|
|
1294
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1295
|
+
const cookieOptionsStr = this.serializeOptions(mergedOptions);
|
|
1296
|
+
const cookieString = `${encodeURIComponent(encryptedKey)}=${encodeURIComponent(finalValue)}${cookieOptionsStr}`;
|
|
1297
|
+
// Verificación de tamaño de cookie
|
|
1298
|
+
if (cookieString.length > 4096) {
|
|
1299
|
+
this.logger.warn(`Cookie '${name}' es muy grande (${cookieString.length} bytes). Puede ser rechazada por el navegador.`);
|
|
1300
|
+
}
|
|
1301
|
+
document.cookie = cookieString;
|
|
1302
|
+
this.logger.verbose(`Stored cookie: ${name}`);
|
|
1303
|
+
this.logger.timeEnd(`cookie:set:${name}`);
|
|
1304
|
+
}
|
|
1305
|
+
catch (error) {
|
|
1306
|
+
this.logger.error(`Error al guardar cookie encriptada para ${name}:`, error);
|
|
1307
|
+
// Fallback sin cifrar como último recurso
|
|
1308
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1309
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Pide una cookie por nombre
|
|
1314
|
+
* Retorna el string o null si no existe
|
|
1315
|
+
*/
|
|
1316
|
+
getRawCookie(name) {
|
|
1317
|
+
const nameEQ = encodeURIComponent(name) + '=';
|
|
1318
|
+
const ca = document.cookie.split(';');
|
|
1319
|
+
for (let i = 0; i < ca.length; i++) {
|
|
1320
|
+
let c = ca[i];
|
|
1321
|
+
while (c.charAt(0) === ' ')
|
|
1322
|
+
c = c.substring(1, c.length);
|
|
1323
|
+
if (c.indexOf(nameEQ) === 0)
|
|
1324
|
+
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
1325
|
+
}
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Recupera y desencripta un valor de cookie
|
|
1330
|
+
*/
|
|
1331
|
+
async get(name) {
|
|
1332
|
+
this.logger.time(`cookie:get:${name}`);
|
|
1333
|
+
if (!EncryptionHelper.isSupported()) {
|
|
1334
|
+
return this.getRawCookie(name);
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
// Find encrypted key
|
|
1338
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(name);
|
|
1339
|
+
let rawValue = this.getRawCookie(encryptedKey);
|
|
1340
|
+
if (!rawValue) {
|
|
1341
|
+
// Backward compatibility just in case fallback was used
|
|
1342
|
+
rawValue = this.getRawCookie(name);
|
|
1343
|
+
if (!rawValue) {
|
|
1344
|
+
this.logger.timeEnd(`cookie:get:${name}`);
|
|
1345
|
+
return null;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
// Check if it has our metadata prefixes
|
|
1349
|
+
if (!rawValue.startsWith('P_') && !rawValue.startsWith('C_')) {
|
|
1350
|
+
// Podría ser un fallback plano
|
|
1351
|
+
this.logger.timeEnd(`cookie:get:${name}`);
|
|
1352
|
+
return rawValue; // Asumimos que es plano
|
|
1353
|
+
}
|
|
1354
|
+
const isCompressed = rawValue.startsWith('C_');
|
|
1355
|
+
const encryptedData = rawValue.substring(2);
|
|
1356
|
+
// Decrypt
|
|
1357
|
+
const decryptedString = await this.encryptionHelper.decrypt(encryptedData);
|
|
1358
|
+
// Decompress if needed
|
|
1359
|
+
let finalValue = decryptedString;
|
|
1360
|
+
if (isCompressed) {
|
|
1361
|
+
const compressedBuffer = this.encryptionHelper['base64ToArrayBuffer'](decryptedString);
|
|
1362
|
+
finalValue = await decompress(new Uint8Array(compressedBuffer));
|
|
1363
|
+
}
|
|
1364
|
+
this.logger.timeEnd(`cookie:get:${name}`);
|
|
1365
|
+
return finalValue;
|
|
1366
|
+
}
|
|
1367
|
+
catch (error) {
|
|
1368
|
+
this.logger.error(`Error al recuperar cookie encriptada para ${name}:`, error);
|
|
1369
|
+
// Fallback
|
|
1370
|
+
return this.getRawCookie(name);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Elimina una cookie
|
|
1375
|
+
*/
|
|
1376
|
+
async remove(name, options) {
|
|
1377
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1378
|
+
// Expirar la cookie configurándole una fecha del pasado
|
|
1379
|
+
const expireOptions = {
|
|
1380
|
+
...mergedOptions,
|
|
1381
|
+
expires: new Date(0),
|
|
1382
|
+
maxAge: 0
|
|
1383
|
+
};
|
|
1384
|
+
if (!EncryptionHelper.isSupported()) {
|
|
1385
|
+
document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(name);
|
|
1389
|
+
// Remove encrypted cookie
|
|
1390
|
+
document.cookie = `${encodeURIComponent(encryptedKey)}=${this.serializeOptions(expireOptions)}`;
|
|
1391
|
+
// Also remove plain version just in case
|
|
1392
|
+
document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Limpia la instancia actual (útil para testing o refresco)
|
|
1396
|
+
*/
|
|
1397
|
+
destroy() {
|
|
1398
|
+
SecureCookie.instance = null;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
SecureCookie.instance = null;
|
|
1402
|
+
|
|
1167
1403
|
const secureStorage$2 = SecureStorage.getInstance();
|
|
1168
1404
|
/**
|
|
1169
1405
|
* Función de debug para verificar el estado del sistema de encriptación
|
|
@@ -1216,6 +1452,202 @@ async function forceMigration(customKeys) {
|
|
|
1216
1452
|
await debugEncryptionState();
|
|
1217
1453
|
}
|
|
1218
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* PlainStorage - Una interfaz consistente con SecureStorage pero sin cifrado.
|
|
1457
|
+
* Envuelve localStorage o sessionStorage con serialización JSON.
|
|
1458
|
+
*/
|
|
1459
|
+
class PlainStorage {
|
|
1460
|
+
constructor(storageEngine = typeof window !== 'undefined' ? window.localStorage : {}) {
|
|
1461
|
+
this.storage = storageEngine;
|
|
1462
|
+
this.logger = new Logger({ prefix: 'PlainStorage' });
|
|
1463
|
+
}
|
|
1464
|
+
async set(key, value) {
|
|
1465
|
+
try {
|
|
1466
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
1467
|
+
this.storage.setItem(key, serialized);
|
|
1468
|
+
}
|
|
1469
|
+
catch (error) {
|
|
1470
|
+
this.logger.error(`Error saving unencrypted item ${key}:`, error);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
async get(key) {
|
|
1474
|
+
try {
|
|
1475
|
+
const value = this.storage.getItem(key);
|
|
1476
|
+
if (value === null)
|
|
1477
|
+
return null;
|
|
1478
|
+
try {
|
|
1479
|
+
return JSON.parse(value);
|
|
1480
|
+
}
|
|
1481
|
+
catch {
|
|
1482
|
+
return value; // Fallback for plain strings
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
catch (error) {
|
|
1486
|
+
this.logger.error(`Error reading unencrypted item ${key}:`, error);
|
|
1487
|
+
return null;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
async remove(key) {
|
|
1491
|
+
this.storage.removeItem(key);
|
|
1492
|
+
}
|
|
1493
|
+
clear() {
|
|
1494
|
+
this.storage.clear();
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Crea una instancia de almacenamiento encriptado.
|
|
1500
|
+
* @param engine 'localStorage' o 'sessionStorage'
|
|
1501
|
+
* @param secretKey Llave secreta opcional para derivación de claves
|
|
1502
|
+
* @param config Configuración adicional opcional
|
|
1503
|
+
* @returns Instancia de SecureStorage
|
|
1504
|
+
*/
|
|
1505
|
+
function createEncryptedStorage(engine = 'localStorage', secretKey, config) {
|
|
1506
|
+
const defaultEngine = typeof window !== 'undefined'
|
|
1507
|
+
? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
|
|
1508
|
+
: {};
|
|
1509
|
+
const mergedConfig = {
|
|
1510
|
+
...config,
|
|
1511
|
+
encryption: {
|
|
1512
|
+
...config?.encryption,
|
|
1513
|
+
...(secretKey ? { appIdentifier: secretKey } : {})
|
|
1514
|
+
},
|
|
1515
|
+
storage: {
|
|
1516
|
+
...config?.storage,
|
|
1517
|
+
storageEngine: defaultEngine
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
return SecureStorage.getInstance(mergedConfig);
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Crea una instancia de almacenamiento plano (sin cifrar).
|
|
1524
|
+
* @param engine 'localStorage' o 'sessionStorage'
|
|
1525
|
+
* @returns Instancia de PlainStorage
|
|
1526
|
+
*/
|
|
1527
|
+
function createPlainStorage(engine = 'localStorage') {
|
|
1528
|
+
const defaultEngine = typeof window !== 'undefined'
|
|
1529
|
+
? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
|
|
1530
|
+
: {};
|
|
1531
|
+
return new PlainStorage(defaultEngine);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* PlainCookie - Una interfaz consistente con SecureCookie pero sin cifrado.
|
|
1536
|
+
*/
|
|
1537
|
+
class PlainCookie {
|
|
1538
|
+
constructor(defaultOptions) {
|
|
1539
|
+
this.logger = new Logger({ prefix: 'PlainCookie' });
|
|
1540
|
+
this.defaultOptions = { path: '/', ...defaultOptions };
|
|
1541
|
+
}
|
|
1542
|
+
serializeOptions(options) {
|
|
1543
|
+
let cookieString = '';
|
|
1544
|
+
if (options.expires)
|
|
1545
|
+
cookieString += `; expires=${options.expires.toUTCString()}`;
|
|
1546
|
+
if (options.maxAge)
|
|
1547
|
+
cookieString += `; max-age=${options.maxAge}`;
|
|
1548
|
+
if (options.domain)
|
|
1549
|
+
cookieString += `; domain=${options.domain}`;
|
|
1550
|
+
if (options.path)
|
|
1551
|
+
cookieString += `; path=${options.path}`;
|
|
1552
|
+
if (options.secure)
|
|
1553
|
+
cookieString += `; secure`;
|
|
1554
|
+
if (options.sameSite)
|
|
1555
|
+
cookieString += `; samesite=${options.sameSite}`;
|
|
1556
|
+
return cookieString;
|
|
1557
|
+
}
|
|
1558
|
+
async set(name, value, options) {
|
|
1559
|
+
try {
|
|
1560
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
1561
|
+
const mergedOptions = { ...this.defaultOptions, ...options };
|
|
1562
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(serialized)}${this.serializeOptions(mergedOptions)}`;
|
|
1563
|
+
}
|
|
1564
|
+
catch (error) {
|
|
1565
|
+
this.logger.error(`Error saving unencrypted cookie ${name}:`, error);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
async get(name) {
|
|
1569
|
+
try {
|
|
1570
|
+
const nameEQ = encodeURIComponent(name) + '=';
|
|
1571
|
+
const ca = document.cookie.split(';');
|
|
1572
|
+
for (let i = 0; i < ca.length; i++) {
|
|
1573
|
+
let c = ca[i];
|
|
1574
|
+
while (c.charAt(0) === ' ')
|
|
1575
|
+
c = c.substring(1, c.length);
|
|
1576
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
1577
|
+
const decoded = decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
1578
|
+
try {
|
|
1579
|
+
return JSON.parse(decoded);
|
|
1580
|
+
}
|
|
1581
|
+
catch {
|
|
1582
|
+
return decoded;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
catch (error) {
|
|
1589
|
+
this.logger.error(`Error reading unencrypted cookie ${name}:`, error);
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
async remove(name, options) {
|
|
1594
|
+
const mergedOptions = { ...this.defaultOptions, ...options };
|
|
1595
|
+
document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions({ ...mergedOptions, expires: new Date(0), maxAge: 0 })}`;
|
|
1596
|
+
}
|
|
1597
|
+
clearAll() {
|
|
1598
|
+
const cookies = document.cookie.split(';');
|
|
1599
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
1600
|
+
const cookie = cookies[i];
|
|
1601
|
+
const eqPos = cookie.indexOf('=');
|
|
1602
|
+
const name = eqPos > -1 ? cookie.substring(0, eqPos).trim() : cookie.trim();
|
|
1603
|
+
this.remove(decodeURIComponent(name));
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Crea una instancia de manejador de cookies encriptadas.
|
|
1610
|
+
* @param secretKey Llave secreta opcional para derivación de claves
|
|
1611
|
+
* @param domain Dominio opcional (incluir punto inicial para subdominios ej. '.dominio.com')
|
|
1612
|
+
* @param config Configuración adicional opcional
|
|
1613
|
+
* @returns Instancia de SecureCookie
|
|
1614
|
+
*/
|
|
1615
|
+
function createEncryptedCookieManager(secretKey, domain, config) {
|
|
1616
|
+
const mergedConfig = {
|
|
1617
|
+
...config,
|
|
1618
|
+
encryption: {
|
|
1619
|
+
...config?.encryption,
|
|
1620
|
+
...(secretKey ? { appIdentifier: secretKey } : {})
|
|
1621
|
+
},
|
|
1622
|
+
cookieOptions: {
|
|
1623
|
+
...config?.cookieOptions,
|
|
1624
|
+
...(domain ? { domain } : {})
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
return SecureCookie.getInstance(mergedConfig);
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Crea una instancia de manejador de cookies plano (sin cifrar).
|
|
1631
|
+
* @param defaultOptions Opciones por defecto para las cookies (ej. domain)
|
|
1632
|
+
* @returns Instancia de PlainCookie
|
|
1633
|
+
*/
|
|
1634
|
+
function createPlainCookieManager(defaultOptions) {
|
|
1635
|
+
return new PlainCookie(defaultOptions);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/**
|
|
1639
|
+
* Instancia predeterminada de almacenamiento local (sin cifrar).
|
|
1640
|
+
*/
|
|
1641
|
+
const localStore = createPlainStorage('localStorage');
|
|
1642
|
+
/**
|
|
1643
|
+
* Instancia predeterminada de almacenamiento de sesión (sin cifrar).
|
|
1644
|
+
*/
|
|
1645
|
+
const sessionStore = createPlainStorage('sessionStorage');
|
|
1646
|
+
/**
|
|
1647
|
+
* Instancia predeterminada de gestor de cookies (sin cifrar).
|
|
1648
|
+
*/
|
|
1649
|
+
const cookies = createPlainCookieManager();
|
|
1650
|
+
|
|
1219
1651
|
/**
|
|
1220
1652
|
* @bantis/local-cipher - Core Module
|
|
1221
1653
|
* Framework-agnostic client-side encryption for browser storage
|
|
@@ -1225,8 +1657,9 @@ async function forceMigration(customKeys) {
|
|
|
1225
1657
|
*/
|
|
1226
1658
|
// Core classes
|
|
1227
1659
|
const secureStorage$1 = SecureStorage.getInstance();
|
|
1660
|
+
const secureCookie = SecureCookie.getInstance();
|
|
1228
1661
|
// Version
|
|
1229
|
-
const VERSION = '2.
|
|
1662
|
+
const VERSION = '2.2.0';
|
|
1230
1663
|
|
|
1231
1664
|
/**
|
|
1232
1665
|
* React Hooks for @bantis/local-cipher
|
|
@@ -1406,18 +1839,35 @@ function useSecureStorageDebug(storage) {
|
|
|
1406
1839
|
}, [secureStorage]);
|
|
1407
1840
|
return debugInfo;
|
|
1408
1841
|
}
|
|
1409
|
-
/**
|
|
1410
|
-
* Hook para usar un namespace de SecureStorage
|
|
1411
|
-
*/
|
|
1412
|
-
function useNamespace(namespace, storage) {
|
|
1413
|
-
const secureStorage = storage || getDefaultStorage();
|
|
1414
|
-
const [namespacedStorage] = useState(() => secureStorage.namespace(namespace));
|
|
1415
|
-
return namespacedStorage;
|
|
1416
|
-
}
|
|
1417
1842
|
/**
|
|
1418
1843
|
* Exportar la instancia de SecureStorage para uso directo
|
|
1419
1844
|
*/
|
|
1420
1845
|
const secureStorage = getDefaultStorage();
|
|
1846
|
+
/**
|
|
1847
|
+
* Hook para sincronizar el estado de React con el Storage Manager (por defecto localStore en texto plano)
|
|
1848
|
+
*/
|
|
1849
|
+
function useLocalStore(key, initialValue, storageManager = localStore) {
|
|
1850
|
+
const [value, setValue] = useState(initialValue);
|
|
1851
|
+
useEffect(() => {
|
|
1852
|
+
let mounted = true;
|
|
1853
|
+
storageManager.get(key).then((saved) => {
|
|
1854
|
+
if (!mounted)
|
|
1855
|
+
return;
|
|
1856
|
+
if (saved !== null) {
|
|
1857
|
+
setValue(saved);
|
|
1858
|
+
}
|
|
1859
|
+
else {
|
|
1860
|
+
storageManager.set(key, initialValue);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
return () => { mounted = false; };
|
|
1864
|
+
}, [key, storageManager]);
|
|
1865
|
+
const setNewValue = useCallback(async (newValue) => {
|
|
1866
|
+
setValue(newValue);
|
|
1867
|
+
await storageManager.set(key, newValue);
|
|
1868
|
+
}, [key, storageManager]);
|
|
1869
|
+
return [value, setNewValue];
|
|
1870
|
+
}
|
|
1421
1871
|
|
|
1422
|
-
export { DEFAULT_CONFIG, EncryptionHelper, EventEmitter, KeyRotation, LIBRARY_VERSION, Logger, NamespacedStorage, STORAGE_VERSION, SecureStorage, VERSION, compress, debugEncryptionState, decompress, forceMigration, initializeSecureStorage, isCompressionSupported, secureStorage as reactSecureStorage, secureStorage$1 as secureStorage, shouldCompress,
|
|
1872
|
+
export { DEFAULT_CONFIG, EncryptionHelper, EventEmitter, KeyRotation, LIBRARY_VERSION, Logger, NamespacedStorage, PlainCookie, PlainStorage, STORAGE_VERSION, SecureCookie, SecureStorage, VERSION, compress, cookies, createEncryptedCookieManager, createEncryptedStorage, createPlainCookieManager, createPlainStorage, debugEncryptionState, decompress, forceMigration, initializeSecureStorage, isCompressionSupported, localStore, secureStorage as reactSecureStorage, secureCookie, secureStorage$1 as secureStorage, sessionStore, shouldCompress, useLocalStore, useSecureStorage, useSecureStorageDebug, useSecureStorageEvents, useSecureStorageItem, useSecureStorageWithExpiry };
|
|
1423
1873
|
//# sourceMappingURL=react.esm.js.map
|