@futdevpro/fsm-dynamo 1.14.19 → 1.14.21

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.
Files changed (31) hide show
  1. package/build/_models/control-models/error.control-model.d.ts.map +1 -1
  2. package/build/_models/control-models/error.control-model.js +3 -0
  3. package/build/_models/control-models/error.control-model.js.map +1 -1
  4. package/build/_modules/crypto/_collections/crypto.util.d.ts.map +1 -1
  5. package/build/_modules/crypto/_collections/crypto.util.edge.spec.d.ts +2 -0
  6. package/build/_modules/crypto/_collections/crypto.util.edge.spec.d.ts.map +1 -0
  7. package/build/_modules/crypto/_collections/crypto.util.edge.spec.js +551 -0
  8. package/build/_modules/crypto/_collections/crypto.util.edge.spec.js.map +1 -0
  9. package/build/_modules/crypto/_collections/crypto.util.extra.spec.d.ts +2 -0
  10. package/build/_modules/crypto/_collections/crypto.util.extra.spec.d.ts.map +1 -0
  11. package/build/_modules/crypto/_collections/crypto.util.extra.spec.js +555 -0
  12. package/build/_modules/crypto/_collections/crypto.util.extra.spec.js.map +1 -0
  13. package/build/_modules/crypto/_collections/crypto.util.js +33 -3
  14. package/build/_modules/crypto/_collections/crypto.util.js.map +1 -1
  15. package/build/_modules/crypto/_collections/crypto.util.simple.spec.d.ts +2 -0
  16. package/build/_modules/crypto/_collections/crypto.util.simple.spec.d.ts.map +1 -0
  17. package/build/_modules/crypto/_collections/crypto.util.simple.spec.js +429 -0
  18. package/build/_modules/crypto/_collections/crypto.util.simple.spec.js.map +1 -0
  19. package/futdevpro-fsm-dynamo-01.14.21.tgz +0 -0
  20. package/package.json +2 -2
  21. package/src/_models/control-models/error.control-model.ts +4 -0
  22. package/src/_modules/crypto/_collections/crypto.util.edge.spec.ts +606 -0
  23. package/src/_modules/crypto/_collections/crypto.util.extra.spec.ts +643 -0
  24. package/src/_modules/crypto/_collections/crypto.util.simple.spec.ts +513 -0
  25. package/src/_modules/crypto/_collections/crypto.util.ts +81 -48
  26. package/build/_modules/crypto/_collections/crypto.util.spec.d.ts +0 -2
  27. package/build/_modules/crypto/_collections/crypto.util.spec.d.ts.map +0 -1
  28. package/build/_modules/crypto/_collections/crypto.util.spec.js +0 -1180
  29. package/build/_modules/crypto/_collections/crypto.util.spec.js.map +0 -1
  30. package/futdevpro-fsm-dynamo-01.14.19.tgz +0 -0
  31. package/src/_modules/crypto/_collections/crypto.util.spec.ts +0 -1370
@@ -35,14 +35,14 @@ export interface CryptoConfig {
35
35
  * systems is more important than cryptographic security.
36
36
  */
37
37
  export class DyFM_Crypto {
38
- private static readonly CRYPTO_VERSION = '1.0';
38
+ private static readonly CRYPTO_VERSION: string = '1.0';
39
39
  private static readonly DEFAULT_CONFIG: Required<CryptoConfig> = {
40
40
  ivLength: 16, // 128 bits
41
41
  saltLength: 16, // 128 bits
42
42
  keyIterations: 1000, // Reduced for better performance and stability
43
43
  keySize: 8 // 256 bits (8 * 32)
44
44
  };
45
- private static readonly defaultErrorUserMsg =
45
+ private static readonly defaultErrorUserMsg: string =
46
46
  `We encountered an unhandled Authentication Error, ` +
47
47
  `\nplease contact the responsible development team.`;
48
48
 
@@ -185,14 +185,14 @@ export class DyFM_Crypto {
185
185
  */
186
186
  private static generateIV(data: string, key: string, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
187
187
  // Create a deterministic seed from data and key
188
- const seed = this.createDeterministicSeed(data, key, 'IV');
188
+ const seed: string = this.createDeterministicSeed(data, key, 'IV');
189
189
 
190
190
  // Use SHA-256 for better stability and consistency
191
- const hash = CryptoJS.SHA256(seed);
191
+ const hash: CryptoJS.lib.WordArray = CryptoJS.SHA256(seed);
192
192
 
193
193
  // Extract exactly 16 bytes (128 bits) for IV
194
194
  // Use the first 4 words (4 * 4 = 16 bytes) from the hash
195
- const ivWords = hash.words.slice(0, 4);
195
+ const ivWords: number[] = hash.words.slice(0, 4);
196
196
  return CryptoJS.lib.WordArray.create(ivWords);
197
197
  }
198
198
 
@@ -205,14 +205,14 @@ export class DyFM_Crypto {
205
205
  */
206
206
  private static generateSalt(data: string, key: string, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
207
207
  // Create a deterministic seed from data and key (different from IV)
208
- const seed = this.createDeterministicSeed(data, key, 'SALT');
208
+ const seed: string = this.createDeterministicSeed(data, key, 'SALT');
209
209
 
210
210
  // Use SHA-256 for better stability and consistency
211
- const hash = CryptoJS.SHA256(seed);
211
+ const hash: CryptoJS.lib.WordArray = CryptoJS.SHA256(seed);
212
212
 
213
213
  // Extract exactly 16 bytes (128 bits) for salt
214
214
  // Use the first 4 words (4 * 4 = 16 bytes) from the hash
215
- const saltWords = hash.words.slice(0, 4);
215
+ const saltWords: number[] = hash.words.slice(0, 4);
216
216
  return CryptoJS.lib.WordArray.create(saltWords);
217
217
  }
218
218
 
@@ -223,7 +223,7 @@ export class DyFM_Crypto {
223
223
  private static createDeterministicSeed(data: string, key: string, purpose: string): string {
224
224
  // Create a consistent seed that includes all relevant factors
225
225
  // Order matters: data + key + purpose for consistency
226
- const seed = `${data}|${key}|${purpose}`;
226
+ const seed: string = `${data}|${key}|${purpose}`;
227
227
  return seed;
228
228
  }
229
229
 
@@ -274,17 +274,17 @@ export class DyFM_Crypto {
274
274
  if (typeof obj === 'object') {
275
275
  // For objects, we need to be more careful about key ordering
276
276
  // Use a stable sort that preserves original order when possible
277
- const keys = Object.keys(obj);
277
+ const keys: string[] = Object.keys(obj);
278
278
 
279
279
  // Only sort if there are potential ordering issues
280
- const needsSorting = keys.some((key, index) => {
280
+ const needsSorting: boolean = keys.some((key, index) => {
281
281
  if (index === 0) return false;
282
282
  return key < keys[index - 1];
283
283
  });
284
284
 
285
- const sortedKeys = needsSorting ? [...keys].sort() : keys;
286
- const pairs = sortedKeys.map(key => {
287
- const value = this.deterministicStringify(obj[key]);
285
+ const sortedKeys: string[] = needsSorting ? [...keys].sort() : keys;
286
+ const pairs: string[] = sortedKeys.map(key => {
287
+ const value: string = this.deterministicStringify(obj[key]);
288
288
  return JSON.stringify(key) + ':' + value;
289
289
  });
290
290
  return '{' + pairs.join(',') + '}';
@@ -313,14 +313,14 @@ export class DyFM_Crypto {
313
313
  }
314
314
 
315
315
  //let parsed = JSON.parse(data);
316
- let parsed = DyFM_Object.failableSafeParseJSON(data);
316
+ let parsed: T = DyFM_Object.failableSafeParseJSON(data);
317
317
 
318
318
  // Handle double-stringified JSON (or more levels of stringification)
319
- let maxAttempts = 3; // Prevent infinite loops
319
+ let maxAttempts: number = 3; // Prevent infinite loops
320
320
  while (typeof parsed === 'string' && maxAttempts > 0) {
321
321
  try {
322
322
  //const nextParsed = JSON.parse(parsed);
323
- const nextParsed = DyFM_Object.failableSafeParseJSON(parsed);
323
+ const nextParsed: T = DyFM_Object.failableSafeParseJSON(parsed);
324
324
  // Only continue if parsing actually changed the result
325
325
  if (nextParsed !== parsed) {
326
326
  parsed = nextParsed;
@@ -376,27 +376,27 @@ export class DyFM_Crypto {
376
376
  static encrypt<T>(data: T, key: string, config?: CryptoConfig): string {
377
377
  try {
378
378
  this.validateInput(data, key, 'encrypt');
379
- const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
379
+ const finalConfig: Required<CryptoConfig> = { ...this.DEFAULT_CONFIG, ...config };
380
380
 
381
381
  // Convert data to string
382
- const dataStr = this.safeSerialize(data);
382
+ const dataStr: string = this.safeSerialize(data);
383
383
 
384
384
  // Generate deterministic IV and salt based on data and key
385
- const iv = this.generateIV(dataStr, key, finalConfig);
386
- const salt = this.generateSalt(dataStr, key, finalConfig);
385
+ const iv: CryptoJS.lib.WordArray = this.generateIV(dataStr, key, finalConfig);
386
+ const salt: CryptoJS.lib.WordArray = this.generateSalt(dataStr, key, finalConfig);
387
387
 
388
388
  // Derive key using PBKDF2
389
- const derivedKey = this.deriveKey(key, salt, finalConfig);
389
+ const derivedKey: CryptoJS.lib.WordArray = this.deriveKey(key, salt, finalConfig);
390
390
 
391
391
  // Encrypt the data
392
- const encrypted = CryptoJS.AES.encrypt(dataStr, derivedKey, {
392
+ const encrypted: CryptoJS.lib.WordArray = CryptoJS.AES.encrypt(dataStr, derivedKey, {
393
393
  iv: iv,
394
394
  mode: CryptoJS.mode.CBC,
395
395
  padding: CryptoJS.pad.Pkcs7
396
396
  });
397
397
 
398
398
  // Combine IV + Salt + Ciphertext (skip version for backward compatibility)
399
- const combined = iv.concat(salt).concat(encrypted.ciphertext);
399
+ const combined: CryptoJS.lib.WordArray = iv.concat(salt).concat(encrypted.ciphertext);
400
400
 
401
401
  // Convert to URL-safe base64
402
402
  return CryptoJS.enc.Base64.stringify(combined)
@@ -422,43 +422,66 @@ export class DyFM_Crypto {
422
422
  static decrypt<T>(encryptedData: string, key: string, config?: CryptoConfig): T {
423
423
  try {
424
424
  this.validateInput(encryptedData, key, 'decrypt');
425
- const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
425
+ const finalConfig: Required<CryptoConfig> = { ...this.DEFAULT_CONFIG, ...config };
426
426
 
427
427
  // Convert from URL-safe base64
428
- const base64 = encryptedData
428
+ const base64: string = encryptedData
429
429
  .replace(/-/g, '+')
430
430
  .replace(/_/g, '/');
431
431
 
432
+ // Add padding if needed (base64 must be multiple of 4 characters)
433
+ // This ensures CryptoJS.parse works correctly even when padding was stripped during URL transmission
434
+ const paddingNeeded: number = (4 - (base64.length % 4)) % 4;
435
+ const paddedBase64: string = base64 + '='.repeat(paddingNeeded);
436
+
437
+ // Validate base64 format before parsing
438
+ // Check if the string length makes sense for expected minimum byte count
439
+ // Minimum expected: 48 bytes = ~64 base64 characters (48 * 4/3 = 64)
440
+ const minExpectedBase64Length: number = Math.ceil((finalConfig.ivLength + finalConfig.saltLength + 16) * 4 / 3);
441
+ if (paddedBase64.length < minExpectedBase64Length) {
442
+ throw new DyFM_Error({
443
+ ...this.getDefaultErrorSettings('decrypt'),
444
+ errorCode: 'DyFM-CRY-DATA-CORRUPTED',
445
+ message: `Encrypted data is too short. Expected at least ${minExpectedBase64Length} base64 characters ` +
446
+ `(for ${(finalConfig.ivLength + finalConfig.saltLength + 16)} bytes), ` +
447
+ `but received ${paddedBase64.length} characters (${encryptedData.length} original). ` +
448
+ `This may indicate the data was truncated during transmission or storage.`
449
+ });
450
+ }
451
+
432
452
  // Parse the combined data
433
- const combined = CryptoJS.enc.Base64.parse(base64);
453
+ const combined: CryptoJS.lib.WordArray = CryptoJS.enc.Base64.parse(paddedBase64);
434
454
 
435
455
  // For now, skip version checking to maintain backward compatibility
436
456
  // TODO: Implement proper version checking in future versions
437
457
 
438
458
  // Validate minimum length (IV + Salt + minimum ciphertext)
439
- const minLength = (finalConfig.ivLength + finalConfig.saltLength + 16) / 4; // 16 bytes minimum for ciphertext
459
+ const minLength: number = (finalConfig.ivLength + finalConfig.saltLength + 16) / 4; // 16 bytes minimum for ciphertext
440
460
  if (combined.words.length < minLength) {
441
461
  throw new DyFM_Error({
442
462
  ...this.getDefaultErrorSettings('decrypt'),
443
463
  errorCode: 'DyFM-CRY-DATA-CORRUPTED',
444
- message: `Encrypted data is corrupted or incomplete. Expected at least ${minLength * 4} bytes, but received ${combined.sigBytes} bytes.`
464
+ message: `Encrypted data is corrupted or incomplete. Expected at least ${minLength * 4} bytes, but received ${combined.sigBytes} bytes. ` +
465
+ `Original string length: ${encryptedData.length} characters. ` +
466
+ `Base64 length: ${base64.length} characters (${paddedBase64.length} with padding). ` +
467
+ `This may indicate the data was truncated during transmission or storage.`
445
468
  });
446
469
  }
447
470
 
448
471
  // Extract IV, salt, and ciphertext (skip version for now)
449
- const ivStart = 0;
450
- const saltStart = ivStart + finalConfig.ivLength / 4;
451
- const cipherStart = saltStart + finalConfig.saltLength / 4;
472
+ const ivStart: number = 0;
473
+ const saltStart: number = ivStart + finalConfig.ivLength / 4;
474
+ const cipherStart: number = saltStart + finalConfig.saltLength / 4;
452
475
 
453
- const iv = CryptoJS.lib.WordArray.create(combined.words.slice(ivStart, saltStart));
454
- const salt = CryptoJS.lib.WordArray.create(combined.words.slice(saltStart, cipherStart));
455
- const ciphertext = CryptoJS.lib.WordArray.create(combined.words.slice(cipherStart));
476
+ const iv: CryptoJS.lib.WordArray = CryptoJS.lib.WordArray.create(combined.words.slice(ivStart, saltStart));
477
+ const salt: CryptoJS.lib.WordArray = CryptoJS.lib.WordArray.create(combined.words.slice(saltStart, cipherStart));
478
+ const ciphertext: CryptoJS.lib.WordArray = CryptoJS.lib.WordArray.create(combined.words.slice(cipherStart));
456
479
 
457
480
  // Derive key using PBKDF2
458
- const derivedKey = this.deriveKey(key, salt, finalConfig);
481
+ const derivedKey: CryptoJS.lib.WordArray = this.deriveKey(key, salt, finalConfig);
459
482
 
460
483
  // Decrypt the data
461
- const decrypted = CryptoJS.AES.decrypt(
484
+ const decrypted: CryptoJS.lib.WordArray = CryptoJS.AES.decrypt(
462
485
  { ciphertext: ciphertext },
463
486
  derivedKey,
464
487
  {
@@ -469,7 +492,7 @@ export class DyFM_Crypto {
469
492
  );
470
493
 
471
494
  // Parse JSON
472
- const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
495
+ const decryptedStr: string = decrypted.toString(CryptoJS.enc.Utf8);
473
496
 
474
497
  // Check if decryption produced empty result
475
498
  if (!decryptedStr || decryptedStr.trim().length === 0) {
@@ -522,15 +545,15 @@ export class DyFM_Crypto {
522
545
  */
523
546
  static generateKey(length: number = 32, customChars?: string): string {
524
547
  // Use custom character set if provided, otherwise use simple safe characters
525
- const chars = customChars || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
526
- let complexKey = '';
548
+ const chars: string = customChars || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
549
+ let complexKey: string = '';
527
550
 
528
551
  // Generate random characters directly for the desired length
529
552
  for (let i = 0; i < length; i++) {
530
553
  // Generate random bytes for each character
531
- const randomBytes = CryptoJS.lib.WordArray.random(1);
532
- const randomValue = randomBytes.words[0];
533
- const charIndex = Math.abs(randomValue) % chars.length;
554
+ const randomBytes: CryptoJS.lib.WordArray = CryptoJS.lib.WordArray.random(1);
555
+ const randomValue: number = randomBytes.words[0];
556
+ const charIndex: number = Math.abs(randomValue) % chars.length;
534
557
  complexKey += chars[charIndex];
535
558
  }
536
559
 
@@ -572,15 +595,15 @@ export class DyFM_Crypto {
572
595
  }
573
596
 
574
597
  // Convert from URL-safe base64
575
- const base64 = encryptedData
598
+ const base64: string = encryptedData
576
599
  .replace(/-/g, '+')
577
600
  .replace(/_/g, '/');
578
601
 
579
602
  // Parse the combined data
580
- const combined = CryptoJS.enc.Base64.parse(base64);
603
+ const combined: CryptoJS.lib.WordArray = CryptoJS.enc.Base64.parse(base64);
581
604
 
582
605
  // For now, just check if the data has minimum required length
583
- const minLength = (16 + 16 + 16) / 4; // IV + Salt + minimum ciphertext
606
+ const minLength: number = (16 + 16 + 16) / 4; // IV + Salt + minimum ciphertext
584
607
 
585
608
  if (combined.words.length < minLength) {
586
609
  return {
@@ -612,7 +635,7 @@ export class DyFM_Crypto {
612
635
  * Gets default error settings with enhanced debugging information
613
636
  */
614
637
  private static getDefaultErrorSettings(operation: string, error?: any): DyFM_Error_Settings {
615
- const baseSettings = {
638
+ const baseSettings: DyFM_Error_Settings = {
616
639
  status: (error as DyFM_Error)?.___status ?? (error as any)?.status ?? 401,
617
640
  message: `Crypto operation "${operation}" failed.`,
618
641
  error: error,
@@ -625,7 +648,17 @@ export class DyFM_Crypto {
625
648
  '\n 1) Wrong encryption key, ' +
626
649
  '\n 2) Corrupted encrypted data, ' +
627
650
  '\n 3) Version incompatibility, ' +
628
- '\n 4) Data was encrypted with different parameters.';
651
+ '\n 4) Data was encrypted with different parameters, ' +
652
+ '\n 5) Data truncation during transmission (check HTTP header size limits, URL length limits), ' +
653
+ '\n 6) Missing base64 padding (should be automatically handled, but may indicate transmission issues).';
654
+
655
+ // Add specific guidance for truncation issues (like the "18 bytes" error)
656
+ baseSettings.message += '\n\nFor truncation issues (e.g., "received 18 bytes"): ' +
657
+ '\n - Check if data is being truncated in HTTP headers (Nginx default: 4-8KB, configurable), ' +
658
+ '\n - Verify URL parameter length limits if passed via query string, ' +
659
+ '\n - Check database field size limits if stored, ' +
660
+ '\n - Ensure proxy/load balancer header size limits are sufficient, ' +
661
+ '\n - Verify the encrypted data string length matches expected size before decryption.';
629
662
  } else if (operation === 'encrypt') {
630
663
  baseSettings.message += '\nThis usually indicates: ' +
631
664
  '\n 1) Invalid input data, ' +
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=crypto.util.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"crypto.util.spec.d.ts","sourceRoot":"","sources":["../../../../src/_modules/crypto/_collections/crypto.util.spec.ts"],"names":[],"mappings":""}