@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/angular.js
CHANGED
|
@@ -23,6 +23,9 @@ const DEFAULT_CONFIG = {
|
|
|
23
23
|
compressionThreshold: 1024,
|
|
24
24
|
autoCleanup: true,
|
|
25
25
|
cleanupInterval: 60000,
|
|
26
|
+
enableCache: true,
|
|
27
|
+
verifyIntegrityOnRead: false,
|
|
28
|
+
storageEngine: typeof window !== 'undefined' ? window.localStorage : {},
|
|
26
29
|
},
|
|
27
30
|
debug: {
|
|
28
31
|
enabled: false,
|
|
@@ -50,6 +53,8 @@ class EncryptionHelper {
|
|
|
50
53
|
this.baseKey = '';
|
|
51
54
|
this.baseKeyPromise = null;
|
|
52
55
|
this.keyVersion = 1;
|
|
56
|
+
// Cache para optimización de rendimiento
|
|
57
|
+
this.keyNameCache = new Map();
|
|
53
58
|
this.config = { ...DEFAULT_CONFIG.encryption, ...config };
|
|
54
59
|
// Load key version from storage
|
|
55
60
|
const storedVersion = localStorage.getItem(EncryptionHelper.KEY_VERSION_KEY);
|
|
@@ -219,10 +224,16 @@ class EncryptionHelper {
|
|
|
219
224
|
* @returns Nombre encriptado con prefijo __enc_
|
|
220
225
|
*/
|
|
221
226
|
async encryptKey(keyName) {
|
|
227
|
+
// Optimización: devolver desde cache si existe
|
|
228
|
+
if (this.keyNameCache.has(keyName)) {
|
|
229
|
+
return this.keyNameCache.get(keyName);
|
|
230
|
+
}
|
|
222
231
|
const baseKey = await this.generateBaseKey();
|
|
223
232
|
const combined = keyName + baseKey;
|
|
224
233
|
const hash = await this.hashString(combined);
|
|
225
|
-
|
|
234
|
+
const encryptedKey = `__enc_${hash.substring(0, 16)}`;
|
|
235
|
+
this.keyNameCache.set(keyName, encryptedKey);
|
|
236
|
+
return encryptedKey;
|
|
226
237
|
}
|
|
227
238
|
/**
|
|
228
239
|
* Limpia todos los datos encriptados del localStorage
|
|
@@ -240,10 +251,11 @@ class EncryptionHelper {
|
|
|
240
251
|
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
241
252
|
// Eliminar salt
|
|
242
253
|
localStorage.removeItem(EncryptionHelper.SALT_STORAGE_KEY);
|
|
243
|
-
// Resetear clave en memoria
|
|
254
|
+
// Resetear clave en memoria y cache
|
|
244
255
|
this.key = null;
|
|
245
256
|
this.baseKey = '';
|
|
246
257
|
this.baseKeyPromise = null;
|
|
258
|
+
this.keyNameCache.clear();
|
|
247
259
|
}
|
|
248
260
|
/**
|
|
249
261
|
* Verifica si el navegador soporta Web Crypto API
|
|
@@ -683,6 +695,7 @@ function shouldCompress(data, threshold = 1024) {
|
|
|
683
695
|
class SecureStorage {
|
|
684
696
|
constructor(config) {
|
|
685
697
|
this.cleanupInterval = null;
|
|
698
|
+
this.memoryCache = new Map();
|
|
686
699
|
// Merge config with defaults
|
|
687
700
|
this.config = {
|
|
688
701
|
encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
|
|
@@ -764,8 +777,12 @@ class SecureStorage {
|
|
|
764
777
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
765
778
|
// Encrypt the value
|
|
766
779
|
const encryptedValue = await this.encryptionHelper.encrypt(serialized);
|
|
767
|
-
// Store in
|
|
768
|
-
|
|
780
|
+
// Store in storage
|
|
781
|
+
this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
|
|
782
|
+
// Update cache
|
|
783
|
+
if (this.config.storage.enableCache) {
|
|
784
|
+
this.memoryCache.set(key, { value });
|
|
785
|
+
}
|
|
769
786
|
this.logger.verbose(`Stored key: ${key}, compressed: ${compressed}, size: ${encryptedValue.length}`);
|
|
770
787
|
this.eventEmitter.emit('encrypted', { key, metadata: { compressed, size: encryptedValue.length } });
|
|
771
788
|
this.logger.timeEnd(`setItem:${key}`);
|
|
@@ -773,7 +790,7 @@ class SecureStorage {
|
|
|
773
790
|
catch (error) {
|
|
774
791
|
this.logger.error(`Error al guardar dato encriptado para ${key}:`, error);
|
|
775
792
|
this.eventEmitter.emit('error', { key, error: error });
|
|
776
|
-
|
|
793
|
+
this.config.storage.storageEngine.setItem(key, value);
|
|
777
794
|
}
|
|
778
795
|
}
|
|
779
796
|
/**
|
|
@@ -826,8 +843,12 @@ class SecureStorage {
|
|
|
826
843
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
827
844
|
// Encrypt the value
|
|
828
845
|
const encryptedValue = await this.encryptionHelper.encrypt(serialized);
|
|
829
|
-
// Store in
|
|
830
|
-
|
|
846
|
+
// Store in storage
|
|
847
|
+
this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
|
|
848
|
+
// Update cache
|
|
849
|
+
if (this.config.storage.enableCache) {
|
|
850
|
+
this.memoryCache.set(key, { value, expiresAt });
|
|
851
|
+
}
|
|
831
852
|
this.logger.verbose(`Stored key with expiry: ${key}, expiresAt: ${expiresAt}`);
|
|
832
853
|
this.eventEmitter.emit('encrypted', { key, metadata: { compressed, expiresAt } });
|
|
833
854
|
this.logger.timeEnd(`setItemWithExpiry:${key}`);
|
|
@@ -844,16 +865,31 @@ class SecureStorage {
|
|
|
844
865
|
async getItem(key) {
|
|
845
866
|
this.logger.time(`getItem:${key}`);
|
|
846
867
|
if (!EncryptionHelper.isSupported()) {
|
|
847
|
-
return
|
|
868
|
+
return this.config.storage.storageEngine.getItem(key);
|
|
848
869
|
}
|
|
849
870
|
try {
|
|
871
|
+
// Check memory cache first
|
|
872
|
+
if (this.config.storage.enableCache && this.memoryCache.has(key)) {
|
|
873
|
+
const cached = this.memoryCache.get(key);
|
|
874
|
+
if (cached.expiresAt && cached.expiresAt < Date.now()) {
|
|
875
|
+
this.logger.info(`Key expired in cache: ${key}`);
|
|
876
|
+
await this.removeItem(key);
|
|
877
|
+
this.eventEmitter.emit('expired', { key });
|
|
878
|
+
this.logger.timeEnd(`getItem:${key}`);
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
this.logger.debug(`Retrieved from cache: ${key}`);
|
|
882
|
+
this.eventEmitter.emit('decrypted', { key });
|
|
883
|
+
this.logger.timeEnd(`getItem:${key}`);
|
|
884
|
+
return cached.value;
|
|
885
|
+
}
|
|
850
886
|
// Encrypt the key
|
|
851
887
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
852
888
|
// Get encrypted value
|
|
853
|
-
let encryptedValue =
|
|
889
|
+
let encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
854
890
|
// Backward compatibility: try with plain key
|
|
855
891
|
if (!encryptedValue) {
|
|
856
|
-
encryptedValue =
|
|
892
|
+
encryptedValue = this.config.storage.storageEngine.getItem(key);
|
|
857
893
|
if (!encryptedValue) {
|
|
858
894
|
this.logger.timeEnd(`getItem:${key}`);
|
|
859
895
|
return null;
|
|
@@ -889,8 +925,8 @@ class SecureStorage {
|
|
|
889
925
|
this.logger.timeEnd(`getItem:${key}`);
|
|
890
926
|
return null;
|
|
891
927
|
}
|
|
892
|
-
// Verify integrity if checksum exists
|
|
893
|
-
if (storedValue.checksum) {
|
|
928
|
+
// Verify integrity if checksum exists and configured
|
|
929
|
+
if (storedValue.checksum && this.config.storage.verifyIntegrityOnRead) {
|
|
894
930
|
const calculatedChecksum = await this.calculateChecksum(storedValue.value);
|
|
895
931
|
if (calculatedChecksum !== storedValue.checksum) {
|
|
896
932
|
this.logger.warn(`Integrity check failed for key: ${key}`);
|
|
@@ -908,6 +944,9 @@ class SecureStorage {
|
|
|
908
944
|
finalValue = await decompress(new Uint8Array(compressedData));
|
|
909
945
|
this.eventEmitter.emit('decompressed', { key });
|
|
910
946
|
}
|
|
947
|
+
if (this.config.storage.enableCache) {
|
|
948
|
+
this.memoryCache.set(key, { value: finalValue, expiresAt: storedValue.expiresAt });
|
|
949
|
+
}
|
|
911
950
|
this.eventEmitter.emit('decrypted', { key });
|
|
912
951
|
this.logger.timeEnd(`getItem:${key}`);
|
|
913
952
|
return finalValue;
|
|
@@ -916,7 +955,7 @@ class SecureStorage {
|
|
|
916
955
|
this.logger.error(`Error al recuperar dato encriptado para ${key}:`, error);
|
|
917
956
|
this.eventEmitter.emit('error', { key, error: error });
|
|
918
957
|
// Fallback: try plain key
|
|
919
|
-
const fallback =
|
|
958
|
+
const fallback = this.config.storage.storageEngine.getItem(key);
|
|
920
959
|
this.logger.timeEnd(`getItem:${key}`);
|
|
921
960
|
return fallback;
|
|
922
961
|
}
|
|
@@ -926,19 +965,20 @@ class SecureStorage {
|
|
|
926
965
|
*/
|
|
927
966
|
async removeItem(key) {
|
|
928
967
|
if (!EncryptionHelper.isSupported()) {
|
|
929
|
-
|
|
968
|
+
this.config.storage.storageEngine.removeItem(key);
|
|
930
969
|
return;
|
|
931
970
|
}
|
|
932
971
|
try {
|
|
933
972
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
934
|
-
|
|
935
|
-
|
|
973
|
+
this.config.storage.storageEngine.removeItem(encryptedKey);
|
|
974
|
+
this.config.storage.storageEngine.removeItem(key); // Remove both versions
|
|
975
|
+
this.memoryCache.delete(key);
|
|
936
976
|
this.eventEmitter.emit('deleted', { key });
|
|
937
977
|
this.logger.info(`Removed key: ${key}`);
|
|
938
978
|
}
|
|
939
979
|
catch (error) {
|
|
940
980
|
this.logger.error(`Error al eliminar dato para ${key}:`, error);
|
|
941
|
-
|
|
981
|
+
this.config.storage.storageEngine.removeItem(key);
|
|
942
982
|
}
|
|
943
983
|
}
|
|
944
984
|
/**
|
|
@@ -953,6 +993,7 @@ class SecureStorage {
|
|
|
953
993
|
*/
|
|
954
994
|
clear() {
|
|
955
995
|
this.encryptionHelper.clearEncryptedData();
|
|
996
|
+
this.memoryCache.clear();
|
|
956
997
|
this.eventEmitter.emit('cleared', {});
|
|
957
998
|
this.logger.info('All encrypted data cleared');
|
|
958
999
|
}
|
|
@@ -963,21 +1004,28 @@ class SecureStorage {
|
|
|
963
1004
|
this.logger.info('Starting cleanup of expired items...');
|
|
964
1005
|
let cleanedCount = 0;
|
|
965
1006
|
const keysToCheck = [];
|
|
966
|
-
for (let i = 0; i <
|
|
967
|
-
const key =
|
|
1007
|
+
for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
|
|
1008
|
+
const key = this.config.storage.storageEngine.key(i);
|
|
968
1009
|
if (key && key.startsWith('__enc_')) {
|
|
969
1010
|
keysToCheck.push(key);
|
|
970
1011
|
}
|
|
971
1012
|
}
|
|
972
1013
|
for (const encryptedKey of keysToCheck) {
|
|
973
1014
|
try {
|
|
974
|
-
const encryptedValue =
|
|
1015
|
+
const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
975
1016
|
if (!encryptedValue)
|
|
976
1017
|
continue;
|
|
977
1018
|
const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
|
|
978
1019
|
const storedValue = JSON.parse(decrypted);
|
|
979
1020
|
if (storedValue.expiresAt && storedValue.expiresAt < Date.now()) {
|
|
980
|
-
|
|
1021
|
+
this.config.storage.storageEngine.removeItem(encryptedKey);
|
|
1022
|
+
// Encontrar la clave original en el cache y eliminarla
|
|
1023
|
+
for (const [cacheKey] of Array.from(this.memoryCache.entries())) {
|
|
1024
|
+
this.encryptionHelper.encryptKey(cacheKey).then(enc => {
|
|
1025
|
+
if (enc === encryptedKey)
|
|
1026
|
+
this.memoryCache.delete(cacheKey);
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
981
1029
|
cleanedCount++;
|
|
982
1030
|
this.eventEmitter.emit('expired', { key: encryptedKey });
|
|
983
1031
|
}
|
|
@@ -995,7 +1043,7 @@ class SecureStorage {
|
|
|
995
1043
|
async verifyIntegrity(key) {
|
|
996
1044
|
try {
|
|
997
1045
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
998
|
-
const encryptedValue =
|
|
1046
|
+
const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
999
1047
|
if (!encryptedValue)
|
|
1000
1048
|
return false;
|
|
1001
1049
|
const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
|
|
@@ -1018,7 +1066,7 @@ class SecureStorage {
|
|
|
1018
1066
|
async getIntegrityInfo(key) {
|
|
1019
1067
|
try {
|
|
1020
1068
|
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
1021
|
-
const encryptedValue =
|
|
1069
|
+
const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
|
|
1022
1070
|
if (!encryptedValue)
|
|
1023
1071
|
return null;
|
|
1024
1072
|
const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
|
|
@@ -1101,7 +1149,7 @@ class SecureStorage {
|
|
|
1101
1149
|
this.logger.info(`Iniciando migración de ${keys.length} claves...`);
|
|
1102
1150
|
for (const key of keys) {
|
|
1103
1151
|
try {
|
|
1104
|
-
const value =
|
|
1152
|
+
const value = this.config.storage.storageEngine.getItem(key);
|
|
1105
1153
|
if (value === null)
|
|
1106
1154
|
continue;
|
|
1107
1155
|
// Try to decrypt to check if already encrypted
|
|
@@ -1114,7 +1162,7 @@ class SecureStorage {
|
|
|
1114
1162
|
// Not encrypted, proceed with migration
|
|
1115
1163
|
}
|
|
1116
1164
|
await this.setItem(key, value);
|
|
1117
|
-
|
|
1165
|
+
this.config.storage.storageEngine.removeItem(key);
|
|
1118
1166
|
this.logger.info(`✓ ${key} migrado exitosamente`);
|
|
1119
1167
|
}
|
|
1120
1168
|
catch (error) {
|
|
@@ -1128,8 +1176,8 @@ class SecureStorage {
|
|
|
1128
1176
|
*/
|
|
1129
1177
|
getDebugInfo() {
|
|
1130
1178
|
const allKeys = [];
|
|
1131
|
-
for (let i = 0; i <
|
|
1132
|
-
const key =
|
|
1179
|
+
for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
|
|
1180
|
+
const key = this.config.storage.storageEngine.key(i);
|
|
1133
1181
|
if (key)
|
|
1134
1182
|
allKeys.push(key);
|
|
1135
1183
|
}
|
|
@@ -1168,6 +1216,194 @@ class SecureStorage {
|
|
|
1168
1216
|
}
|
|
1169
1217
|
SecureStorage.instance = null;
|
|
1170
1218
|
|
|
1219
|
+
/**
|
|
1220
|
+
* SecureCookie - API de alto nivel para almacenamiento en cookies cifradas
|
|
1221
|
+
* Soporta opciones de cookies incluyendo domininios/subdominios y compresión.
|
|
1222
|
+
*/
|
|
1223
|
+
class SecureCookie {
|
|
1224
|
+
constructor(config) {
|
|
1225
|
+
this.config = {
|
|
1226
|
+
encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
|
|
1227
|
+
cookieOptions: { path: '/', ...config?.cookieOptions },
|
|
1228
|
+
compression: config?.compression ?? true,
|
|
1229
|
+
debug: { ...DEFAULT_CONFIG.debug, ...config?.debug }
|
|
1230
|
+
};
|
|
1231
|
+
this.logger = new Logger(this.config.debug);
|
|
1232
|
+
this.encryptionHelper = new EncryptionHelper(this.config.encryption);
|
|
1233
|
+
this.logger.info('SecureCookie initialized', this.config);
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Obtiene la instancia singleton de SecureCookie
|
|
1237
|
+
*/
|
|
1238
|
+
static getInstance(config) {
|
|
1239
|
+
if (!SecureCookie.instance) {
|
|
1240
|
+
SecureCookie.instance = new SecureCookie(config);
|
|
1241
|
+
}
|
|
1242
|
+
return SecureCookie.instance;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Serializa las opciones de cookie en un string
|
|
1246
|
+
*/
|
|
1247
|
+
serializeOptions(options) {
|
|
1248
|
+
let cookieString = '';
|
|
1249
|
+
if (options.expires) {
|
|
1250
|
+
cookieString += `; expires=${options.expires.toUTCString()}`;
|
|
1251
|
+
}
|
|
1252
|
+
if (options.maxAge) {
|
|
1253
|
+
cookieString += `; max-age=${options.maxAge}`;
|
|
1254
|
+
}
|
|
1255
|
+
if (options.domain) {
|
|
1256
|
+
cookieString += `; domain=${options.domain}`;
|
|
1257
|
+
}
|
|
1258
|
+
if (options.path) {
|
|
1259
|
+
cookieString += `; path=${options.path}`;
|
|
1260
|
+
}
|
|
1261
|
+
if (options.secure) {
|
|
1262
|
+
cookieString += `; secure`;
|
|
1263
|
+
}
|
|
1264
|
+
if (options.sameSite) {
|
|
1265
|
+
cookieString += `; samesite=${options.sameSite}`;
|
|
1266
|
+
}
|
|
1267
|
+
return cookieString;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Guarda un valor encriptado en una cookie
|
|
1271
|
+
*/
|
|
1272
|
+
async set(name, value, options) {
|
|
1273
|
+
this.logger.time(`cookie:set:${name}`);
|
|
1274
|
+
if (!EncryptionHelper.isSupported()) {
|
|
1275
|
+
this.logger.warn('Web Crypto API no soportada, guardando cookie sin cifrar');
|
|
1276
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1277
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
// Check compression (aggressively compress cookies to save space since max is ~4KB)
|
|
1282
|
+
const shouldCompressData = this.config.compression && shouldCompress(value, 200);
|
|
1283
|
+
let processedValue = value;
|
|
1284
|
+
let metadataPrefix = 'P_'; // P_ = Plain
|
|
1285
|
+
if (shouldCompressData) {
|
|
1286
|
+
this.logger.debug(`Compressing cookie value for: ${name}`);
|
|
1287
|
+
const compressedData = await compress(value);
|
|
1288
|
+
processedValue = this.encryptionHelper['arrayBufferToBase64'](compressedData.buffer);
|
|
1289
|
+
metadataPrefix = 'C_'; // C_ = Compressed
|
|
1290
|
+
}
|
|
1291
|
+
// Encrypt key name
|
|
1292
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(name);
|
|
1293
|
+
// Serialize and Encrypt Value
|
|
1294
|
+
// Adding metadata prefix to avoid bloating value with massive StoredValue JSON headers
|
|
1295
|
+
const encryptedData = await this.encryptionHelper.encrypt(processedValue);
|
|
1296
|
+
const finalValue = metadataPrefix + encryptedData;
|
|
1297
|
+
// Prepare Cookie string
|
|
1298
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1299
|
+
const cookieOptionsStr = this.serializeOptions(mergedOptions);
|
|
1300
|
+
const cookieString = `${encodeURIComponent(encryptedKey)}=${encodeURIComponent(finalValue)}${cookieOptionsStr}`;
|
|
1301
|
+
// Verificación de tamaño de cookie
|
|
1302
|
+
if (cookieString.length > 4096) {
|
|
1303
|
+
this.logger.warn(`Cookie '${name}' es muy grande (${cookieString.length} bytes). Puede ser rechazada por el navegador.`);
|
|
1304
|
+
}
|
|
1305
|
+
document.cookie = cookieString;
|
|
1306
|
+
this.logger.verbose(`Stored cookie: ${name}`);
|
|
1307
|
+
this.logger.timeEnd(`cookie:set:${name}`);
|
|
1308
|
+
}
|
|
1309
|
+
catch (error) {
|
|
1310
|
+
this.logger.error(`Error al guardar cookie encriptada para ${name}:`, error);
|
|
1311
|
+
// Fallback sin cifrar como último recurso
|
|
1312
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1313
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Pide una cookie por nombre
|
|
1318
|
+
* Retorna el string o null si no existe
|
|
1319
|
+
*/
|
|
1320
|
+
getRawCookie(name) {
|
|
1321
|
+
const nameEQ = encodeURIComponent(name) + '=';
|
|
1322
|
+
const ca = document.cookie.split(';');
|
|
1323
|
+
for (let i = 0; i < ca.length; i++) {
|
|
1324
|
+
let c = ca[i];
|
|
1325
|
+
while (c.charAt(0) === ' ')
|
|
1326
|
+
c = c.substring(1, c.length);
|
|
1327
|
+
if (c.indexOf(nameEQ) === 0)
|
|
1328
|
+
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
1329
|
+
}
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Recupera y desencripta un valor de cookie
|
|
1334
|
+
*/
|
|
1335
|
+
async get(name) {
|
|
1336
|
+
this.logger.time(`cookie:get:${name}`);
|
|
1337
|
+
if (!EncryptionHelper.isSupported()) {
|
|
1338
|
+
return this.getRawCookie(name);
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
// Find encrypted key
|
|
1342
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(name);
|
|
1343
|
+
let rawValue = this.getRawCookie(encryptedKey);
|
|
1344
|
+
if (!rawValue) {
|
|
1345
|
+
// Backward compatibility just in case fallback was used
|
|
1346
|
+
rawValue = this.getRawCookie(name);
|
|
1347
|
+
if (!rawValue) {
|
|
1348
|
+
this.logger.timeEnd(`cookie:get:${name}`);
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
// Check if it has our metadata prefixes
|
|
1353
|
+
if (!rawValue.startsWith('P_') && !rawValue.startsWith('C_')) {
|
|
1354
|
+
// Podría ser un fallback plano
|
|
1355
|
+
this.logger.timeEnd(`cookie:get:${name}`);
|
|
1356
|
+
return rawValue; // Asumimos que es plano
|
|
1357
|
+
}
|
|
1358
|
+
const isCompressed = rawValue.startsWith('C_');
|
|
1359
|
+
const encryptedData = rawValue.substring(2);
|
|
1360
|
+
// Decrypt
|
|
1361
|
+
const decryptedString = await this.encryptionHelper.decrypt(encryptedData);
|
|
1362
|
+
// Decompress if needed
|
|
1363
|
+
let finalValue = decryptedString;
|
|
1364
|
+
if (isCompressed) {
|
|
1365
|
+
const compressedBuffer = this.encryptionHelper['base64ToArrayBuffer'](decryptedString);
|
|
1366
|
+
finalValue = await decompress(new Uint8Array(compressedBuffer));
|
|
1367
|
+
}
|
|
1368
|
+
this.logger.timeEnd(`cookie:get:${name}`);
|
|
1369
|
+
return finalValue;
|
|
1370
|
+
}
|
|
1371
|
+
catch (error) {
|
|
1372
|
+
this.logger.error(`Error al recuperar cookie encriptada para ${name}:`, error);
|
|
1373
|
+
// Fallback
|
|
1374
|
+
return this.getRawCookie(name);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Elimina una cookie
|
|
1379
|
+
*/
|
|
1380
|
+
async remove(name, options) {
|
|
1381
|
+
const mergedOptions = { ...this.config.cookieOptions, ...options };
|
|
1382
|
+
// Expirar la cookie configurándole una fecha del pasado
|
|
1383
|
+
const expireOptions = {
|
|
1384
|
+
...mergedOptions,
|
|
1385
|
+
expires: new Date(0),
|
|
1386
|
+
maxAge: 0
|
|
1387
|
+
};
|
|
1388
|
+
if (!EncryptionHelper.isSupported()) {
|
|
1389
|
+
document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(name);
|
|
1393
|
+
// Remove encrypted cookie
|
|
1394
|
+
document.cookie = `${encodeURIComponent(encryptedKey)}=${this.serializeOptions(expireOptions)}`;
|
|
1395
|
+
// Also remove plain version just in case
|
|
1396
|
+
document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Limpia la instancia actual (útil para testing o refresco)
|
|
1400
|
+
*/
|
|
1401
|
+
destroy() {
|
|
1402
|
+
SecureCookie.instance = null;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
SecureCookie.instance = null;
|
|
1406
|
+
|
|
1171
1407
|
const secureStorage$1 = SecureStorage.getInstance();
|
|
1172
1408
|
/**
|
|
1173
1409
|
* Función de debug para verificar el estado del sistema de encriptación
|
|
@@ -1220,6 +1456,202 @@ async function forceMigration(customKeys) {
|
|
|
1220
1456
|
await debugEncryptionState();
|
|
1221
1457
|
}
|
|
1222
1458
|
|
|
1459
|
+
/**
|
|
1460
|
+
* PlainStorage - Una interfaz consistente con SecureStorage pero sin cifrado.
|
|
1461
|
+
* Envuelve localStorage o sessionStorage con serialización JSON.
|
|
1462
|
+
*/
|
|
1463
|
+
class PlainStorage {
|
|
1464
|
+
constructor(storageEngine = typeof window !== 'undefined' ? window.localStorage : {}) {
|
|
1465
|
+
this.storage = storageEngine;
|
|
1466
|
+
this.logger = new Logger({ prefix: 'PlainStorage' });
|
|
1467
|
+
}
|
|
1468
|
+
async set(key, value) {
|
|
1469
|
+
try {
|
|
1470
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
1471
|
+
this.storage.setItem(key, serialized);
|
|
1472
|
+
}
|
|
1473
|
+
catch (error) {
|
|
1474
|
+
this.logger.error(`Error saving unencrypted item ${key}:`, error);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async get(key) {
|
|
1478
|
+
try {
|
|
1479
|
+
const value = this.storage.getItem(key);
|
|
1480
|
+
if (value === null)
|
|
1481
|
+
return null;
|
|
1482
|
+
try {
|
|
1483
|
+
return JSON.parse(value);
|
|
1484
|
+
}
|
|
1485
|
+
catch {
|
|
1486
|
+
return value; // Fallback for plain strings
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
catch (error) {
|
|
1490
|
+
this.logger.error(`Error reading unencrypted item ${key}:`, error);
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
async remove(key) {
|
|
1495
|
+
this.storage.removeItem(key);
|
|
1496
|
+
}
|
|
1497
|
+
clear() {
|
|
1498
|
+
this.storage.clear();
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Crea una instancia de almacenamiento encriptado.
|
|
1504
|
+
* @param engine 'localStorage' o 'sessionStorage'
|
|
1505
|
+
* @param secretKey Llave secreta opcional para derivación de claves
|
|
1506
|
+
* @param config Configuración adicional opcional
|
|
1507
|
+
* @returns Instancia de SecureStorage
|
|
1508
|
+
*/
|
|
1509
|
+
function createEncryptedStorage(engine = 'localStorage', secretKey, config) {
|
|
1510
|
+
const defaultEngine = typeof window !== 'undefined'
|
|
1511
|
+
? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
|
|
1512
|
+
: {};
|
|
1513
|
+
const mergedConfig = {
|
|
1514
|
+
...config,
|
|
1515
|
+
encryption: {
|
|
1516
|
+
...config?.encryption,
|
|
1517
|
+
...(secretKey ? { appIdentifier: secretKey } : {})
|
|
1518
|
+
},
|
|
1519
|
+
storage: {
|
|
1520
|
+
...config?.storage,
|
|
1521
|
+
storageEngine: defaultEngine
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
return SecureStorage.getInstance(mergedConfig);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Crea una instancia de almacenamiento plano (sin cifrar).
|
|
1528
|
+
* @param engine 'localStorage' o 'sessionStorage'
|
|
1529
|
+
* @returns Instancia de PlainStorage
|
|
1530
|
+
*/
|
|
1531
|
+
function createPlainStorage(engine = 'localStorage') {
|
|
1532
|
+
const defaultEngine = typeof window !== 'undefined'
|
|
1533
|
+
? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
|
|
1534
|
+
: {};
|
|
1535
|
+
return new PlainStorage(defaultEngine);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* PlainCookie - Una interfaz consistente con SecureCookie pero sin cifrado.
|
|
1540
|
+
*/
|
|
1541
|
+
class PlainCookie {
|
|
1542
|
+
constructor(defaultOptions) {
|
|
1543
|
+
this.logger = new Logger({ prefix: 'PlainCookie' });
|
|
1544
|
+
this.defaultOptions = { path: '/', ...defaultOptions };
|
|
1545
|
+
}
|
|
1546
|
+
serializeOptions(options) {
|
|
1547
|
+
let cookieString = '';
|
|
1548
|
+
if (options.expires)
|
|
1549
|
+
cookieString += `; expires=${options.expires.toUTCString()}`;
|
|
1550
|
+
if (options.maxAge)
|
|
1551
|
+
cookieString += `; max-age=${options.maxAge}`;
|
|
1552
|
+
if (options.domain)
|
|
1553
|
+
cookieString += `; domain=${options.domain}`;
|
|
1554
|
+
if (options.path)
|
|
1555
|
+
cookieString += `; path=${options.path}`;
|
|
1556
|
+
if (options.secure)
|
|
1557
|
+
cookieString += `; secure`;
|
|
1558
|
+
if (options.sameSite)
|
|
1559
|
+
cookieString += `; samesite=${options.sameSite}`;
|
|
1560
|
+
return cookieString;
|
|
1561
|
+
}
|
|
1562
|
+
async set(name, value, options) {
|
|
1563
|
+
try {
|
|
1564
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
1565
|
+
const mergedOptions = { ...this.defaultOptions, ...options };
|
|
1566
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(serialized)}${this.serializeOptions(mergedOptions)}`;
|
|
1567
|
+
}
|
|
1568
|
+
catch (error) {
|
|
1569
|
+
this.logger.error(`Error saving unencrypted cookie ${name}:`, error);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
async get(name) {
|
|
1573
|
+
try {
|
|
1574
|
+
const nameEQ = encodeURIComponent(name) + '=';
|
|
1575
|
+
const ca = document.cookie.split(';');
|
|
1576
|
+
for (let i = 0; i < ca.length; i++) {
|
|
1577
|
+
let c = ca[i];
|
|
1578
|
+
while (c.charAt(0) === ' ')
|
|
1579
|
+
c = c.substring(1, c.length);
|
|
1580
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
1581
|
+
const decoded = decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
1582
|
+
try {
|
|
1583
|
+
return JSON.parse(decoded);
|
|
1584
|
+
}
|
|
1585
|
+
catch {
|
|
1586
|
+
return decoded;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
catch (error) {
|
|
1593
|
+
this.logger.error(`Error reading unencrypted cookie ${name}:`, error);
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
async remove(name, options) {
|
|
1598
|
+
const mergedOptions = { ...this.defaultOptions, ...options };
|
|
1599
|
+
document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions({ ...mergedOptions, expires: new Date(0), maxAge: 0 })}`;
|
|
1600
|
+
}
|
|
1601
|
+
clearAll() {
|
|
1602
|
+
const cookies = document.cookie.split(';');
|
|
1603
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
1604
|
+
const cookie = cookies[i];
|
|
1605
|
+
const eqPos = cookie.indexOf('=');
|
|
1606
|
+
const name = eqPos > -1 ? cookie.substring(0, eqPos).trim() : cookie.trim();
|
|
1607
|
+
this.remove(decodeURIComponent(name));
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Crea una instancia de manejador de cookies encriptadas.
|
|
1614
|
+
* @param secretKey Llave secreta opcional para derivación de claves
|
|
1615
|
+
* @param domain Dominio opcional (incluir punto inicial para subdominios ej. '.dominio.com')
|
|
1616
|
+
* @param config Configuración adicional opcional
|
|
1617
|
+
* @returns Instancia de SecureCookie
|
|
1618
|
+
*/
|
|
1619
|
+
function createEncryptedCookieManager(secretKey, domain, config) {
|
|
1620
|
+
const mergedConfig = {
|
|
1621
|
+
...config,
|
|
1622
|
+
encryption: {
|
|
1623
|
+
...config?.encryption,
|
|
1624
|
+
...(secretKey ? { appIdentifier: secretKey } : {})
|
|
1625
|
+
},
|
|
1626
|
+
cookieOptions: {
|
|
1627
|
+
...config?.cookieOptions,
|
|
1628
|
+
...(domain ? { domain } : {})
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
return SecureCookie.getInstance(mergedConfig);
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Crea una instancia de manejador de cookies plano (sin cifrar).
|
|
1635
|
+
* @param defaultOptions Opciones por defecto para las cookies (ej. domain)
|
|
1636
|
+
* @returns Instancia de PlainCookie
|
|
1637
|
+
*/
|
|
1638
|
+
function createPlainCookieManager(defaultOptions) {
|
|
1639
|
+
return new PlainCookie(defaultOptions);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Instancia predeterminada de almacenamiento local (sin cifrar).
|
|
1644
|
+
*/
|
|
1645
|
+
const localStore = createPlainStorage('localStorage');
|
|
1646
|
+
/**
|
|
1647
|
+
* Instancia predeterminada de almacenamiento de sesión (sin cifrar).
|
|
1648
|
+
*/
|
|
1649
|
+
const sessionStore = createPlainStorage('sessionStorage');
|
|
1650
|
+
/**
|
|
1651
|
+
* Instancia predeterminada de gestor de cookies (sin cifrar).
|
|
1652
|
+
*/
|
|
1653
|
+
const cookies = createPlainCookieManager();
|
|
1654
|
+
|
|
1223
1655
|
/**
|
|
1224
1656
|
* @bantis/local-cipher - Core Module
|
|
1225
1657
|
* Framework-agnostic client-side encryption for browser storage
|
|
@@ -1229,8 +1661,9 @@ async function forceMigration(customKeys) {
|
|
|
1229
1661
|
*/
|
|
1230
1662
|
// Core classes
|
|
1231
1663
|
const secureStorage = SecureStorage.getInstance();
|
|
1664
|
+
const secureCookie = SecureCookie.getInstance();
|
|
1232
1665
|
// Version
|
|
1233
|
-
const VERSION = '2.
|
|
1666
|
+
const VERSION = '2.2.0';
|
|
1234
1667
|
|
|
1235
1668
|
/******************************************************************************
|
|
1236
1669
|
Copyright (c) Microsoft Corporation.
|
|
@@ -1523,6 +1956,50 @@ let SecureStorageService = (() => {
|
|
|
1523
1956
|
return _classThis;
|
|
1524
1957
|
})();
|
|
1525
1958
|
|
|
1959
|
+
let StorageService = (() => {
|
|
1960
|
+
let _classDecorators = [core.Injectable({
|
|
1961
|
+
providedIn: 'root'
|
|
1962
|
+
})];
|
|
1963
|
+
let _classDescriptor;
|
|
1964
|
+
let _classExtraInitializers = [];
|
|
1965
|
+
let _classThis;
|
|
1966
|
+
_classThis = class {
|
|
1967
|
+
constructor() {
|
|
1968
|
+
/** Acceso directo a localStorage sin cifrar */
|
|
1969
|
+
this.local = localStore;
|
|
1970
|
+
/** Acceso directo a sessionStorage sin cifrar */
|
|
1971
|
+
this.session = sessionStore;
|
|
1972
|
+
/** Acceso directo a cookies sin cifrar */
|
|
1973
|
+
this.cookies = cookies;
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Crea una instancia de almacenamiento sincronizado y cifrado
|
|
1977
|
+
* @param engine local o session storage
|
|
1978
|
+
* @param secretKey Llave de cifrado
|
|
1979
|
+
*/
|
|
1980
|
+
createEncryptedStorage(engine, secretKey) {
|
|
1981
|
+
return createEncryptedStorage(engine, secretKey);
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Crea un gestor de cookies cifradas
|
|
1985
|
+
* @param secretKey Llave de cifrado
|
|
1986
|
+
* @param domain Configuración de subdominios
|
|
1987
|
+
*/
|
|
1988
|
+
createEncryptedCookieManager(secretKey, domain) {
|
|
1989
|
+
return createEncryptedCookieManager(secretKey, domain);
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
__setFunctionName(_classThis, "StorageService");
|
|
1993
|
+
(() => {
|
|
1994
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
1995
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
1996
|
+
_classThis = _classDescriptor.value;
|
|
1997
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
1998
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
1999
|
+
})();
|
|
2000
|
+
return _classThis;
|
|
2001
|
+
})();
|
|
2002
|
+
|
|
1526
2003
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
1527
2004
|
exports.EncryptionHelper = EncryptionHelper;
|
|
1528
2005
|
exports.EventEmitter = EventEmitter;
|
|
@@ -1530,15 +2007,27 @@ exports.KeyRotation = KeyRotation;
|
|
|
1530
2007
|
exports.LIBRARY_VERSION = LIBRARY_VERSION;
|
|
1531
2008
|
exports.Logger = Logger;
|
|
1532
2009
|
exports.NamespacedStorage = NamespacedStorage;
|
|
2010
|
+
exports.PlainCookie = PlainCookie;
|
|
2011
|
+
exports.PlainStorage = PlainStorage;
|
|
1533
2012
|
exports.STORAGE_VERSION = STORAGE_VERSION;
|
|
2013
|
+
exports.SecureCookie = SecureCookie;
|
|
1534
2014
|
exports.SecureStorage = SecureStorage;
|
|
1535
2015
|
exports.SecureStorageService = SecureStorageService;
|
|
2016
|
+
exports.StorageService = StorageService;
|
|
1536
2017
|
exports.VERSION = VERSION;
|
|
1537
2018
|
exports.compress = compress;
|
|
2019
|
+
exports.cookies = cookies;
|
|
2020
|
+
exports.createEncryptedCookieManager = createEncryptedCookieManager;
|
|
2021
|
+
exports.createEncryptedStorage = createEncryptedStorage;
|
|
2022
|
+
exports.createPlainCookieManager = createPlainCookieManager;
|
|
2023
|
+
exports.createPlainStorage = createPlainStorage;
|
|
1538
2024
|
exports.debugEncryptionState = debugEncryptionState;
|
|
1539
2025
|
exports.decompress = decompress;
|
|
1540
2026
|
exports.forceMigration = forceMigration;
|
|
1541
2027
|
exports.isCompressionSupported = isCompressionSupported;
|
|
2028
|
+
exports.localStore = localStore;
|
|
2029
|
+
exports.secureCookie = secureCookie;
|
|
1542
2030
|
exports.secureStorage = secureStorage;
|
|
2031
|
+
exports.sessionStore = sessionStore;
|
|
1543
2032
|
exports.shouldCompress = shouldCompress;
|
|
1544
2033
|
//# sourceMappingURL=angular.js.map
|