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