@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.
- package/build/_models/control-models/error.control-model.d.ts.map +1 -1
- package/build/_models/control-models/error.control-model.js +3 -0
- package/build/_models/control-models/error.control-model.js.map +1 -1
- package/build/_modules/crypto/_collections/crypto.util.d.ts.map +1 -1
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.d.ts +2 -0
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.js +551 -0
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.js.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.d.ts +2 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.js +555 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.js.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.js +33 -3
- package/build/_modules/crypto/_collections/crypto.util.js.map +1 -1
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.d.ts +2 -0
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.js +429 -0
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.js.map +1 -0
- package/futdevpro-fsm-dynamo-01.14.21.tgz +0 -0
- package/package.json +2 -2
- package/src/_models/control-models/error.control-model.ts +4 -0
- package/src/_modules/crypto/_collections/crypto.util.edge.spec.ts +606 -0
- package/src/_modules/crypto/_collections/crypto.util.extra.spec.ts +643 -0
- package/src/_modules/crypto/_collections/crypto.util.simple.spec.ts +513 -0
- package/src/_modules/crypto/_collections/crypto.util.ts +81 -48
- package/build/_modules/crypto/_collections/crypto.util.spec.d.ts +0 -2
- package/build/_modules/crypto/_collections/crypto.util.spec.d.ts.map +0 -1
- package/build/_modules/crypto/_collections/crypto.util.spec.js +0 -1180
- package/build/_modules/crypto/_collections/crypto.util.spec.js.map +0 -1
- package/futdevpro-fsm-dynamo-01.14.19.tgz +0 -0
- 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(
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.util.spec.d.ts","sourceRoot":"","sources":["../../../../src/_modules/crypto/_collections/crypto.util.spec.ts"],"names":[],"mappings":""}
|