@futdevpro/fsm-dynamo 1.14.20 → 1.14.22

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