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