@bantis/local-cipher 1.0.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/LICENSE +21 -0
- package/README.md +253 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.esm.js +767 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +779 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var core = require('@angular/core');
|
|
5
|
+
var rxjs = require('rxjs');
|
|
6
|
+
var operators = require('rxjs/operators');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* EncryptionHelper - Clase responsable de todas las operaciones criptográficas
|
|
10
|
+
* Implementa AES-256-GCM con derivación de claves PBKDF2 y fingerprinting del navegador
|
|
11
|
+
*/
|
|
12
|
+
class EncryptionHelper {
|
|
13
|
+
constructor() {
|
|
14
|
+
// Propiedades privadas
|
|
15
|
+
this.key = null;
|
|
16
|
+
this.baseKey = '';
|
|
17
|
+
this.baseKeyPromise = null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Genera un fingerprint único del navegador
|
|
21
|
+
* Combina múltiples características del navegador para crear una huella digital
|
|
22
|
+
*/
|
|
23
|
+
async generateBaseKey() {
|
|
24
|
+
if (this.baseKeyPromise) {
|
|
25
|
+
return this.baseKeyPromise;
|
|
26
|
+
}
|
|
27
|
+
this.baseKeyPromise = (async () => {
|
|
28
|
+
const components = [
|
|
29
|
+
navigator.userAgent,
|
|
30
|
+
navigator.language,
|
|
31
|
+
screen.width.toString(),
|
|
32
|
+
screen.height.toString(),
|
|
33
|
+
screen.colorDepth.toString(),
|
|
34
|
+
new Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
35
|
+
EncryptionHelper.APP_IDENTIFIER,
|
|
36
|
+
];
|
|
37
|
+
const fingerprint = components.join('|');
|
|
38
|
+
this.baseKey = await this.hashString(fingerprint);
|
|
39
|
+
return this.baseKey;
|
|
40
|
+
})();
|
|
41
|
+
return this.baseKeyPromise;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Hashea un string usando SHA-256
|
|
45
|
+
* @param str - String a hashear
|
|
46
|
+
* @returns Hash hexadecimal
|
|
47
|
+
*/
|
|
48
|
+
async hashString(str) {
|
|
49
|
+
const encoder = new TextEncoder();
|
|
50
|
+
const data = encoder.encode(str);
|
|
51
|
+
const hashBuffer = await crypto.subtle.digest(EncryptionHelper.HASH_ALGORITHM, data);
|
|
52
|
+
return this.arrayBufferToHex(hashBuffer);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Deriva una clave criptográfica usando PBKDF2
|
|
56
|
+
* @param password - Password base (fingerprint)
|
|
57
|
+
* @param salt - Salt aleatorio
|
|
58
|
+
* @returns CryptoKey para AES-GCM
|
|
59
|
+
*/
|
|
60
|
+
async deriveKey(password, salt) {
|
|
61
|
+
const encoder = new TextEncoder();
|
|
62
|
+
const passwordBuffer = encoder.encode(password);
|
|
63
|
+
// Importar el password como material de clave
|
|
64
|
+
const keyMaterial = await crypto.subtle.importKey('raw', passwordBuffer, 'PBKDF2', false, ['deriveBits', 'deriveKey']);
|
|
65
|
+
// Derivar la clave AES-GCM
|
|
66
|
+
return crypto.subtle.deriveKey({
|
|
67
|
+
name: 'PBKDF2',
|
|
68
|
+
salt,
|
|
69
|
+
iterations: EncryptionHelper.ITERATIONS,
|
|
70
|
+
hash: EncryptionHelper.HASH_ALGORITHM,
|
|
71
|
+
}, keyMaterial, {
|
|
72
|
+
name: EncryptionHelper.ALGORITHM,
|
|
73
|
+
length: EncryptionHelper.KEY_LENGTH,
|
|
74
|
+
}, false, ['encrypt', 'decrypt']);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Inicializa el sistema de encriptación generando un nuevo salt
|
|
78
|
+
*/
|
|
79
|
+
async initialize() {
|
|
80
|
+
// Generar salt aleatorio
|
|
81
|
+
const salt = crypto.getRandomValues(new Uint8Array(EncryptionHelper.SALT_LENGTH));
|
|
82
|
+
// Obtener y hashear el baseKey
|
|
83
|
+
const baseKey = await this.generateBaseKey();
|
|
84
|
+
// Derivar la clave
|
|
85
|
+
this.key = await this.deriveKey(baseKey, salt);
|
|
86
|
+
// Guardar el salt en localStorage
|
|
87
|
+
localStorage.setItem(EncryptionHelper.SALT_STORAGE_KEY, this.arrayBufferToBase64(salt.buffer));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Inicializa desde un salt almacenado previamente
|
|
91
|
+
*/
|
|
92
|
+
async initializeFromStored() {
|
|
93
|
+
const storedSalt = localStorage.getItem(EncryptionHelper.SALT_STORAGE_KEY);
|
|
94
|
+
if (!storedSalt) {
|
|
95
|
+
// Si no hay salt almacenado, inicializar uno nuevo
|
|
96
|
+
await this.initialize();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Recuperar el salt
|
|
100
|
+
const salt = new Uint8Array(this.base64ToArrayBuffer(storedSalt));
|
|
101
|
+
// Obtener el baseKey
|
|
102
|
+
const baseKey = await this.generateBaseKey();
|
|
103
|
+
// Derivar la misma clave
|
|
104
|
+
this.key = await this.deriveKey(baseKey, salt);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Encripta un texto plano usando AES-256-GCM
|
|
108
|
+
* @param plaintext - Texto a encriptar
|
|
109
|
+
* @returns Texto encriptado en Base64 (IV + datos encriptados)
|
|
110
|
+
*/
|
|
111
|
+
async encrypt(plaintext) {
|
|
112
|
+
if (!this.key) {
|
|
113
|
+
await this.initializeFromStored();
|
|
114
|
+
}
|
|
115
|
+
if (!this.key) {
|
|
116
|
+
throw new Error('No se pudo inicializar la clave de encriptación');
|
|
117
|
+
}
|
|
118
|
+
// Convertir texto a bytes
|
|
119
|
+
const encoder = new TextEncoder();
|
|
120
|
+
const data = encoder.encode(plaintext);
|
|
121
|
+
// Generar IV aleatorio
|
|
122
|
+
const iv = crypto.getRandomValues(new Uint8Array(EncryptionHelper.IV_LENGTH));
|
|
123
|
+
// Encriptar
|
|
124
|
+
const encryptedBuffer = await crypto.subtle.encrypt({
|
|
125
|
+
name: EncryptionHelper.ALGORITHM,
|
|
126
|
+
iv,
|
|
127
|
+
}, this.key, data);
|
|
128
|
+
// Combinar IV + datos encriptados
|
|
129
|
+
const combined = new Uint8Array(iv.length + encryptedBuffer.byteLength);
|
|
130
|
+
combined.set(iv, 0);
|
|
131
|
+
combined.set(new Uint8Array(encryptedBuffer), iv.length);
|
|
132
|
+
// Retornar en Base64
|
|
133
|
+
return this.arrayBufferToBase64(combined.buffer);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Desencripta un texto encriptado
|
|
137
|
+
* @param ciphertext - Texto encriptado en Base64
|
|
138
|
+
* @returns Texto plano
|
|
139
|
+
*/
|
|
140
|
+
async decrypt(ciphertext) {
|
|
141
|
+
if (!this.key) {
|
|
142
|
+
await this.initializeFromStored();
|
|
143
|
+
}
|
|
144
|
+
if (!this.key) {
|
|
145
|
+
throw new Error('No se pudo inicializar la clave de encriptación');
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
// Decodificar de Base64
|
|
149
|
+
const combined = new Uint8Array(this.base64ToArrayBuffer(ciphertext));
|
|
150
|
+
// Extraer IV y datos encriptados
|
|
151
|
+
const iv = combined.slice(0, EncryptionHelper.IV_LENGTH);
|
|
152
|
+
const encryptedData = combined.slice(EncryptionHelper.IV_LENGTH);
|
|
153
|
+
// Desencriptar
|
|
154
|
+
const decryptedBuffer = await crypto.subtle.decrypt({
|
|
155
|
+
name: EncryptionHelper.ALGORITHM,
|
|
156
|
+
iv,
|
|
157
|
+
}, this.key, encryptedData);
|
|
158
|
+
// Convertir bytes a texto
|
|
159
|
+
const decoder = new TextDecoder();
|
|
160
|
+
return decoder.decode(decryptedBuffer);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw new Error(`Error al desencriptar: ${error instanceof Error ? error.message : 'Error desconocido'}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Encripta el nombre de una clave para ofuscar nombres en localStorage
|
|
168
|
+
* @param keyName - Nombre original de la clave
|
|
169
|
+
* @returns Nombre encriptado con prefijo __enc_
|
|
170
|
+
*/
|
|
171
|
+
async encryptKey(keyName) {
|
|
172
|
+
const baseKey = await this.generateBaseKey();
|
|
173
|
+
const combined = keyName + baseKey;
|
|
174
|
+
const hash = await this.hashString(combined);
|
|
175
|
+
return `__enc_${hash.substring(0, 16)}`;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Limpia todos los datos encriptados del localStorage
|
|
179
|
+
*/
|
|
180
|
+
clearEncryptedData() {
|
|
181
|
+
const keysToRemove = [];
|
|
182
|
+
// Identificar todas las claves encriptadas
|
|
183
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
184
|
+
const key = localStorage.key(i);
|
|
185
|
+
if (key && key.startsWith('__enc_')) {
|
|
186
|
+
keysToRemove.push(key);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Eliminar claves encriptadas
|
|
190
|
+
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
191
|
+
// Eliminar salt
|
|
192
|
+
localStorage.removeItem(EncryptionHelper.SALT_STORAGE_KEY);
|
|
193
|
+
// Resetear clave en memoria
|
|
194
|
+
this.key = null;
|
|
195
|
+
this.baseKey = '';
|
|
196
|
+
this.baseKeyPromise = null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Verifica si el navegador soporta Web Crypto API
|
|
200
|
+
*/
|
|
201
|
+
static isSupported() {
|
|
202
|
+
return !!(typeof crypto !== 'undefined' &&
|
|
203
|
+
crypto.subtle &&
|
|
204
|
+
crypto.getRandomValues);
|
|
205
|
+
}
|
|
206
|
+
// Métodos auxiliares para conversión de datos
|
|
207
|
+
arrayBufferToBase64(buffer) {
|
|
208
|
+
const bytes = new Uint8Array(buffer);
|
|
209
|
+
let binary = '';
|
|
210
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
211
|
+
binary += String.fromCharCode(bytes[i]);
|
|
212
|
+
}
|
|
213
|
+
return btoa(binary);
|
|
214
|
+
}
|
|
215
|
+
base64ToArrayBuffer(base64) {
|
|
216
|
+
const binary = atob(base64);
|
|
217
|
+
const bytes = new Uint8Array(binary.length);
|
|
218
|
+
for (let i = 0; i < binary.length; i++) {
|
|
219
|
+
bytes[i] = binary.charCodeAt(i);
|
|
220
|
+
}
|
|
221
|
+
return bytes.buffer;
|
|
222
|
+
}
|
|
223
|
+
arrayBufferToHex(buffer) {
|
|
224
|
+
const bytes = new Uint8Array(buffer);
|
|
225
|
+
return Array.from(bytes)
|
|
226
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
227
|
+
.join('');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Constantes criptográficas
|
|
231
|
+
EncryptionHelper.ALGORITHM = 'AES-GCM';
|
|
232
|
+
EncryptionHelper.KEY_LENGTH = 256;
|
|
233
|
+
EncryptionHelper.IV_LENGTH = 12; // 96 bits para GCM
|
|
234
|
+
EncryptionHelper.SALT_LENGTH = 16; // 128 bits
|
|
235
|
+
EncryptionHelper.ITERATIONS = 100000;
|
|
236
|
+
EncryptionHelper.HASH_ALGORITHM = 'SHA-256';
|
|
237
|
+
EncryptionHelper.SALT_STORAGE_KEY = '__app_salt';
|
|
238
|
+
EncryptionHelper.APP_IDENTIFIER = 'mtt-local-cipher-v1'; // Identificador único de la app
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* SecureStorage - API de alto nivel que imita localStorage con cifrado automático
|
|
242
|
+
* Implementa el patrón Singleton
|
|
243
|
+
*/
|
|
244
|
+
class SecureStorage {
|
|
245
|
+
constructor() {
|
|
246
|
+
this.encryptionHelper = new EncryptionHelper();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Obtiene la instancia singleton de SecureStorage
|
|
250
|
+
*/
|
|
251
|
+
static getInstance() {
|
|
252
|
+
if (!SecureStorage.instance) {
|
|
253
|
+
SecureStorage.instance = new SecureStorage();
|
|
254
|
+
}
|
|
255
|
+
return SecureStorage.instance;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Guarda un valor encriptado en localStorage
|
|
259
|
+
* @param key - Clave para almacenar
|
|
260
|
+
* @param value - Valor a encriptar y almacenar
|
|
261
|
+
*/
|
|
262
|
+
async setItem(key, value) {
|
|
263
|
+
if (!EncryptionHelper.isSupported()) {
|
|
264
|
+
console.warn('Web Crypto API no soportada, usando localStorage sin encriptar');
|
|
265
|
+
localStorage.setItem(key, value);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
// Encriptar el nombre de la clave
|
|
270
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
271
|
+
// Encriptar el valor
|
|
272
|
+
const encryptedValue = await this.encryptionHelper.encrypt(value);
|
|
273
|
+
// Guardar en localStorage
|
|
274
|
+
localStorage.setItem(encryptedKey, encryptedValue);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
console.error('Error al guardar dato encriptado, usando fallback:', error);
|
|
278
|
+
localStorage.setItem(key, value);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Recupera y desencripta un valor de localStorage
|
|
283
|
+
* @param key - Clave a buscar
|
|
284
|
+
* @returns Valor desencriptado o null si no existe
|
|
285
|
+
*/
|
|
286
|
+
async getItem(key) {
|
|
287
|
+
if (!EncryptionHelper.isSupported()) {
|
|
288
|
+
return localStorage.getItem(key);
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
// Encriptar el nombre de la clave
|
|
292
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
293
|
+
// Buscar el valor encriptado
|
|
294
|
+
let encryptedValue = localStorage.getItem(encryptedKey);
|
|
295
|
+
// Retrocompatibilidad: si no existe con clave encriptada, buscar con clave normal
|
|
296
|
+
if (!encryptedValue) {
|
|
297
|
+
encryptedValue = localStorage.getItem(key);
|
|
298
|
+
if (!encryptedValue) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
// Si encontramos un valor con clave normal, intentar desencriptarlo
|
|
302
|
+
// (podría ser un valor ya encriptado pero con clave antigua)
|
|
303
|
+
}
|
|
304
|
+
// Desencriptar el valor
|
|
305
|
+
return await this.encryptionHelper.decrypt(encryptedValue);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error('Error al recuperar dato encriptado:', error);
|
|
309
|
+
// Fallback: intentar leer directamente
|
|
310
|
+
return localStorage.getItem(key);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Elimina un valor de localStorage
|
|
315
|
+
* @param key - Clave a eliminar
|
|
316
|
+
*/
|
|
317
|
+
async removeItem(key) {
|
|
318
|
+
if (!EncryptionHelper.isSupported()) {
|
|
319
|
+
localStorage.removeItem(key);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
// Encriptar el nombre de la clave
|
|
324
|
+
const encryptedKey = await this.encryptionHelper.encryptKey(key);
|
|
325
|
+
// Eliminar ambas versiones (encriptada y normal) por seguridad
|
|
326
|
+
localStorage.removeItem(encryptedKey);
|
|
327
|
+
localStorage.removeItem(key);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error('Error al eliminar dato encriptado:', error);
|
|
331
|
+
localStorage.removeItem(key);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Verifica si existe un valor para la clave dada
|
|
336
|
+
* @param key - Clave a verificar
|
|
337
|
+
* @returns true si existe, false si no
|
|
338
|
+
*/
|
|
339
|
+
async hasItem(key) {
|
|
340
|
+
const value = await this.getItem(key);
|
|
341
|
+
return value !== null;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Limpia todos los datos encriptados
|
|
345
|
+
*/
|
|
346
|
+
clear() {
|
|
347
|
+
this.encryptionHelper.clearEncryptedData();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Migra datos existentes no encriptados a formato encriptado
|
|
351
|
+
* @param keys - Array de claves a migrar
|
|
352
|
+
*/
|
|
353
|
+
async migrateExistingData(keys) {
|
|
354
|
+
if (!EncryptionHelper.isSupported()) {
|
|
355
|
+
console.warn('Web Crypto API no soportada, no se puede migrar');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
console.log(`🔄 Iniciando migración de ${keys.length} claves...`);
|
|
359
|
+
for (const key of keys) {
|
|
360
|
+
try {
|
|
361
|
+
// Leer el valor no encriptado
|
|
362
|
+
const value = localStorage.getItem(key);
|
|
363
|
+
if (value === null) {
|
|
364
|
+
continue; // La clave no existe, saltar
|
|
365
|
+
}
|
|
366
|
+
// Verificar si ya está encriptado intentando desencriptarlo
|
|
367
|
+
try {
|
|
368
|
+
await this.encryptionHelper.decrypt(value);
|
|
369
|
+
console.log(`✓ ${key} ya está encriptado, saltando`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// No está encriptado, proceder con la migración
|
|
374
|
+
}
|
|
375
|
+
// Guardar usando setItem (que encriptará automáticamente)
|
|
376
|
+
await this.setItem(key, value);
|
|
377
|
+
// Eliminar la versión no encriptada
|
|
378
|
+
localStorage.removeItem(key);
|
|
379
|
+
console.log(`✓ ${key} migrado exitosamente`);
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error(`✗ Error al migrar ${key}:`, error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
console.log('✅ Migración completada');
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Obtiene información de debug sobre el estado del almacenamiento
|
|
389
|
+
*/
|
|
390
|
+
getDebugInfo() {
|
|
391
|
+
const allKeys = [];
|
|
392
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
393
|
+
const key = localStorage.key(i);
|
|
394
|
+
if (key)
|
|
395
|
+
allKeys.push(key);
|
|
396
|
+
}
|
|
397
|
+
const encryptedKeys = allKeys.filter(key => key.startsWith('__enc_'));
|
|
398
|
+
const unencryptedKeys = allKeys.filter(key => !key.startsWith('__enc_') && key !== '__app_salt');
|
|
399
|
+
return {
|
|
400
|
+
cryptoSupported: EncryptionHelper.isSupported(),
|
|
401
|
+
encryptedKeys,
|
|
402
|
+
unencryptedKeys,
|
|
403
|
+
totalKeys: allKeys.length,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
SecureStorage.instance = null;
|
|
408
|
+
|
|
409
|
+
const secureStorage$2 = SecureStorage.getInstance();
|
|
410
|
+
/**
|
|
411
|
+
* Hook de React para usar SecureStorage de forma reactiva
|
|
412
|
+
* Similar a useState pero con persistencia encriptada
|
|
413
|
+
*
|
|
414
|
+
* @param key - Clave para almacenar en localStorage
|
|
415
|
+
* @param initialValue - Valor inicial si no existe en storage
|
|
416
|
+
* @returns [value, setValue, loading, error]
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* const [token, setToken, loading] = useSecureStorage('accessToken', '');
|
|
420
|
+
*/
|
|
421
|
+
function useSecureStorage(key, initialValue) {
|
|
422
|
+
const [storedValue, setStoredValue] = react.useState(initialValue);
|
|
423
|
+
const [loading, setLoading] = react.useState(true);
|
|
424
|
+
const [error, setError] = react.useState(null);
|
|
425
|
+
// Cargar valor inicial
|
|
426
|
+
react.useEffect(() => {
|
|
427
|
+
const loadValue = async () => {
|
|
428
|
+
try {
|
|
429
|
+
setLoading(true);
|
|
430
|
+
setError(null);
|
|
431
|
+
const item = await secureStorage$2.getItem(key);
|
|
432
|
+
if (item !== null) {
|
|
433
|
+
// Si T es un objeto, parsear JSON
|
|
434
|
+
if (typeof initialValue === 'object') {
|
|
435
|
+
setStoredValue(JSON.parse(item));
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
setStoredValue(item);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
setStoredValue(initialValue);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
setError(err instanceof Error ? err : new Error('Error al cargar valor'));
|
|
447
|
+
setStoredValue(initialValue);
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
setLoading(false);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
loadValue();
|
|
454
|
+
}, [key]); // Solo recargar si cambia la clave
|
|
455
|
+
// Función para actualizar el valor
|
|
456
|
+
const setValue = react.useCallback(async (value) => {
|
|
457
|
+
try {
|
|
458
|
+
setError(null);
|
|
459
|
+
setStoredValue(value);
|
|
460
|
+
// Si T es un objeto, convertir a JSON
|
|
461
|
+
const valueToStore = typeof value === 'object'
|
|
462
|
+
? JSON.stringify(value)
|
|
463
|
+
: String(value);
|
|
464
|
+
await secureStorage$2.setItem(key, valueToStore);
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
setError(err instanceof Error ? err : new Error('Error al guardar valor'));
|
|
468
|
+
throw err;
|
|
469
|
+
}
|
|
470
|
+
}, [key]);
|
|
471
|
+
return [storedValue, setValue, loading, error];
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Hook para verificar si una clave existe en SecureStorage
|
|
475
|
+
*
|
|
476
|
+
* @param key - Clave a verificar
|
|
477
|
+
* @returns [exists, loading, error]
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* const [hasToken, loading] = useSecureStorageItem('accessToken');
|
|
481
|
+
*/
|
|
482
|
+
function useSecureStorageItem(key) {
|
|
483
|
+
const [exists, setExists] = react.useState(false);
|
|
484
|
+
const [loading, setLoading] = react.useState(true);
|
|
485
|
+
const [error, setError] = react.useState(null);
|
|
486
|
+
react.useEffect(() => {
|
|
487
|
+
const checkExists = async () => {
|
|
488
|
+
try {
|
|
489
|
+
setLoading(true);
|
|
490
|
+
setError(null);
|
|
491
|
+
const hasItem = await secureStorage$2.hasItem(key);
|
|
492
|
+
setExists(hasItem);
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
setError(err instanceof Error ? err : new Error('Error al verificar clave'));
|
|
496
|
+
setExists(false);
|
|
497
|
+
}
|
|
498
|
+
finally {
|
|
499
|
+
setLoading(false);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
checkExists();
|
|
503
|
+
}, [key]);
|
|
504
|
+
return [exists, loading, error];
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Hook para obtener información de debug del almacenamiento
|
|
508
|
+
*
|
|
509
|
+
* @returns Información de debug
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* const debugInfo = useSecureStorageDebug();
|
|
513
|
+
* console.log(`Claves encriptadas: ${debugInfo.encryptedKeys.length}`);
|
|
514
|
+
*/
|
|
515
|
+
function useSecureStorageDebug() {
|
|
516
|
+
const [debugInfo, setDebugInfo] = react.useState(secureStorage$2.getDebugInfo());
|
|
517
|
+
react.useEffect(() => {
|
|
518
|
+
// Actualizar cada segundo
|
|
519
|
+
const interval = setInterval(() => {
|
|
520
|
+
setDebugInfo(secureStorage$2.getDebugInfo());
|
|
521
|
+
}, 1000);
|
|
522
|
+
return () => clearInterval(interval);
|
|
523
|
+
}, []);
|
|
524
|
+
return debugInfo;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/******************************************************************************
|
|
528
|
+
Copyright (c) Microsoft Corporation.
|
|
529
|
+
|
|
530
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
531
|
+
purpose with or without fee is hereby granted.
|
|
532
|
+
|
|
533
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
534
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
535
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
536
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
537
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
538
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
539
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
540
|
+
***************************************************************************** */
|
|
541
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
545
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
546
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
547
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
548
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
549
|
+
var _, done = false;
|
|
550
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
551
|
+
var context = {};
|
|
552
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
553
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
554
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
555
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
556
|
+
if (kind === "accessor") {
|
|
557
|
+
if (result === void 0) continue;
|
|
558
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
559
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
560
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
561
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
562
|
+
}
|
|
563
|
+
else if (_ = accept(result)) {
|
|
564
|
+
if (kind === "field") initializers.unshift(_);
|
|
565
|
+
else descriptor[key] = _;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
569
|
+
done = true;
|
|
570
|
+
}
|
|
571
|
+
function __runInitializers(thisArg, initializers, value) {
|
|
572
|
+
var useValue = arguments.length > 2;
|
|
573
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
574
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
575
|
+
}
|
|
576
|
+
return useValue ? value : void 0;
|
|
577
|
+
}
|
|
578
|
+
function __setFunctionName(f, name, prefix) {
|
|
579
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
580
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
581
|
+
}
|
|
582
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
583
|
+
var e = new Error(message);
|
|
584
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Servicio de Angular para SecureStorage
|
|
589
|
+
* Proporciona una API reactiva usando RxJS Observables
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* constructor(private secureStorage: SecureStorageService) {}
|
|
593
|
+
*
|
|
594
|
+
* // Guardar
|
|
595
|
+
* this.secureStorage.setItem('token', 'abc123').subscribe();
|
|
596
|
+
*
|
|
597
|
+
* // Leer
|
|
598
|
+
* this.secureStorage.getItem('token').subscribe(token => console.log(token));
|
|
599
|
+
*/
|
|
600
|
+
let SecureStorageService = (() => {
|
|
601
|
+
let _classDecorators = [core.Injectable({
|
|
602
|
+
providedIn: 'root'
|
|
603
|
+
})];
|
|
604
|
+
let _classDescriptor;
|
|
605
|
+
let _classExtraInitializers = [];
|
|
606
|
+
let _classThis;
|
|
607
|
+
_classThis = class {
|
|
608
|
+
constructor() {
|
|
609
|
+
this.debugInfo$ = new rxjs.BehaviorSubject(this.getDebugInfo());
|
|
610
|
+
this.storage = SecureStorage.getInstance();
|
|
611
|
+
// Actualizar debug info cada segundo
|
|
612
|
+
setInterval(() => {
|
|
613
|
+
this.debugInfo$.next(this.getDebugInfo());
|
|
614
|
+
}, 1000);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Guarda un valor encriptado
|
|
618
|
+
* @param key - Clave
|
|
619
|
+
* @param value - Valor a guardar
|
|
620
|
+
* @returns Observable que completa cuando se guarda
|
|
621
|
+
*/
|
|
622
|
+
setItem(key, value) {
|
|
623
|
+
return rxjs.from(this.storage.setItem(key, value));
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Recupera un valor desencriptado
|
|
627
|
+
* @param key - Clave
|
|
628
|
+
* @returns Observable con el valor o null
|
|
629
|
+
*/
|
|
630
|
+
getItem(key) {
|
|
631
|
+
return rxjs.from(this.storage.getItem(key));
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Elimina un valor
|
|
635
|
+
* @param key - Clave a eliminar
|
|
636
|
+
* @returns Observable que completa cuando se elimina
|
|
637
|
+
*/
|
|
638
|
+
removeItem(key) {
|
|
639
|
+
return rxjs.from(this.storage.removeItem(key));
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Verifica si existe una clave
|
|
643
|
+
* @param key - Clave a verificar
|
|
644
|
+
* @returns Observable con true/false
|
|
645
|
+
*/
|
|
646
|
+
hasItem(key) {
|
|
647
|
+
return rxjs.from(this.storage.hasItem(key));
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Limpia todos los datos encriptados
|
|
651
|
+
*/
|
|
652
|
+
clear() {
|
|
653
|
+
this.storage.clear();
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Migra datos existentes a formato encriptado
|
|
657
|
+
* @param keys - Array de claves a migrar
|
|
658
|
+
* @returns Observable que completa cuando termina la migración
|
|
659
|
+
*/
|
|
660
|
+
migrateExistingData(keys) {
|
|
661
|
+
return rxjs.from(this.storage.migrateExistingData(keys));
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Obtiene información de debug como Observable
|
|
665
|
+
* @returns Observable con información de debug que se actualiza automáticamente
|
|
666
|
+
*/
|
|
667
|
+
getDebugInfo$() {
|
|
668
|
+
return this.debugInfo$.asObservable();
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Obtiene información de debug de forma síncrona
|
|
672
|
+
*/
|
|
673
|
+
getDebugInfo() {
|
|
674
|
+
return this.storage.getDebugInfo();
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Helper para guardar objetos JSON
|
|
678
|
+
* @param key - Clave
|
|
679
|
+
* @param value - Objeto a guardar
|
|
680
|
+
*/
|
|
681
|
+
setObject(key, value) {
|
|
682
|
+
return this.setItem(key, JSON.stringify(value));
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Helper para recuperar objetos JSON
|
|
686
|
+
* @param key - Clave
|
|
687
|
+
* @returns Observable con el objeto parseado o null
|
|
688
|
+
*/
|
|
689
|
+
getObject(key) {
|
|
690
|
+
return this.getItem(key).pipe(operators.map(value => value ? JSON.parse(value) : null), operators.catchError(() => rxjs.from([null])));
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
__setFunctionName(_classThis, "SecureStorageService");
|
|
694
|
+
(() => {
|
|
695
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
696
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
697
|
+
_classThis = _classDescriptor.value;
|
|
698
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
699
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
700
|
+
})();
|
|
701
|
+
return _classThis;
|
|
702
|
+
})();
|
|
703
|
+
|
|
704
|
+
const secureStorage$1 = SecureStorage.getInstance();
|
|
705
|
+
/**
|
|
706
|
+
* Función de debug para verificar el estado del sistema de encriptación
|
|
707
|
+
* Muestra información detallada en la consola
|
|
708
|
+
*/
|
|
709
|
+
async function debugEncryptionState() {
|
|
710
|
+
console.group('🔐 Estado del Sistema de Encriptación');
|
|
711
|
+
console.log('Soporte Crypto API:', EncryptionHelper.isSupported());
|
|
712
|
+
// Obtener información de debug
|
|
713
|
+
const debugInfo = secureStorage$1.getDebugInfo();
|
|
714
|
+
console.log('Claves encriptadas:', debugInfo.encryptedKeys.length);
|
|
715
|
+
console.log('Claves sin encriptar:', debugInfo.unencryptedKeys);
|
|
716
|
+
console.log('Total de claves:', debugInfo.totalKeys);
|
|
717
|
+
if (debugInfo.encryptedKeys.length > 0) {
|
|
718
|
+
console.log('✅ Datos encriptados encontrados:');
|
|
719
|
+
debugInfo.encryptedKeys.forEach(key => {
|
|
720
|
+
const value = localStorage.getItem(key);
|
|
721
|
+
console.log(` ${key}: ${value?.substring(0, 30)}...`);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
console.log('⚠️ No se encontraron datos encriptados');
|
|
726
|
+
}
|
|
727
|
+
if (debugInfo.unencryptedKeys.length > 0) {
|
|
728
|
+
console.log('⚠️ Claves sin encriptar encontradas:');
|
|
729
|
+
debugInfo.unencryptedKeys.forEach(key => {
|
|
730
|
+
console.log(` ${key}`);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
console.groupEnd();
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Fuerza la migración de claves comunes a formato encriptado
|
|
737
|
+
* Útil para desarrollo y testing
|
|
738
|
+
*/
|
|
739
|
+
async function forceMigration(customKeys) {
|
|
740
|
+
const defaultKeys = [
|
|
741
|
+
'accessToken',
|
|
742
|
+
'refreshToken',
|
|
743
|
+
'user',
|
|
744
|
+
'sessionId',
|
|
745
|
+
'authToken',
|
|
746
|
+
'userData',
|
|
747
|
+
];
|
|
748
|
+
const keysToMigrate = customKeys || defaultKeys;
|
|
749
|
+
console.log(`🔄 Iniciando migración forzada de ${keysToMigrate.length} claves...`);
|
|
750
|
+
await secureStorage$1.migrateExistingData(keysToMigrate);
|
|
751
|
+
console.log('✅ Migración forzada completada');
|
|
752
|
+
// Mostrar estado después de la migración
|
|
753
|
+
await debugEncryptionState();
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* @mtt/local-cipher
|
|
758
|
+
* Librería de cifrado local AES-256-GCM para Angular, React y JavaScript
|
|
759
|
+
*
|
|
760
|
+
* @author MTT
|
|
761
|
+
* @license MIT
|
|
762
|
+
*/
|
|
763
|
+
// Core exports
|
|
764
|
+
const secureStorage = SecureStorage.getInstance();
|
|
765
|
+
// Version
|
|
766
|
+
const VERSION = '1.0.0';
|
|
767
|
+
|
|
768
|
+
exports.EncryptionHelper = EncryptionHelper;
|
|
769
|
+
exports.SecureStorage = SecureStorage;
|
|
770
|
+
exports.SecureStorageService = SecureStorageService;
|
|
771
|
+
exports.VERSION = VERSION;
|
|
772
|
+
exports.debugEncryptionState = debugEncryptionState;
|
|
773
|
+
exports.forceMigration = forceMigration;
|
|
774
|
+
exports.reactSecureStorage = secureStorage$2;
|
|
775
|
+
exports.secureStorage = secureStorage;
|
|
776
|
+
exports.useSecureStorage = useSecureStorage;
|
|
777
|
+
exports.useSecureStorageDebug = useSecureStorageDebug;
|
|
778
|
+
exports.useSecureStorageItem = useSecureStorageItem;
|
|
779
|
+
//# sourceMappingURL=index.js.map
|