s3-client-dtb 0.0.1 → 1.0.2

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 CHANGED
@@ -1,6 +1,10 @@
1
1
  # s3-client-dtb
2
2
 
3
- Paquete de almacenamiento de archivos para Node.js y NestJS.
3
+ Paquete de almacenamiento de archivos para Node.js y NestJS.
4
+
5
+ ## Servidor previsto (modo remoto)
6
+
7
+ En modo **cliente** (`StorageClient`) o **online**, este paquete está pensado para hablar con el backend ejecutable **[s3-back-dtb](https://www.npmjs.com/package/s3-back-dtb)** en npm. Instálalo en el servidor, configura las variables de entorno compartidas (`CLIENT_SECRET`, `TOTP_SECRET`, etc.) y usa la misma URL en `STORAGE_BASE_URL`. En modo local (`StorageCore` / **offline**) no necesitas ese servidor.
4
8
 
5
9
  ## 🎯 Dos Modos de Uso
6
10
 
@@ -13,7 +17,7 @@ Almacena archivos directamente en el sistema de archivos de tu servidor. **Ideal
13
17
  - Cuando no necesitas un backend separado
14
18
 
15
19
  ### 🌐 Modo Cliente (StorageClient)
16
- Se conecta a un backend remoto mediante HTTP. **Ideal para:
20
+ Se conecta a un backend remoto mediante HTTP. **Ideal para:**
17
21
  - Producción con backend separado
18
22
  - Múltiples aplicaciones compartiendo almacenamiento
19
23
  - Escalabilidad y distribución
@@ -22,7 +26,10 @@ Se conecta a un backend remoto mediante HTTP. **Ideal para:
22
26
 
23
27
  - ✅ Almacenamiento local de archivos (StorageCore)
24
28
  - ✅ Cliente HTTP para backend remoto (StorageClient)
25
- - ✅ Autenticación con API keys
29
+ - ✅ Autenticación con JWT y TOTP (Time-based OTP)
30
+ - ✅ Generación automática de tokens con CLIENT_SECRET + TOTP
31
+ - ✅ Configuración mediante variables de entorno
32
+ - ✅ Tiempos de expiración configurables por cliente
26
33
  - ✅ Validación de tipos MIME
27
34
  - ✅ Límites de tamaño de archivo
28
35
  - ✅ Sanitización de rutas (protección contra path traversal)
@@ -47,13 +54,88 @@ npm install @nestjs/common
47
54
 
48
55
  ### Elegir el Modo Correcto
49
56
 
50
- **¿Necesitas almacenar archivos localmente?** → Usa `StorageCore`
51
- **¿Necesitas conectarte a un backend remoto?** → Usa `StorageClient`
57
+ **¿Necesitas almacenar archivos localmente?** → Usa `StorageCore` o modo `offline`
58
+ **¿Necesitas conectarte a un backend remoto?** → Usa `StorageClient` o modo `online`
52
59
 
53
60
  Ambos tienen la **misma API**, solo cambia la inicialización.
54
61
 
55
62
  ---
56
63
 
64
+ ## 🔄 Modo Offline/Online (Recomendado para desarrollo)
65
+
66
+ El paquete soporta un **modo automático** que detecta si estás en desarrollo (offline) o producción (online) basándose en la variable de entorno `STORAGE_MODE`.
67
+
68
+ ### ¿Por qué usar esto?
69
+
70
+ - **En desarrollo**: No necesitas tener un servidor de almacenamiento corriendo
71
+ - **En producción**: Usa el servidor remoto sin cambiar código
72
+ - **Mismo código**: La API es idéntica, solo cambian las variables de entorno
73
+
74
+ ### Configuración Rápida
75
+
76
+ **Para desarrollo (offline):**
77
+ ```bash
78
+ # .env
79
+ STORAGE_MODE=offline
80
+ STORAGE_ROOT_PATH=./uploads
81
+ STORAGE_MAX_FILE_SIZE=50mb # Opcional
82
+ STORAGE_ALLOWED_MIME_TYPES=image/*,video/* # Opcional
83
+ ```
84
+
85
+ **Para producción (online):**
86
+ ```bash
87
+ # .env
88
+ STORAGE_MODE=online
89
+ STORAGE_BASE_URL=https://storage.example.com
90
+ STORAGE_CLIENT_ID=mi-app
91
+ STORAGE_CLIENT_NAME=Mi Aplicacion
92
+ STORAGE_PERMISSIONS=read,write,delete
93
+ STORAGE_ALLOWED_PATHS=*
94
+ STORAGE_CLIENT_SECRET=tu-clave-secreta...
95
+ STORAGE_TOTP_SECRET=tu-semilla-totp...
96
+ ```
97
+
98
+ ### Uso en Código
99
+
100
+ ```typescript
101
+ import 'dotenv/config';
102
+ import { createClientFromConfig, isOfflineClient } from 's3-client-dtb';
103
+
104
+ // ✅ Detecta automáticamente el modo según STORAGE_MODE
105
+ const client = createClientFromConfig();
106
+
107
+ // La API es idéntica en ambos modos
108
+ const path = await client.uploadFile('images', buffer, 'image/png');
109
+ const stream = await client.downloadFile(path);
110
+ const exists = await client.fileExists(path);
111
+ await client.deleteFile(path);
112
+
113
+ // Si necesitas saber en qué modo estás:
114
+ if (isOfflineClient(client)) {
115
+ console.log('Modo offline - archivos guardados localmente');
116
+ } else {
117
+ console.log('Modo online - usando servidor remoto');
118
+ }
119
+ ```
120
+
121
+ ### Funciones Auxiliares
122
+
123
+ ```typescript
124
+ import {
125
+ createClientFromConfig, // Detecta modo automáticamente
126
+ createOfflineClient, // Forzar modo offline
127
+ createOnlineClient, // Forzar modo online (deprecated, usa createClientFromConfig)
128
+ getStorageMode, // Obtener el modo actual
129
+ isOfflineClient, // Verificar si es cliente offline
130
+ isOnlineClient, // Verificar si es cliente online
131
+ } from 's3-client-dtb';
132
+
133
+ // Obtener el modo configurado
134
+ const mode = getStorageMode(); // 'online' | 'offline'
135
+ ```
136
+
137
+ ---
138
+
57
139
  ## 📁 Modo Local (StorageCore)
58
140
 
59
141
  ### Configuración
@@ -95,51 +177,363 @@ const exists = await storage.fileExists(path);
95
177
 
96
178
  ## 🌐 Modo Cliente (StorageClient)
97
179
 
98
- ### Configuración
180
+ ### Configuración con Variables de Entorno
181
+
182
+ El cliente se configura mediante variables de entorno. Crea un archivo `.env` basado en `.env.example`:
183
+
184
+ ```bash
185
+ # Modo de almacenamiento (online para servidor remoto)
186
+ STORAGE_MODE=online
187
+
188
+ # Variables requeridas para modo online
189
+ STORAGE_CLIENT_ID=mi-cliente-123
190
+ STORAGE_CLIENT_NAME=Mi Aplicacion
191
+ STORAGE_BASE_URL=http://localhost:3000
192
+ STORAGE_PERMISSIONS=["read","write","delete"]
193
+ STORAGE_ALLOWED_PATHS=["*"]
194
+ STORAGE_CLIENT_SECRET=tu-clave-secreta-compartida-minimo-32-caracteres
195
+ STORAGE_TOTP_SECRET=tu-semilla-totp-compartida-minimo-16-caracteres
196
+
197
+ # Variables opcionales
198
+ STORAGE_TOKEN_EXPIRES_IN=5m
199
+ STORAGE_TOTP_PERIOD=30
200
+ STORAGE_RATE_LIMIT_REQUESTS=1000
201
+ STORAGE_RATE_LIMIT_WINDOW=1h
202
+ ```
203
+
204
+ **IMPORTANTE**:
205
+ - Los valores de `STORAGE_CLIENT_SECRET` y `STORAGE_TOTP_SECRET` deben ser los mismos que están en las variables de entorno del servidor (`CLIENT_SECRET` y `TOTP_SECRET`).
206
+ - Si el servidor usa `TOTP_PERIOD` distinto de 30, configura `STORAGE_TOTP_PERIOD` con el mismo valor (15–300 segundos).
207
+ - Para `STORAGE_PERMISSIONS` y `STORAGE_ALLOWED_PATHS` puedes usar JSON array (`["read","write","delete"]`) o valores separados por comas (`read,write,delete`).
208
+
209
+ ### 🔗 Conectando con el Backend
210
+
211
+ Para que el cliente se conecte correctamente al backend, necesitas asegurarte de que ciertos valores coincidan exactamente entre ambos.
212
+
213
+ #### Checklist de Configuración
214
+
215
+ Antes de usar el cliente, verifica que:
216
+
217
+ - [ ] **`STORAGE_CLIENT_SECRET`** (cliente) = **`CLIENT_SECRET`** (servidor)
218
+ - [ ] **`STORAGE_TOTP_SECRET`** (cliente) = **`TOTP_SECRET`** (servidor)
219
+ - [ ] **`STORAGE_TOTP_PERIOD`** (cliente) = **`TOTP_PERIOD`** (servidor) - Si el servidor usa un valor distinto de 30
220
+ - [ ] **`STORAGE_BASE_URL`** apunta a la URL correcta del servidor (ej: `http://localhost:3000`)
221
+
222
+ #### Ejemplo de Configuración Completa
223
+
224
+ **Backend (s3-package/.env):**
225
+ ```env
226
+ CLIENT_SECRET=mi-clave-secreta-compartida-minimo-32-caracteres-1234567890
227
+ TOTP_SECRET=mi-semilla-totp-compartida-minimo-16-caracteres
228
+ TOTP_PERIOD=30
229
+ PORT=3000
230
+ ```
231
+
232
+ **Cliente (tu-app/.env):**
233
+ ```env
234
+ STORAGE_CLIENT_ID=mi-cliente-123
235
+ STORAGE_CLIENT_NAME=Mi Aplicacion
236
+ STORAGE_BASE_URL=http://localhost:3000
237
+ STORAGE_PERMISSIONS=["read","write","delete"]
238
+ STORAGE_ALLOWED_PATHS=["*"]
239
+ STORAGE_CLIENT_SECRET=mi-clave-secreta-compartida-minimo-32-caracteres-1234567890
240
+ STORAGE_TOTP_SECRET=mi-semilla-totp-compartida-minimo-16-caracteres
241
+ STORAGE_TOTP_PERIOD=30
242
+ ```
243
+
244
+ **⚠️ Importante**: Los valores de `CLIENT_SECRET` y `TOTP_SECRET` deben ser **exactamente iguales** en ambos archivos `.env`.
245
+
246
+ #### Verificar la Conexión
247
+
248
+ Una vez configurado, puedes verificar que la conexión funciona correctamente:
249
+
250
+ ```typescript
251
+ import 'dotenv/config';
252
+ import { createClientFromConfig } from 's3-client-dtb';
253
+
254
+ const client = createClientFromConfig();
255
+
256
+ // Verificar que puedes generar códigos TOTP correctos
257
+ try {
258
+ await client.verifyTotp();
259
+ console.log('✅ Conexión con el backend verificada correctamente');
260
+ } catch (error) {
261
+ console.error('❌ Error al conectar con el backend:', error);
262
+ // Verifica que los secrets coincidan y que el servidor esté corriendo
263
+ }
264
+ ```
265
+
266
+ #### Solución de Problemas de Conexión
267
+
268
+ **Error: "UNAUTHORIZED" o "TOTP_VERIFY_ERROR"**
269
+ - Verifica que `STORAGE_CLIENT_SECRET` y `STORAGE_TOTP_SECRET` coincidan exactamente con los del servidor
270
+ - Verifica que `STORAGE_TOTP_PERIOD` coincida con `TOTP_PERIOD` del servidor
271
+ - Asegúrate de que el servidor esté corriendo y accesible en `STORAGE_BASE_URL`
272
+
273
+ **Error: "Timeout al conectar con el servidor"**
274
+ - Verifica que `STORAGE_BASE_URL` sea correcta y el servidor esté corriendo
275
+ - Verifica la conectividad de red
276
+ - Aumenta el `timeout` en la configuración manual si es necesario
277
+
278
+ **Error: "FORBIDDEN"**
279
+ - Verifica que los `STORAGE_PERMISSIONS` incluyan la operación que intentas realizar
280
+ - Verifica que el `STORAGE_ALLOWED_PATHS` permita la ruta que intentas usar
281
+
282
+ ### Cargar Variables de Entorno
283
+
284
+ Si usas Node.js, puedes usar `dotenv` para cargar el archivo `.env`:
285
+
286
+ ```bash
287
+ npm install dotenv
288
+ ```
289
+
290
+ ```typescript
291
+ import 'dotenv/config';
292
+ import { createClientFromConfig } from 's3-client-dtb';
293
+
294
+ const client = createClientFromConfig();
295
+ ```
296
+
297
+ O cargar manualmente:
298
+
299
+ ```typescript
300
+ import { config } from 'dotenv';
301
+ config(); // Carga .env desde el directorio actual
302
+
303
+ import { createClientFromConfig } from 's3-client-dtb';
304
+ const client = createClientFromConfig();
305
+ ```
306
+
307
+ ### Crear Cliente desde Variables de Entorno
308
+
309
+ ```typescript
310
+ import { createClientFromConfig } from 's3-client-dtb';
311
+
312
+ // Lee las variables de entorno automáticamente
313
+ const client = createClientFromConfig();
314
+ ```
315
+
316
+ ### Configuración Manual
99
317
 
100
318
  ```typescript
101
319
  import { StorageClient } from 's3-client-dtb';
320
+ import type { ClientConfig } from 's3-client-dtb';
321
+
322
+ const clientConfig: ClientConfig = {
323
+ clientId: 'mi-cliente-123',
324
+ name: 'Mi Aplicacion',
325
+ permissions: ['read', 'write', 'delete'],
326
+ allowedPaths: ['*'],
327
+ rateLimit: {
328
+ requests: 1000,
329
+ window: '1h'
330
+ },
331
+ tokenExpiresIn: '5m'
332
+ };
102
333
 
103
- // ✅ Configuración simple - Solo necesitas URL y API key
104
334
  const client = new StorageClient({
105
- baseUrl: 'https://api.midominio.com', // URL del backend
106
- apiKey: 'sk_live_xxx', // Tu API key
107
- timeout: 30000 // Opcional: timeout en ms
335
+ baseUrl: 'https://api.midominio.com',
336
+ clientConfig,
337
+ clientSecret: 'tu-clave-secreta-compartida-minimo-32-caracteres',
338
+ totpSecret: 'tu-semilla-totp-compartida-minimo-16-caracteres',
339
+ totpPeriodSeconds: 30, // opcional; debe coincidir con TOTP_PERIOD del servidor
108
340
  });
109
341
  ```
110
342
 
343
+ ### Autenticación Automática
344
+
345
+ El cliente genera tokens automáticamente usando TOTP:
346
+
347
+ ```typescript
348
+ import { createClientFromConfig } from 's3-client-dtb';
349
+
350
+ const client = createClientFromConfig();
351
+
352
+ // El cliente genera tokens automáticamente usando CLIENT_SECRET + TOTP
353
+ // No necesitas llamar ningún método de autenticación manualmente
354
+
355
+ // Opcional: Verificar que puedes generar códigos TOTP correctos
356
+ await client.verifyTotp();
357
+
358
+ // Ahora puedes usar el cliente normalmente
359
+ // Los tokens se generan y renuevan automáticamente
360
+ ```
361
+
362
+ ### Variables de Entorno
363
+
364
+ #### Requeridas
365
+
366
+ - `STORAGE_CLIENT_ID` (string): Identificador único del cliente
367
+ - `STORAGE_CLIENT_NAME` (string): Nombre descriptivo de la aplicación
368
+ - `STORAGE_BASE_URL` (string): URL del backend (ej: `http://localhost:3000`)
369
+ - `STORAGE_PERMISSIONS` (string): Permisos del cliente. Puede ser JSON array (`["read","write","delete"]`) o valores separados por comas (`read,write,delete`)
370
+ - `STORAGE_ALLOWED_PATHS` (string): Paths permitidos. Puede ser JSON array (`["*"]` o `["images/*","documents/*"]`) o valores separados por comas (`*` o `images/*,documents/*`)
371
+ - `STORAGE_CLIENT_SECRET` (string): Clave secreta compartida con el servidor (mínimo 32 caracteres)
372
+ - `STORAGE_TOTP_SECRET` (string): Semilla TOTP compartida con el servidor (mínimo 16 caracteres)
373
+
374
+ #### Opcionales
375
+
376
+ - `STORAGE_TOKEN_EXPIRES_IN` (string): Duración del token JWT (ej: `"5m"`, `"1h"`, `"30m"`). Si no se especifica, se usa el default del servidor (`TOKEN_EXPIRES_IN`)
377
+ - `STORAGE_TOTP_PERIOD` (number): Período TOTP en segundos (15–300, default 30). Debe coincidir con `TOTP_PERIOD` del servidor.
378
+ - `STORAGE_RATE_LIMIT_REQUESTS` (number): Número de peticiones permitidas (solo se usa si también defines `STORAGE_RATE_LIMIT_WINDOW`)
379
+ - `STORAGE_RATE_LIMIT_WINDOW` (string): Ventana de tiempo para el rate limit (ej: `"1h"`, `"30m"`). Solo se usa si también defines `STORAGE_RATE_LIMIT_REQUESTS`
380
+
381
+ #### Tabla de Referencia Rápida
382
+
383
+ | Variable | Requerida | Tipo | Default | Descripción |
384
+ |----------|-----------|------|---------|-------------|
385
+ | `STORAGE_CLIENT_ID` | ✅ | string | - | Identificador único del cliente |
386
+ | `STORAGE_CLIENT_NAME` | ✅ | string | - | Nombre descriptivo de la aplicación |
387
+ | `STORAGE_BASE_URL` | ✅ | string | - | URL del backend (ej: `http://localhost:3000`) |
388
+ | `STORAGE_PERMISSIONS` | ✅ | string | - | Permisos: JSON array o comma-separated (`["read","write","delete"]` o `read,write,delete`) |
389
+ | `STORAGE_ALLOWED_PATHS` | ✅ | string | - | Paths permitidos: JSON array o comma-separated (`["*"]` o `*`) |
390
+ | `STORAGE_CLIENT_SECRET` | ✅ | string | - | Clave secreta compartida (mínimo 32 caracteres). **Debe coincidir con `CLIENT_SECRET` del servidor** |
391
+ | `STORAGE_TOTP_SECRET` | ✅ | string | - | Semilla TOTP compartida (mínimo 16 caracteres). **Debe coincidir con `TOTP_SECRET` del servidor** |
392
+ | `STORAGE_TOKEN_EXPIRES_IN` | ❌ | string | `5m` | Duración del token JWT (`30s`, `5m`, `1h`, `7d`) |
393
+ | `STORAGE_TOTP_PERIOD` | ❌ | number | `30` | Período TOTP en segundos (15-300). **Debe coincidir con `TOTP_PERIOD` del servidor** |
394
+ | `STORAGE_RATE_LIMIT_REQUESTS` | ❌ | number | - | Número de peticiones permitidas (requiere `STORAGE_RATE_LIMIT_WINDOW`) |
395
+ | `STORAGE_RATE_LIMIT_WINDOW` | ❌ | string | - | Ventana de tiempo para rate limit (`1h`, `30m`, `7d`) |
396
+
111
397
  ### Ejemplos de baseUrl
112
398
 
113
399
  ```typescript
114
400
  // Con dominio
115
- new StorageClient({ baseUrl: 'https://api.midominio.com', apiKey: 'sk_xxx' });
401
+ new StorageClient({
402
+ baseUrl: 'https://api.midominio.com',
403
+ clientConfig
404
+ });
116
405
 
117
406
  // Con IP directa
118
- new StorageClient({ baseUrl: 'http://192.168.1.100:3000', apiKey: 'sk_xxx' });
407
+ new StorageClient({
408
+ baseUrl: 'http://192.168.1.100:3000',
409
+ clientConfig
410
+ });
119
411
 
120
412
  // Con puerto personalizado
121
- new StorageClient({ baseUrl: 'http://localhost:8080', apiKey: 'sk_xxx' });
413
+ new StorageClient({
414
+ baseUrl: 'http://localhost:8080',
415
+ clientConfig
416
+ });
122
417
  ```
123
418
 
124
419
  ### Uso Completo
125
420
 
126
421
  ```typescript
127
- import { StorageClient } from 's3-client-dtb';
422
+ import 'dotenv/config'; // Cargar variables de entorno
423
+ import { createClientFromConfig } from 's3-client-dtb';
424
+ import {
425
+ FileNotFoundError,
426
+ UploadError,
427
+ StorageError
428
+ } from 's3-client-dtb';
128
429
  import { readFileSync } from 'fs';
129
430
 
130
- const client = new StorageClient({
131
- baseUrl: 'https://api.midominio.com',
132
- apiKey: 'sk_live_xxx'
133
- });
431
+ // Inicializar cliente desde variables de entorno
432
+ const client = createClientFromConfig();
134
433
 
135
- // ⚠️ IMPORTANTE: La API es IDÉNTICA a StorageCore
136
- const buffer = readFileSync('./mi-imagen.jpg');
137
- const path = await client.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
138
- const stream = await client.downloadFile(path);
139
- const metadata = await client.getFileMetadata(path);
140
- const { files, total } = await client.listFiles('images');
141
- await client.deleteFile(path);
142
- const exists = await client.fileExists(path);
434
+ // Opcional: Verificar que la configuración TOTP es correcta
435
+ try {
436
+ await client.verifyTotp();
437
+ console.log('✅ Cliente configurado correctamente');
438
+ } catch (error) {
439
+ console.error('❌ Error en configuración:', error);
440
+ process.exit(1);
441
+ }
442
+
443
+ // Ejemplo: Subir un archivo
444
+ try {
445
+ const buffer = readFileSync('./mi-imagen.jpg');
446
+ const path = await client.uploadFile(
447
+ 'images/photo.jpg', // Ruta donde se guardará
448
+ buffer, // Buffer del archivo
449
+ 'image/jpeg' // Tipo MIME
450
+ );
451
+ console.log('✅ Archivo subido en:', path);
452
+ } catch (error) {
453
+ if (error instanceof UploadError) {
454
+ console.error('❌ Error al subir:', error.message);
455
+ } else {
456
+ console.error('❌ Error inesperado:', error);
457
+ }
458
+ }
459
+
460
+ // Ejemplo: Descargar un archivo
461
+ try {
462
+ const stream = await client.downloadFile('images/photo.jpg');
463
+
464
+ // Convertir stream a buffer si es necesario
465
+ const chunks: Buffer[] = [];
466
+ for await (const chunk of stream) {
467
+ chunks.push(chunk);
468
+ }
469
+ const downloadedBuffer = Buffer.concat(chunks);
470
+ console.log('✅ Archivo descargado:', downloadedBuffer.length, 'bytes');
471
+ } catch (error) {
472
+ if (error instanceof FileNotFoundError) {
473
+ console.error('❌ Archivo no encontrado');
474
+ } else {
475
+ console.error('❌ Error al descargar:', error);
476
+ }
477
+ }
478
+
479
+ // Ejemplo: Obtener metadata
480
+ try {
481
+ const metadata = await client.getFileMetadata('images/photo.jpg');
482
+ console.log('Metadata:', {
483
+ nombre: metadata.name,
484
+ tamaño: metadata.size,
485
+ tipo: metadata.mimeType,
486
+ creado: metadata.createdAt
487
+ });
488
+ } catch (error) {
489
+ console.error('❌ Error al obtener metadata:', error);
490
+ }
491
+
492
+ // Ejemplo: Listar archivos
493
+ try {
494
+ const { files, total } = await client.listFiles('images', {
495
+ limit: 10, // Máximo 10 archivos
496
+ offset: 0, // Desde el inicio
497
+ prefix: 'photo' // Solo archivos que empiecen con "photo"
498
+ });
499
+ console.log(`✅ Encontrados ${files.length} de ${total} archivos`);
500
+ files.forEach(file => {
501
+ console.log(` - ${file.name} (${file.size} bytes)`);
502
+ });
503
+ } catch (error) {
504
+ console.error('❌ Error al listar archivos:', error);
505
+ }
506
+
507
+ // Ejemplo: Verificar existencia
508
+ const exists = await client.fileExists('images/photo.jpg');
509
+ console.log('Archivo existe:', exists);
510
+
511
+ // Ejemplo: Eliminar archivo
512
+ try {
513
+ await client.deleteFile('images/photo.jpg');
514
+ console.log('✅ Archivo eliminado');
515
+ } catch (error) {
516
+ if (error instanceof FileNotFoundError) {
517
+ console.error('❌ Archivo no encontrado para eliminar');
518
+ } else {
519
+ console.error('❌ Error al eliminar:', error);
520
+ }
521
+ }
522
+ ```
523
+
524
+ ### Gestión de Tokens
525
+
526
+ El cliente genera tokens automáticamente usando TOTP:
527
+
528
+ - **Generación automática**: El cliente genera tokens JWT usando `CLIENT_SECRET + TOTP_CODE`
529
+ - **TOTP dinámico**: El código TOTP cambia cada 30 segundos, proporcionando seguridad adicional
530
+ - **Renovación automática**: Los tokens se regeneran automáticamente cuando expiran o cuando cambia el código TOTP
531
+ - **Caché inteligente**: Los tokens se cachean y se regeneran solo cuando es necesario
532
+ - **Errores 401**: El cliente regenera el token automáticamente si recibe un 401
533
+
534
+ ```typescript
535
+ // El cliente siempre está listo para usar, genera tokens automáticamente
536
+ // No necesitas métodos de autenticación manual
143
537
  ```
144
538
 
145
539
  ---
@@ -151,20 +545,23 @@ Como ambos tienen la misma API, cambiar de modo es **súper simple**:
151
545
  ### Opción 1: Variable de Entorno
152
546
 
153
547
  ```typescript
154
- import { StorageCore, StorageClient } from 's3-client-dtb';
548
+ import { StorageCore, createClientFromConfig } from 's3-client-dtb';
155
549
 
156
550
  // Usar variable de entorno para decidir el modo
157
551
  const useRemote = process.env.STORAGE_MODE === 'remote';
158
552
 
159
553
  const storage = useRemote
160
- ? new StorageClient({
161
- baseUrl: process.env.STORAGE_URL!,
162
- apiKey: process.env.STORAGE_API_KEY!
163
- })
554
+ ? createClientFromConfig() // Lee variables de entorno automáticamente
164
555
  : new StorageCore({
165
556
  rootPath: process.env.STORAGE_PATH || './uploads'
166
557
  });
167
558
 
559
+ // Si es remoto, el cliente genera tokens automáticamente
560
+ // Opcional: Verificar TOTP
561
+ if (useRemote && storage instanceof StorageClient) {
562
+ await storage.verifyTotp();
563
+ }
564
+
168
565
  // El resto del código es idéntico
169
566
  await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
170
567
  ```
@@ -172,15 +569,18 @@ await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
172
569
  ### Opción 2: Factory Function
173
570
 
174
571
  ```typescript
175
- import { StorageCore, StorageClient } from 's3-client-dtb';
176
-
177
- function createStorage() {
178
- if (process.env.STORAGE_URL && process.env.STORAGE_API_KEY) {
179
- // Modo remoto
180
- return new StorageClient({
181
- baseUrl: process.env.STORAGE_URL,
182
- apiKey: process.env.STORAGE_API_KEY
183
- });
572
+ import { StorageCore, createClientFromConfig } from 's3-client-dtb';
573
+
574
+ async function createStorage() {
575
+ if (process.env.STORAGE_URL) {
576
+ // Modo remoto - usa variables de entorno
577
+ const client = createClientFromConfig();
578
+
579
+ // El cliente genera tokens automáticamente
580
+ // Opcional: Verificar TOTP
581
+ await client.verifyTotp();
582
+
583
+ return client;
184
584
  }
185
585
 
186
586
  // Modo local (por defecto)
@@ -189,35 +589,12 @@ function createStorage() {
189
589
  });
190
590
  }
191
591
 
192
- const storage = createStorage();
592
+ const storage = await createStorage();
193
593
  // Usar normalmente
194
594
  await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
195
595
  ```
196
596
 
197
- ### Opción 3: TypeScript con Interfaz Común
198
-
199
- ```typescript
200
- import { StorageCore, StorageClient } from 's3-client-dtb';
201
- import type { FileMetadata, ListFilesOptions } from 's3-client-dtb';
202
-
203
- // Interfaz común para ambos modos
204
- interface Storage {
205
- uploadFile(path: string, buffer: Buffer, mimeType: string, prefix?: string): Promise<string>;
206
- downloadFile(path: string): Promise<Readable>;
207
- deleteFile(path: string): Promise<void>;
208
- getFileMetadata(path: string): Promise<FileMetadata>;
209
- listFiles(directoryPath?: string, options?: ListFilesOptions): Promise<{files: FileMetadata[], total: number}>;
210
- fileExists(path: string): Promise<boolean>;
211
- }
212
-
213
- // Crear instancia según configuración
214
- const storage: Storage = process.env.STORAGE_MODE === 'remote'
215
- ? new StorageClient({ baseUrl: process.env.STORAGE_URL!, apiKey: process.env.STORAGE_API_KEY! })
216
- : new StorageCore({ rootPath: './uploads' });
217
-
218
- // Código tipo-seguro
219
- await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
220
- ```
597
+ ---
221
598
 
222
599
  ## Uso desde NestJS
223
600
 
@@ -282,6 +659,8 @@ export class FilesService {
282
659
  }
283
660
  ```
284
661
 
662
+ ---
663
+
285
664
  ## 📖 API Completa
286
665
 
287
666
  ### ⚡ Métodos Comunes (Ambos Modos)
@@ -325,84 +704,40 @@ interface StorageCoreOptions {
325
704
  #### StorageClient (Modo Remoto)
326
705
 
327
706
  ```typescript
707
+ // Opción 1: Desde variables de entorno (recomendado)
708
+ createClientFromConfig(): StorageClient
709
+
710
+ // Opción 2: Manual
328
711
  new StorageClient(options: StorageClientOptions)
329
712
 
330
713
  interface StorageClientOptions {
331
- baseUrl: string; // URL del backend: "https://api.com" o "http://192.168.1.100:3000"
332
- apiKey: string; // API key: "sk_live_xxx"
333
- timeout?: number; // Opcional: timeout en ms (default: 30000)
714
+ baseUrl: string; // URL del backend
715
+ clientConfig: ClientConfig; // Configuración del cliente
716
+ clientSecret: string; // Clave secreta compartida (mínimo 32 caracteres). Debe coincidir con CLIENT_SECRET del servidor
717
+ totpSecret: string; // Semilla TOTP compartida (mínimo 16 caracteres). Debe coincidir con TOTP_SECRET del servidor
718
+ totpPeriodSeconds?: number; // Opcional: Período TOTP en segundos (15-300, default: 30). Debe coincidir con TOTP_PERIOD del servidor
719
+ timeout?: number; // Opcional: timeout en ms (default: 30000)
334
720
  }
335
- ```
336
-
337
- **Nota:** El cliente automáticamente agrega el prefijo `/apifiles` a las URLs y maneja la autenticación con API keys.
338
-
339
- #### Constructor
340
721
 
341
- ```typescript
342
- new StorageCore(options: StorageCoreOptions)
722
+ interface ClientConfig {
723
+ clientId: string;
724
+ name: string;
725
+ permissions: ('read' | 'write' | 'delete')[];
726
+ allowedPaths: string[];
727
+ rateLimit?: {
728
+ requests: number;
729
+ window: string;
730
+ };
731
+ tokenExpiresIn?: string; // Opcional: "30m", "1h", "5m", "7d"
732
+ }
343
733
  ```
344
734
 
345
- **Opciones:**
346
- - `rootPath` (string, requerido): Ruta raíz donde se almacenarán los archivos
347
- - `maxFileSize` (string, opcional): Tamaño máximo de archivo (ej: "50mb", "1gb"). Por defecto: "50mb"
348
- - `allowedMimeTypes` (string[], opcional): Tipos MIME permitidos (ej: ["image/*", "video/*"]). Por defecto: ["*"]
349
-
350
- #### Métodos
351
-
352
- ##### `uploadFile(path, buffer, mimeType, prefix?)`
353
-
354
- Sube un archivo al almacenamiento.
355
-
356
- - `path` (string): Ruta relativa donde se guardará el archivo
357
- - `buffer` (Buffer): Contenido del archivo
358
- - `mimeType` (string): Tipo MIME del archivo
359
- - `prefix` (string, opcional): Prefijo para el nombre del archivo (se genera un UUID)
360
-
361
- **Retorna:** `Promise<string>` - Ruta relativa del archivo guardado
362
-
363
- ##### `downloadFile(path)`
364
-
365
- Descarga un archivo como stream.
366
-
367
- - `path` (string): Ruta relativa del archivo
368
-
369
- **Retorna:** `Promise<Readable>` - Stream de Node.js
370
-
371
- ##### `deleteFile(path)`
372
-
373
- Elimina un archivo.
374
-
375
- - `path` (string): Ruta relativa del archivo
376
-
377
- **Retorna:** `Promise<void>`
378
-
379
- ##### `getFileMetadata(path)`
380
-
381
- Obtiene metadata de un archivo.
382
-
383
- - `path` (string): Ruta relativa del archivo
384
-
385
- **Retorna:** `Promise<FileMetadata>`
386
-
387
- ##### `listFiles(directoryPath?, options?)`
388
-
389
- Lista archivos en un directorio.
390
-
391
- - `directoryPath` (string, opcional): Ruta del directorio (vacío para raíz)
392
- - `options` (ListFilesOptions, opcional):
393
- - `limit` (number): Número máximo de archivos
394
- - `offset` (number): Offset para paginación
395
- - `prefix` (string): Filtrar por prefijo en el nombre
396
-
397
- **Retorna:** `Promise<{ files: FileMetadata[], total: number }>`
398
-
399
- ##### `fileExists(path)`
400
-
401
- Verifica si un archivo existe.
735
+ **Nota:**
736
+ - El cliente automáticamente agrega el prefijo `/apifiles` a las URLs
737
+ - Genera tokens automáticamente usando `CLIENT_SECRET + TOTP` (no requiere autenticación manual)
738
+ - Regenera tokens automáticamente cuando expiran o cuando cambia el código TOTP
402
739
 
403
- - `path` (string): Ruta relativa del archivo
404
-
405
- **Retorna:** `Promise<boolean>`
740
+ ---
406
741
 
407
742
  ## Tipos
408
743
 
@@ -420,32 +755,41 @@ interface FileMetadata {
420
755
  }
421
756
  ```
422
757
 
758
+ ### ListFilesOptions
759
+
760
+ ```typescript
761
+ interface ListFilesOptions {
762
+ limit?: number;
763
+ offset?: number;
764
+ prefix?: string;
765
+ }
766
+ ```
767
+
768
+ ---
769
+
423
770
  ## 📋 Comparación Rápida
424
771
 
425
772
  | Característica | StorageCore (Local) | StorageClient (Remoto) |
426
773
  |---------------|---------------------|------------------------|
427
- | **Inicialización** | `new StorageCore({ rootPath })` | `new StorageClient({ baseUrl, apiKey })` |
774
+ | **Inicialización** | `new StorageCore({ rootPath })` | `createClientFromConfig()` o `new StorageClient({ baseUrl, clientConfig })` |
428
775
  | **Almacenamiento** | Sistema de archivos local | Backend remoto vía HTTP |
429
776
  | **API** | ✅ Idéntica | ✅ Idéntica |
430
- | **Autenticación** | ❌ No requiere | ✅ Requiere API key |
777
+ | **Autenticación** | ❌ No requiere | ✅ Genera tokens JWT con TOTP (automático) |
778
+ | **Configuración** | Opciones en constructor | Variables de entorno |
779
+ | **Tiempos de expiración** | ❌ No aplica | ✅ Configurables mediante variables de entorno |
431
780
  | **Ideal para** | Desarrollo, mismo servidor | Producción, múltiples apps |
432
- | **Configuración** | Solo ruta | URL + API key |
781
+
782
+ ---
433
783
 
434
784
  ## 💡 Recomendaciones
435
785
 
436
786
  - **Desarrollo**: Usa `StorageCore` con `rootPath: './uploads'`
437
- - **Producción**: Usa `StorageClient` con variables de entorno
787
+ - **Producción**: Usa `StorageClient` con variables de entorno y autenticación OTP
438
788
  - **Cambio fácil**: Ambos tienen la misma API, solo cambia la inicialización
789
+ - **Seguridad**: El cliente renueva tokens automáticamente según los tiempos configurados
790
+ - **Configuración**: Usa `createClientFromConfig()` para leer automáticamente las variables de entorno
439
791
 
440
- ### ListFilesOptions
441
-
442
- ```typescript
443
- interface ListFilesOptions {
444
- limit?: number;
445
- offset?: number;
446
- prefix?: string;
447
- }
448
- ```
792
+ ---
449
793
 
450
794
  ## Errores
451
795
 
@@ -459,6 +803,7 @@ El paquete lanza errores personalizados que extienden `StorageError`:
459
803
  - `DeleteError`: Error al eliminar archivo
460
804
  - `MetadataError`: Error al obtener metadata
461
805
  - `ListError`: Error al listar archivos
806
+ - `StorageError`: Error genérico
462
807
 
463
808
  Todos los errores tienen una propiedad `code` para identificación:
464
809
 
@@ -474,10 +819,49 @@ try {
474
819
 
475
820
  ## Seguridad
476
821
 
822
+ - **Autenticación JWT con OTP**: Sistema seguro de autenticación de dos factores
823
+ - **Renovación automática de tokens**: Configurable por cliente mediante variables de entorno
824
+ - **Validación en cadena**: Cada refresh token valida el anterior
477
825
  - **Sanitización de rutas**: Protección contra path traversal (`..`)
478
826
  - **Validación de caracteres**: Solo permite caracteres seguros en rutas
479
827
  - **Validación de MIME types**: Verifica tipos de archivo permitidos
480
828
  - **Límites de tamaño**: Previene archivos demasiado grandes
829
+ - **Permisos granulares**: Cada cliente tiene permisos específicos (read, write, delete)
830
+ - **Paths permitidos**: Cada cliente solo puede acceder a paths configurados
831
+
832
+ ## Ejemplo Completo con Variables de Entorno
833
+
834
+ 1. **Crear archivo `.env`:**
835
+ ```bash
836
+ STORAGE_CLIENT_ID=mi-app-prod
837
+ STORAGE_CLIENT_NAME=Mi Aplicacion
838
+ STORAGE_BASE_URL=http://localhost:3000
839
+ STORAGE_PERMISSIONS=["read","write","delete"]
840
+ STORAGE_ALLOWED_PATHS=["*"]
841
+ STORAGE_CLIENT_SECRET=tu-clave-secreta-compartida-minimo-32-caracteres
842
+ STORAGE_TOTP_SECRET=tu-semilla-totp-compartida-minimo-16-caracteres
843
+ STORAGE_TOKEN_EXPIRES_IN=5m
844
+ STORAGE_TOTP_PERIOD=30
845
+ STORAGE_RATE_LIMIT_REQUESTS=1000
846
+ STORAGE_RATE_LIMIT_WINDOW=1h
847
+ ```
848
+
849
+ 2. **Usar el cliente:**
850
+ ```typescript
851
+ import 'dotenv/config'; // Cargar variables de entorno
852
+ import { createClientFromConfig } from 's3-client-dtb';
853
+ import { readFileSync } from 'fs';
854
+
855
+ const client = createClientFromConfig(); // Lee variables de entorno
856
+
857
+ // El cliente genera tokens automáticamente
858
+ // Opcional: Verificar TOTP
859
+ await client.verifyTotp();
860
+
861
+ // Usar normalmente
862
+ const buffer = readFileSync('./imagen.jpg');
863
+ const path = await client.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
864
+ ```
481
865
 
482
866
  ## Licencia
483
867