@xbg.solutions/utils-hashing 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.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Encrypted Fields Registry
3
+ *
4
+ * Central registry of all PII fields that must be encrypted at rest.
5
+ * All fields use AES-256-GCM for authenticated encryption.
6
+ */
7
+ export declare const HASHED_FIELDS: {
8
+ readonly 'user.email': true;
9
+ readonly 'user.phoneNumber': true;
10
+ };
11
+ export type HashedFieldPath = keyof typeof HASHED_FIELDS;
12
+ /**
13
+ * Check if a field path should be hashed
14
+ */
15
+ export declare function isHashedField(fieldPath: string): boolean;
16
+ //# sourceMappingURL=hashed-fields-lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashed-fields-lookup.d.ts","sourceRoot":"","sources":["../src/hashed-fields-lookup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,aAAa;;;CAQhB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,aAAa,CAAC;AAEzD;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAExD"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Encrypted Fields Registry
4
+ *
5
+ * Central registry of all PII fields that must be encrypted at rest.
6
+ * All fields use AES-256-GCM for authenticated encryption.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.HASHED_FIELDS = void 0;
10
+ exports.isHashedField = isHashedField;
11
+ exports.HASHED_FIELDS = {
12
+ // User PII
13
+ 'user.email': true,
14
+ 'user.phoneNumber': true,
15
+ // Add project-specific PII fields here, e.g.:
16
+ // 'customer.email': true,
17
+ // 'address.addressLine1': true,
18
+ };
19
+ /**
20
+ * Check if a field path should be hashed
21
+ */
22
+ function isHashedField(fieldPath) {
23
+ return fieldPath in exports.HASHED_FIELDS;
24
+ }
25
+ //# sourceMappingURL=hashed-fields-lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashed-fields-lookup.js","sourceRoot":"","sources":["../src/hashed-fields-lookup.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAiBH,sCAEC;AAjBY,QAAA,aAAa,GAAG;IAC3B,WAAW;IACX,YAAY,EAAE,IAAI;IAClB,kBAAkB,EAAE,IAAI;IAExB,8CAA8C;IAC9C,0BAA0B;IAC1B,gCAAgC;CACxB,CAAC;AAIX;;GAEG;AACH,SAAgB,aAAa,CAAC,SAAiB;IAC7C,OAAO,SAAS,IAAI,qBAAa,CAAC;AACpC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * PII Encryption Utilities
3
+ *
4
+ * Encrypt PII fields before storage using AES-256-GCM.
5
+ * AES-256-GCM provides authenticated encryption with reversible decryption.
6
+ *
7
+ * Encrypted format: `${iv}:${encrypted}:${authTag}` (all base64 encoded)
8
+ */
9
+ /**
10
+ * Encrypt a single value using AES-256-GCM
11
+ *
12
+ * @param value - Plaintext value to encrypt
13
+ * @returns Encrypted value in format: `${iv}:${encrypted}:${authTag}` (base64)
14
+ */
15
+ export declare function hashValue(value: string): string;
16
+ /**
17
+ * Hash all PII fields in an object based on entity type
18
+ *
19
+ * @param data - Object containing fields to hash
20
+ * @param entityType - Type of entity ('user', 'contact', 'address')
21
+ * @returns New object with hashed fields
22
+ *
23
+ * @example
24
+ * hashFields({ email: 'test@example.com' }, 'user')
25
+ * // Returns { email: '$2b$10$...' }
26
+ */
27
+ export declare function hashFields<T extends Record<string, unknown>>(data: T, entityType: 'user' | 'contact' | 'address'): T;
28
+ //# sourceMappingURL=hasher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasher.d.ts","sourceRoot":"","sources":["../src/hasher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmCH;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkB/C;AAGD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1D,IAAI,EAAE,CAAC,EACP,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GACzC,CAAC,CAkBH"}
package/lib/hasher.js ADDED
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * PII Encryption Utilities
4
+ *
5
+ * Encrypt PII fields before storage using AES-256-GCM.
6
+ * AES-256-GCM provides authenticated encryption with reversible decryption.
7
+ *
8
+ * Encrypted format: `${iv}:${encrypted}:${authTag}` (all base64 encoded)
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.hashValue = hashValue;
45
+ exports.hashFields = hashFields;
46
+ const crypto = __importStar(require("crypto"));
47
+ const hashed_fields_lookup_1 = require("./hashed-fields-lookup");
48
+ // AES-256-GCM configuration
49
+ const ALGORITHM = 'aes-256-gcm';
50
+ const IV_LENGTH = 12; // 96 bits for GCM
51
+ const KEY_LENGTH = 32; // 256 bits
52
+ /**
53
+ * Get encryption key from environment
54
+ * Key must be 32 bytes (64 hex characters) for AES-256
55
+ */
56
+ function getEncryptionKey() {
57
+ const keyHex = process.env.PII_ENCRYPTION_KEY;
58
+ if (!keyHex) {
59
+ throw new Error('PII_ENCRYPTION_KEY not found in environment. ' +
60
+ 'Generate with: openssl rand -hex 32');
61
+ }
62
+ // Validate key length
63
+ if (keyHex.length !== KEY_LENGTH * 2) {
64
+ throw new Error(`PII_ENCRYPTION_KEY must be ${KEY_LENGTH * 2} hex characters (${KEY_LENGTH} bytes). ` +
65
+ `Current length: ${keyHex.length}`);
66
+ }
67
+ return Buffer.from(keyHex, 'hex');
68
+ }
69
+ /**
70
+ * Encrypt a single value using AES-256-GCM
71
+ *
72
+ * @param value - Plaintext value to encrypt
73
+ * @returns Encrypted value in format: `${iv}:${encrypted}:${authTag}` (base64)
74
+ */
75
+ function hashValue(value) {
76
+ const key = getEncryptionKey();
77
+ // Generate random IV (initialization vector)
78
+ const iv = crypto.randomBytes(IV_LENGTH);
79
+ // Create cipher
80
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
81
+ // Encrypt the value
82
+ let encrypted = cipher.update(value, 'utf8', 'base64');
83
+ encrypted += cipher.final('base64');
84
+ // Get authentication tag
85
+ const authTag = cipher.getAuthTag();
86
+ // Return in format: iv:encrypted:authTag (all base64)
87
+ return `${iv.toString('base64')}:${encrypted}:${authTag.toString('base64')}`;
88
+ }
89
+ /**
90
+ * Hash all PII fields in an object based on entity type
91
+ *
92
+ * @param data - Object containing fields to hash
93
+ * @param entityType - Type of entity ('user', 'contact', 'address')
94
+ * @returns New object with hashed fields
95
+ *
96
+ * @example
97
+ * hashFields({ email: 'test@example.com' }, 'user')
98
+ * // Returns { email: '$2b$10$...' }
99
+ */
100
+ function hashFields(data, entityType) {
101
+ const result = Object.assign({}, data);
102
+ // Iterate through top-level fields and check if they should be hashed
103
+ for (const fieldName of Object.keys(result)) {
104
+ const fieldPath = `${entityType}.${fieldName}`;
105
+ if ((0, hashed_fields_lookup_1.isHashedField)(fieldPath)) {
106
+ const value = result[fieldName];
107
+ // Only hash non-null, non-empty strings
108
+ if (value && typeof value === 'string' && value.length > 0) {
109
+ result[fieldName] = hashValue(value);
110
+ }
111
+ }
112
+ }
113
+ return result;
114
+ }
115
+ //# sourceMappingURL=hasher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasher.js","sourceRoot":"","sources":["../src/hasher.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCH,8BAkBC;AAcD,gCAqBC;AA5FD,+CAAiC;AACjC,iEAAuD;AAEvD,4BAA4B;AAC5B,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC,kBAAkB;AACxC,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC,WAAW;AAElC;;;GAGG;AACH,SAAS,gBAAgB;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,+CAA+C;YAC/C,qCAAqC,CACtC,CAAC;IACJ,CAAC;IAED,sBAAsB;IACtB,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,8BAA8B,UAAU,GAAG,CAAC,oBAAoB,UAAU,WAAW;YACrF,mBAAmB,MAAM,CAAC,MAAM,EAAE,CACnC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,KAAa;IACrC,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAE/B,6CAA6C;IAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAEzC,gBAAgB;IAChB,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAEzD,oBAAoB;IACpB,IAAI,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvD,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAEpC,yBAAyB;IACzB,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAEpC,sDAAsD;IACtD,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC/E,CAAC;AAGD;;;;;;;;;;GAUG;AACH,SAAgB,UAAU,CACxB,IAAO,EACP,UAA0C;IAE1C,MAAM,MAAM,qBAAQ,IAAI,CAAE,CAAC;IAE3B,sEAAsE;IACtE,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,GAAG,UAAU,IAAI,SAAS,EAAE,CAAC;QAE/C,IAAI,IAAA,oCAAa,EAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;YAEhC,wCAAwC;YACxC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1D,MAAkC,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Hashing utilities barrel export
3
+ */
4
+ export { HASHED_FIELDS, isHashedField } from './hashed-fields-lookup';
5
+ export type { HashedFieldPath } from './hashed-fields-lookup';
6
+ export { hashValue, hashFields } from './hasher';
7
+ export { unhashValue, unhashFields } from './unhashing';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * Hashing utilities barrel export
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.unhashFields = exports.unhashValue = exports.hashFields = exports.hashValue = exports.isHashedField = exports.HASHED_FIELDS = void 0;
7
+ var hashed_fields_lookup_1 = require("./hashed-fields-lookup");
8
+ Object.defineProperty(exports, "HASHED_FIELDS", { enumerable: true, get: function () { return hashed_fields_lookup_1.HASHED_FIELDS; } });
9
+ Object.defineProperty(exports, "isHashedField", { enumerable: true, get: function () { return hashed_fields_lookup_1.isHashedField; } });
10
+ var hasher_1 = require("./hasher");
11
+ Object.defineProperty(exports, "hashValue", { enumerable: true, get: function () { return hasher_1.hashValue; } });
12
+ Object.defineProperty(exports, "hashFields", { enumerable: true, get: function () { return hasher_1.hashFields; } });
13
+ var unhashing_1 = require("./unhashing");
14
+ Object.defineProperty(exports, "unhashValue", { enumerable: true, get: function () { return unhashing_1.unhashValue; } });
15
+ Object.defineProperty(exports, "unhashFields", { enumerable: true, get: function () { return unhashing_1.unhashFields; } });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,+DAAsE;AAA7D,qHAAA,aAAa,OAAA;AAAE,qHAAA,aAAa,OAAA;AAErC,mCAAiD;AAAxC,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAC9B,yCAAwD;AAA/C,wGAAA,WAAW,OAAA;AAAE,yGAAA,YAAY,OAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * PII Decryption Utilities (Lazy Pattern)
3
+ *
4
+ * Decrypt PII fields on-demand when explicitly requested.
5
+ * Uses AES-256-GCM for authenticated decryption.
6
+ *
7
+ * Encrypted format: `${iv}:${encrypted}:${authTag}` (all base64 encoded)
8
+ */
9
+ /**
10
+ * Decrypt a single value using AES-256-GCM
11
+ *
12
+ * @param encryptedValue - Encrypted value in format: `${iv}:${encrypted}:${authTag}` (base64)
13
+ * @returns Decrypted plaintext value
14
+ * @throws Error if decryption fails or format is invalid
15
+ */
16
+ export declare function unhashValue(value: string): string;
17
+ /**
18
+ * Unhash specific fields in an object (lazy pattern)
19
+ *
20
+ * Services explicitly request which fields to unhash.
21
+ * No automatic unhashing on every GET request.
22
+ *
23
+ * @param data - Object containing hashed fields
24
+ * @param fieldsToUnhash - Array of field paths to unhash (e.g., ['contact.email'])
25
+ * @returns New object with unhashed fields
26
+ *
27
+ * @example
28
+ * unhashFields(contactData, ['contact.email', 'contact.phoneNumber'])
29
+ */
30
+ export declare function unhashFields<T extends Record<string, unknown>>(data: T, fieldsToUnhash: string[]): T;
31
+ //# sourceMappingURL=unhashing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unhashing.d.ts","sourceRoot":"","sources":["../src/unhashing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA2CH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgCjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,IAAI,EAAE,CAAC,EACP,cAAc,EAAE,MAAM,EAAE,GACvB,CAAC,CA0BH"}
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * PII Decryption Utilities (Lazy Pattern)
4
+ *
5
+ * Decrypt PII fields on-demand when explicitly requested.
6
+ * Uses AES-256-GCM for authenticated decryption.
7
+ *
8
+ * Encrypted format: `${iv}:${encrypted}:${authTag}` (all base64 encoded)
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.unhashValue = unhashValue;
45
+ exports.unhashFields = unhashFields;
46
+ const crypto = __importStar(require("crypto"));
47
+ const hashed_fields_lookup_1 = require("./hashed-fields-lookup");
48
+ // AES-256-GCM configuration
49
+ const ALGORITHM = 'aes-256-gcm';
50
+ const KEY_LENGTH = 32; // 256 bits
51
+ /**
52
+ * Get encryption key from environment
53
+ * Key must be 32 bytes (64 hex characters) for AES-256
54
+ */
55
+ function getEncryptionKey() {
56
+ const keyHex = process.env.PII_ENCRYPTION_KEY;
57
+ if (!keyHex) {
58
+ throw new Error('PII_ENCRYPTION_KEY not found in environment. ' +
59
+ 'Generate with: openssl rand -hex 32');
60
+ }
61
+ // Validate key length
62
+ if (keyHex.length !== KEY_LENGTH * 2) {
63
+ throw new Error(`PII_ENCRYPTION_KEY must be ${KEY_LENGTH * 2} hex characters (${KEY_LENGTH} bytes). ` +
64
+ `Current length: ${keyHex.length}`);
65
+ }
66
+ return Buffer.from(keyHex, 'hex');
67
+ }
68
+ /**
69
+ * Check if a value appears to be AES-256-GCM encrypted
70
+ * Format: iv:encrypted:authTag (base64 strings separated by colons)
71
+ */
72
+ function isEncrypted(value) {
73
+ const parts = value.split(':');
74
+ return parts.length === 3 && parts.every(part => part.length > 0);
75
+ }
76
+ /**
77
+ * Decrypt a single value using AES-256-GCM
78
+ *
79
+ * @param encryptedValue - Encrypted value in format: `${iv}:${encrypted}:${authTag}` (base64)
80
+ * @returns Decrypted plaintext value
81
+ * @throws Error if decryption fails or format is invalid
82
+ */
83
+ function unhashValue(value) {
84
+ // Validate format
85
+ if (!isEncrypted(value)) {
86
+ throw new Error('Invalid encrypted value format. ' +
87
+ 'Expected: iv:encrypted:authTag (base64)');
88
+ }
89
+ const key = getEncryptionKey();
90
+ // Parse encrypted value components
91
+ const [ivBase64, encryptedBase64, authTagBase64] = value.split(':');
92
+ const iv = Buffer.from(ivBase64, 'base64');
93
+ const authTag = Buffer.from(authTagBase64, 'base64');
94
+ // Create decipher
95
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
96
+ decipher.setAuthTag(authTag);
97
+ try {
98
+ // Decrypt the value
99
+ let decrypted = decipher.update(encryptedBase64, 'base64', 'utf8');
100
+ decrypted += decipher.final('utf8');
101
+ return decrypted;
102
+ }
103
+ catch (error) {
104
+ throw new Error('Decryption failed. This may indicate data corruption or wrong encryption key. ' +
105
+ `Original error: ${error instanceof Error ? error.message : String(error)}`);
106
+ }
107
+ }
108
+ /**
109
+ * Unhash specific fields in an object (lazy pattern)
110
+ *
111
+ * Services explicitly request which fields to unhash.
112
+ * No automatic unhashing on every GET request.
113
+ *
114
+ * @param data - Object containing hashed fields
115
+ * @param fieldsToUnhash - Array of field paths to unhash (e.g., ['contact.email'])
116
+ * @returns New object with unhashed fields
117
+ *
118
+ * @example
119
+ * unhashFields(contactData, ['contact.email', 'contact.phoneNumber'])
120
+ */
121
+ function unhashFields(data, fieldsToUnhash) {
122
+ const result = Object.assign({}, data);
123
+ for (const fieldPath of fieldsToUnhash) {
124
+ // Validate that this field is actually an encrypted field
125
+ if (!(0, hashed_fields_lookup_1.isHashedField)(fieldPath)) {
126
+ continue;
127
+ }
128
+ // Extract entity type and field name from path
129
+ // e.g., 'contact.email' -> entityType='contact', fieldName='email'
130
+ const parts = fieldPath.split('.');
131
+ if (parts.length !== 2) {
132
+ continue;
133
+ }
134
+ const fieldName = parts[1];
135
+ const value = result[fieldName];
136
+ // Only attempt to decrypt string values that appear to be encrypted
137
+ if (value && typeof value === 'string' && isEncrypted(value)) {
138
+ result[fieldName] = unhashValue(value);
139
+ }
140
+ }
141
+ return result;
142
+ }
143
+ //# sourceMappingURL=unhashing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unhashing.js","sourceRoot":"","sources":["../src/unhashing.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDH,kCAgCC;AAeD,oCA6BC;AA5HD,+CAAiC;AACjC,iEAAuD;AAEvD,4BAA4B;AAC5B,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC,WAAW;AAElC;;;GAGG;AACH,SAAS,gBAAgB;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,+CAA+C;YAC/C,qCAAqC,CACtC,CAAC;IACJ,CAAC;IAED,sBAAsB;IACtB,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,8BAA8B,UAAU,GAAG,CAAC,oBAAoB,UAAU,WAAW;YACrF,mBAAmB,MAAM,CAAC,MAAM,EAAE,CACnC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,KAAa;IACvC,kBAAkB;IAClB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,kCAAkC;YAClC,yCAAyC,CAC1C,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAE/B,mCAAmC;IACnC,MAAM,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpE,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAErD,kBAAkB;IAClB,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7D,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAE7B,IAAI,CAAC;QACH,oBAAoB;QACpB,IAAI,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnE,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAEpC,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,gFAAgF;YAChF,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC5E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,YAAY,CAC1B,IAAO,EACP,cAAwB;IAExB,MAAM,MAAM,qBAAQ,IAAI,CAAE,CAAC;IAE3B,KAAK,MAAM,SAAS,IAAI,cAAc,EAAE,CAAC;QACvC,0DAA0D;QAC1D,IAAI,CAAC,IAAA,oCAAa,EAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,+CAA+C;QAC/C,mEAAmE;QACnE,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAEhC,oEAAoE;QACpE,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5D,MAAkC,CAAC,SAAS,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@xbg.solutions/utils-hashing",
3
+ "version": "1.0.0",
4
+ "description": "PII encryption with AES-256-GCM and field-level hashing",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": ["lib"],
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "build:watch": "tsc --watch",
11
+ "clean": "rm -rf lib",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "dependencies": {"lodash": "^4.17.21"},
15
+ "devDependencies": {"@types/lodash": "^4.14.202", "@types/node": "^20.11.0", "typescript": "^5.3.3"},
16
+ "engines": {
17
+ "node": "22"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }