@futdevpro/nts-dynamo 1.15.117 → 1.15.118
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/.dynamo/logs/cicd-pipeline/output.log +1642 -1640
- package/.dynamo/logs/cicd-pipeline/status.json +37 -37
- package/build/_collections/field-encryption.util.d.ts +52 -0
- package/build/_collections/field-encryption.util.d.ts.map +1 -0
- package/build/_collections/field-encryption.util.js +148 -0
- package/build/_collections/field-encryption.util.js.map +1 -0
- package/build/_services/base/db.service.d.ts.map +1 -1
- package/build/_services/base/db.service.js +15 -2
- package/build/_services/base/db.service.js.map +1 -1
- package/package.json +2 -2
- package/src/_collections/field-encryption.util.spec.ts +109 -0
- package/src/_collections/field-encryption.util.ts +173 -0
- package/src/_services/base/db.service.encryption.spec.ts +76 -0
- package/src/_services/base/db.service.ts +22 -6
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { DyFM_DataProperties } from '@futdevpro/fsm-dynamo';
|
|
2
|
+
import { DyFM_Crypto } from '@futdevpro/fsm-dynamo/crypto';
|
|
3
|
+
|
|
4
|
+
import { DyNTS_FieldEncryption_Util } from './field-encryption.util';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AT-REST field-encryption transzform-util tesztjei (AAA). Lefedi: opt-in no-op, create- ÉS
|
|
8
|
+
* update-alakú doc, roundtrip, legacy-plaintext, beágyazott (subObjectParams tömb), immutabilitás,
|
|
9
|
+
* idempotencia, kulcs-hiány.
|
|
10
|
+
*/
|
|
11
|
+
describe('| DyNTS_FieldEncryption_Util', () => {
|
|
12
|
+
const KEY: string = 'db-content-crypt-key-abcdefgh';
|
|
13
|
+
|
|
14
|
+
// encrypt:true a `secret` mezőn; a `name` sima; a `steps[]` beágyazott, benne encrypt:true `prompt`.
|
|
15
|
+
const encProps: DyFM_DataProperties<any> = {
|
|
16
|
+
name: { key: 'name', type: 'string' },
|
|
17
|
+
secret: { key: 'secret', type: 'string', encrypt: true },
|
|
18
|
+
steps: {
|
|
19
|
+
key: 'steps', type: 'object[]',
|
|
20
|
+
subObjectParams: {
|
|
21
|
+
prompt: { key: 'prompt', type: 'string', encrypt: true },
|
|
22
|
+
label: { key: 'label', type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
} as unknown as DyFM_DataProperties<any>;
|
|
26
|
+
|
|
27
|
+
const plainProps: DyFM_DataProperties<any> = {
|
|
28
|
+
name: { key: 'name', type: 'string' },
|
|
29
|
+
email: { key: 'email', type: 'string' },
|
|
30
|
+
} as unknown as DyFM_DataProperties<any>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => { process.env.FDP_CORE_DBCONTENT_CRYPT_KEY = KEY; });
|
|
33
|
+
afterEach(() => { delete process.env.FDP_CORE_DBCONTENT_CRYPT_KEY; });
|
|
34
|
+
|
|
35
|
+
it('| opt-in: encrypt:true mező nélküli modell → NO-OP (ugyanaz a referencia)', () => {
|
|
36
|
+
const data = { name: 'x', email: 'y' };
|
|
37
|
+
expect(DyNTS_FieldEncryption_Util.encryptDoc(data, plainProps)).toBe(data);
|
|
38
|
+
expect(DyNTS_FieldEncryption_Util.decryptDoc(data, plainProps)).toBe(data);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('| CREATE-alak (nincs _id): encrypt:true mezőt envelope-ba teszi, a simát nem bántja', () => {
|
|
42
|
+
const data: any = { name: 'plain-name', secret: 'top-secret' };
|
|
43
|
+
const enc: any = DyNTS_FieldEncryption_Util.encryptDoc(data, encProps);
|
|
44
|
+
|
|
45
|
+
expect(DyFM_Crypto.isEncryptedDbValue(enc.secret)).toBe(true);
|
|
46
|
+
expect(enc.name).toBe('plain-name');
|
|
47
|
+
// immutabilitás: az eredeti NEM változott
|
|
48
|
+
expect(data.secret).toBe('top-secret');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('| UPDATE-alak (van _id): ugyanúgy titkosít (a modifyData/findByIdAndUpdate útra)', () => {
|
|
52
|
+
const data: any = { _id: 'abc123', name: 'n', secret: 'updated-secret' };
|
|
53
|
+
const enc: any = DyNTS_FieldEncryption_Util.encryptDoc(data, encProps);
|
|
54
|
+
|
|
55
|
+
expect(enc._id).toBe('abc123');
|
|
56
|
+
expect(DyFM_Crypto.isEncryptedDbValue(enc.secret)).toBe(true);
|
|
57
|
+
expect(DyNTS_FieldEncryption_Util.decryptDoc(enc, encProps).secret).toBe('updated-secret');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('| roundtrip: encrypt → decrypt visszaadja az eredetit', () => {
|
|
61
|
+
const data: any = { name: 'n', secret: 'a hosszú ő/ű prompt' };
|
|
62
|
+
const enc: any = DyNTS_FieldEncryption_Util.encryptDoc(data, encProps);
|
|
63
|
+
const dec: any = DyNTS_FieldEncryption_Util.decryptDoc(enc, encProps);
|
|
64
|
+
|
|
65
|
+
expect(dec.secret).toBe('a hosszú ő/ű prompt');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('| beágyazott tömb (subObjectParams): a steps[].prompt titkosítva/visszafejtve, label érintetlen', () => {
|
|
69
|
+
const data: any = { name: 'n', steps: [ { prompt: 'p1', label: 'l1' }, { prompt: 'p2', label: 'l2' } ] };
|
|
70
|
+
const enc: any = DyNTS_FieldEncryption_Util.encryptDoc(data, encProps);
|
|
71
|
+
|
|
72
|
+
expect(DyFM_Crypto.isEncryptedDbValue(enc.steps[0].prompt)).toBe(true);
|
|
73
|
+
expect(enc.steps[0].label).toBe('l1');
|
|
74
|
+
expect(data.steps[0].prompt).toBe('p1'); // eredeti nem mutálódott
|
|
75
|
+
|
|
76
|
+
const dec: any = DyNTS_FieldEncryption_Util.decryptDoc(enc, encProps);
|
|
77
|
+
expect(dec.steps[0].prompt).toBe('p1');
|
|
78
|
+
expect(dec.steps[1].prompt).toBe('p2');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('| decrypt: legacy plaintext (envelope nélkül) VÁLTOZATLAN', () => {
|
|
82
|
+
const data: any = { name: 'n', secret: 'legacy plaintext, még nem migrált' };
|
|
83
|
+
const dec: any = DyNTS_FieldEncryption_Util.decryptDoc(data, encProps);
|
|
84
|
+
expect(dec.secret).toBe('legacy plaintext, még nem migrált');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('| idempotens: már-envelope értéket nem titkosít újra', () => {
|
|
88
|
+
const enc1: any = DyNTS_FieldEncryption_Util.encryptDoc({ secret: 's' }, encProps);
|
|
89
|
+
const enc2: any = DyNTS_FieldEncryption_Util.encryptDoc(enc1, encProps);
|
|
90
|
+
expect(enc2.secret).toBe(enc1.secret);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('| üres/undefined encrypt:true értéket NEM titkosít (a crypto arra dobna)', () => {
|
|
94
|
+
const enc: any = DyNTS_FieldEncryption_Util.encryptDoc({ name: 'n', secret: '' }, encProps);
|
|
95
|
+
expect(enc.secret).toBe('');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('| kulcs-hiány + encrypt:true mező → encrypt DOB (fail-loud misconfig)', () => {
|
|
99
|
+
delete process.env.FDP_CORE_DBCONTENT_CRYPT_KEY;
|
|
100
|
+
expect(() => DyNTS_FieldEncryption_Util.encryptDoc({ secret: 's' }, encProps)).toThrow();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('| kulcs-hiány READ-en NEM dob (a read ne törjön) → az envelope visszafejtetlen marad', () => {
|
|
104
|
+
const enc: any = DyNTS_FieldEncryption_Util.encryptDoc({ secret: 's' }, encProps);
|
|
105
|
+
delete process.env.FDP_CORE_DBCONTENT_CRYPT_KEY;
|
|
106
|
+
const dec: any = DyNTS_FieldEncryption_Util.decryptDoc(enc, encProps);
|
|
107
|
+
expect(dec.secret).toBe(enc.secret); // envelope marad, nincs throw
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DyFM_DataProperties,
|
|
3
|
+
DyFM_Error,
|
|
4
|
+
} from '@futdevpro/fsm-dynamo';
|
|
5
|
+
import { DyFM_Crypto } from '@futdevpro/fsm-dynamo/crypto';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AT-REST field-encryption transzform (Option B, bedrock). A DBService a közös írási úton
|
|
9
|
+
* (`createData` / `modifyData`) a persist ELŐTT titkosítja, a read-post-processor
|
|
10
|
+
* (`stringifyDataId`) a load UTÁN visszafejti az `encrypt:true` mezőket.
|
|
11
|
+
*
|
|
12
|
+
* **App-rétegben fut, NEM mongoose-hook** — a `modifyData` `findByIdAndUpdate`-je megkerüli a
|
|
13
|
+
* mongoose settereket/pre-save-hookokat, ezért a transzform explicit itt történik.
|
|
14
|
+
*
|
|
15
|
+
* **Opt-in, default-off:** ha egy modell egyetlen mezője sem `encrypt:true`, a transzform tiszta
|
|
16
|
+
* NO-OP (ugyanaz az objektum-referencia, nincs klónozás, nincs viselkedés-változás, kulcs sem kell).
|
|
17
|
+
*
|
|
18
|
+
* **Legacy-plaintext biztonságos:** a read-transzform CSAK a `DYENC1:` envelope-értékeket fejti vissza
|
|
19
|
+
* (`isEncryptedDbValue`); a régi, még nem migrált plaintext értékek változatlanul jönnek vissza.
|
|
20
|
+
*
|
|
21
|
+
* Kulcs: `FDP_CORE_DBCONTENT_CRYPT_KEY` (Keystore/CI-env). Verzió: `FDP_CORE_DBCONTENT_CRYPT_KEY_VERSION`
|
|
22
|
+
* (default 1) — a kulcs-rotációhoz (a verzió az envelope-ban utazik).
|
|
23
|
+
*/
|
|
24
|
+
export class DyNTS_FieldEncryption_Util {
|
|
25
|
+
static readonly ENV_KEY: string = 'FDP_CORE_DBCONTENT_CRYPT_KEY';
|
|
26
|
+
static readonly ENV_KEY_VERSION: string = 'FDP_CORE_DBCONTENT_CRYPT_KEY_VERSION';
|
|
27
|
+
|
|
28
|
+
/** `hasEncryptedFields` cache per properties-objektum (referencia szerint). */
|
|
29
|
+
private static readonly _hasEncCache: WeakMap<object, boolean> = new WeakMap();
|
|
30
|
+
|
|
31
|
+
/** A titkosító kulcs env-ből (üres string, ha nincs beállítva). */
|
|
32
|
+
static getKey(): string {
|
|
33
|
+
return process.env[this.ENV_KEY] ?? '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Az aktuális kulcs-verzió (default 1). */
|
|
37
|
+
static getKeyVersion(): number {
|
|
38
|
+
const parsed: number = Number(process.env[this.ENV_KEY_VERSION]);
|
|
39
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True, ha a properties-fa (rekurzívan a `subObjectParams`-on át) tartalmaz `encrypt:true` mezőt.
|
|
44
|
+
* Cache-elt (a properties-objektum immutábilis a modell-élettartam alatt).
|
|
45
|
+
*/
|
|
46
|
+
static hasEncryptedFields(properties?: DyFM_DataProperties<any>): boolean {
|
|
47
|
+
if (!properties) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const cached: boolean | undefined = this._hasEncCache.get(properties);
|
|
52
|
+
if (cached !== undefined) {
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let has: boolean = false;
|
|
57
|
+
for (const key in properties) {
|
|
58
|
+
const prop: any = properties[key]; // ccap-review-disable-line no-any-type
|
|
59
|
+
if (!prop) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (prop.encrypt === true) {
|
|
63
|
+
has = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (prop.subObjectParams && this.hasEncryptedFields(prop.subObjectParams)) {
|
|
67
|
+
has = true;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this._hasEncCache.set(properties, has);
|
|
73
|
+
return has;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Titkosítja a doc `encrypt:true` mezőit persist előtt. **Klónt ad vissza** — az eredeti (hívó)
|
|
78
|
+
* objektumot NEM mutálja (különben a `this.data` ciphertextté válna). NO-OP, ha nincs enc-mező.
|
|
79
|
+
* @throws ha van enc-mező, de a kulcs env-var nincs beállítva (fail-loud misconfiguration).
|
|
80
|
+
*/
|
|
81
|
+
static encryptDoc<T>(data: T, properties?: DyFM_DataProperties<any>): T {
|
|
82
|
+
if (data == null || !this.hasEncryptedFields(properties)) {
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const key: string = this.getKey();
|
|
87
|
+
if (!key) {
|
|
88
|
+
throw new DyFM_Error({
|
|
89
|
+
status: 500,
|
|
90
|
+
errorCode: 'DyNTS-ENC-KEY-MISSING',
|
|
91
|
+
message:
|
|
92
|
+
`At-rest encryption: "${this.ENV_KEY}" env-var is not set, but a model field is encrypt:true. ` +
|
|
93
|
+
`Set the key in the Keystore/CI-env and redeploy.`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this._transform(data, properties as DyFM_DataProperties<any>, 'encrypt', key, this.getKeyVersion());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Visszafejti a doc `encrypt:true` mezőit load után. CSAK a `DYENC1:` envelope-értékeket bántja
|
|
102
|
+
* (legacy plaintext változatlan). NO-OP, ha nincs enc-mező. Kulcs hiányában read-en NEM dob
|
|
103
|
+
* (a read ne törjön el) — az envelope-értékek visszafejtetlenül maradnak (a hívó látja + a hiba loggolt).
|
|
104
|
+
*/
|
|
105
|
+
static decryptDoc<T>(data: T, properties?: DyFM_DataProperties<any>): T {
|
|
106
|
+
if (data == null || !this.hasEncryptedFields(properties)) {
|
|
107
|
+
return data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const key: string = this.getKey();
|
|
111
|
+
if (!key) {
|
|
112
|
+
return data;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this._transform(data, properties as DyFM_DataProperties<any>, 'decrypt', key, this.getKeyVersion());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Rekurzív per-field walk: az `encrypt:true` mezőket titkosítja/visszafejti, a `subObjectParams`-os
|
|
120
|
+
* mezőkbe (objektum VAGY objektum-tömb) leszáll. Immutábilis: minden érintett konténert sekélyen
|
|
121
|
+
* klónoz (az eredetit nem mutálja); a nem-érintett ágakat referencia szerint megosztja (read-only).
|
|
122
|
+
*/
|
|
123
|
+
private static _transform<T>(
|
|
124
|
+
value: T,
|
|
125
|
+
properties: DyFM_DataProperties<any>,
|
|
126
|
+
mode: 'encrypt' | 'decrypt',
|
|
127
|
+
key: string,
|
|
128
|
+
keyVersion: number,
|
|
129
|
+
): T {
|
|
130
|
+
if (value == null || typeof value !== 'object') {
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result: any = Array.isArray(value) ? [ ...(value as any[]) ] : { ...(value as any) }; // ccap-review-disable-line no-any-type
|
|
135
|
+
|
|
136
|
+
for (const propKey in properties) {
|
|
137
|
+
const prop: any = properties[propKey]; // ccap-review-disable-line no-any-type
|
|
138
|
+
if (!prop) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (prop.encrypt === true) {
|
|
143
|
+
const current: any = result[propKey]; // ccap-review-disable-line no-any-type
|
|
144
|
+
|
|
145
|
+
if (mode === 'encrypt') {
|
|
146
|
+
// Üres/undefined/null-t NEM titkosítunk (a DyFM_Crypto ezekre dob); már-envelope-ot sem (idempotens).
|
|
147
|
+
if (current != null && current !== '' && !DyFM_Crypto.isEncryptedDbValue(current)) {
|
|
148
|
+
result[propKey] = DyFM_Crypto.encryptDbValue(current, key, keyVersion);
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// CSAK envelope-ot fejtünk vissza (legacy plaintext változatlan).
|
|
152
|
+
if (DyFM_Crypto.isEncryptedDbValue(current)) {
|
|
153
|
+
result[propKey] = DyFM_Crypto.decryptDbValue(current, key);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} else if (prop.subObjectParams) {
|
|
157
|
+
const current: any = result[propKey]; // ccap-review-disable-line no-any-type
|
|
158
|
+
|
|
159
|
+
if (current != null && typeof current === 'object') {
|
|
160
|
+
if (Array.isArray(current)) {
|
|
161
|
+
result[propKey] = current.map(
|
|
162
|
+
(item: any): any => this._transform(item, prop.subObjectParams, mode, key, keyVersion), // ccap-review-disable-line no-any-type
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
result[propKey] = this._transform(current, prop.subObjectParams, mode, key, keyVersion);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result as T;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DyFM_DataModel_Params,
|
|
3
|
+
DyFM_Metadata,
|
|
4
|
+
DyFM_BasicProperty_Type,
|
|
5
|
+
} from '@futdevpro/fsm-dynamo';
|
|
6
|
+
import { DyFM_Crypto } from '@futdevpro/fsm-dynamo/crypto';
|
|
7
|
+
import * as mongoose from 'mongoose';
|
|
8
|
+
|
|
9
|
+
import { DyNTS_DBService } from './db.service';
|
|
10
|
+
import { DyNTS_FieldEncryption_Util } from '../../_collections/field-encryption.util';
|
|
11
|
+
|
|
12
|
+
class EncMeta extends DyFM_Metadata {
|
|
13
|
+
name: string = '';
|
|
14
|
+
secret: string = '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const encParams: DyFM_DataModel_Params<EncMeta> = new DyFM_DataModel_Params<EncMeta>({
|
|
18
|
+
dataName: 'test_enc_data',
|
|
19
|
+
properties: {
|
|
20
|
+
name: { key: 'name', type: DyFM_BasicProperty_Type.string },
|
|
21
|
+
secret: { key: 'secret', type: DyFM_BasicProperty_Type.string, encrypt: true },
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* AT-REST encryption a DB-layer create/update útján. A `createData`/`modifyData` mongoose-hívása
|
|
27
|
+
* DB-kapcsolat nélkül nem futtatható végig (l. db.service.spec: mongoose.model getter-only), de:
|
|
28
|
+
* - a `findByIdAndUpdate` INSTANCE-property-ként spy-olható → az UPDATE-út (a findByIdAndUpdate
|
|
29
|
+
* hook-bypass caveat) igazolható;
|
|
30
|
+
* - a CREATE-út az AZONOS `encryptDoc`-ot hívja `new this.dataModel()` ELŐTT → spy-val igazolható,
|
|
31
|
+
* hogy a persist előtt lefut (a save DB nélkül elhasal, de már az encrypt UTÁN).
|
|
32
|
+
*/
|
|
33
|
+
describe('| DyNTS_DBService at-rest field-encryption (create + update path)', () => {
|
|
34
|
+
let db: DyNTS_DBService<EncMeta>;
|
|
35
|
+
const KEY: string = 'db-content-crypt-key-abcdefgh';
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
process.env.FDP_CORE_DBCONTENT_CRYPT_KEY = KEY;
|
|
39
|
+
db = new DyNTS_DBService<EncMeta>(encParams);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
delete process.env.FDP_CORE_DBCONTENT_CRYPT_KEY;
|
|
44
|
+
delete (mongoose.models as Record<string, unknown>)['test_enc_data'];
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('| UPDATE: modifyData → a findByIdAndUpdate CIPHERTEXT-et kap (hook-bypass ellenére), a return plaintext', async () => {
|
|
48
|
+
const spy: jasmine.Spy = spyOn(db['dataModel'], 'findByIdAndUpdate').and.returnValue(Promise.resolve({}) as never);
|
|
49
|
+
|
|
50
|
+
const result: EncMeta = await db.modifyData({ _id: 'id1', name: 'n', secret: 'plain-secret' } as EncMeta, 'issuer');
|
|
51
|
+
|
|
52
|
+
const persisted: any = spy.calls.mostRecent().args[1]; // ccap-review-disable-line no-any-type
|
|
53
|
+
expect(DyFM_Crypto.isEncryptedDbValue(persisted.secret)).toBe(true); // DB-be ciphertext ment
|
|
54
|
+
expect(persisted.name).toBe('n'); // sima mező érintetlen
|
|
55
|
+
expect(result.secret).toBe('plain-secret'); // a hívó plaintextet kap vissza
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('| CREATE: createData a persist (new dataModel/save) ELŐTT titkosít (encryptDoc a create-adattal)', async () => {
|
|
59
|
+
const encSpy: jasmine.Spy = spyOn(DyNTS_FieldEncryption_Util, 'encryptDoc').and.callThrough();
|
|
60
|
+
|
|
61
|
+
// A .save() DB-kapcsolat nélkül buffer-elne (~10s). Kikapcsoljuk a bufferelést → GYORS fail.
|
|
62
|
+
const prevBuffer: boolean | undefined = mongoose.get('bufferCommands') as boolean | undefined;
|
|
63
|
+
mongoose.set('bufferCommands', false);
|
|
64
|
+
try {
|
|
65
|
+
await db.createData({ name: 'n', secret: 'plain-secret' } as EncMeta, 'issuer');
|
|
66
|
+
} catch {
|
|
67
|
+
// A .save() DB nélkül elhasal — a lényeg, hogy az encrypt-transzform ELŐTTE lefutott.
|
|
68
|
+
} finally {
|
|
69
|
+
mongoose.set('bufferCommands', prevBuffer ?? true);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
expect(encSpy).toHaveBeenCalled();
|
|
73
|
+
const passed: any = encSpy.calls.mostRecent().args[0]; // ccap-review-disable-line no-any-type
|
|
74
|
+
expect(passed.secret).toBe('plain-secret');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DyFM_Object
|
|
17
17
|
} from '@futdevpro/fsm-dynamo';
|
|
18
18
|
import { DyNTS_archiveSuffix } from '../../_collections/archive.util';
|
|
19
|
+
import { DyNTS_FieldEncryption_Util } from '../../_collections/field-encryption.util';
|
|
19
20
|
import { DyNTS_DBUpdate } from '../../_models/types/db-update.type';
|
|
20
21
|
import { DyNTS_DBQueryOptions } from '../../_models/interfaces/db-query-options.interface';
|
|
21
22
|
import { DyNTS_global_settings } from '../../_collections/global-settings.const';
|
|
@@ -100,7 +101,11 @@ export class DyNTS_DBService<T extends DyFM_Metadata> {
|
|
|
100
101
|
data.__createdBy = issuer;
|
|
101
102
|
data.__lastModifiedBy = issuer;
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
// AT-REST field-encryption (opt-in): az encrypt:true mezőket a persist ELŐTT titkosítjuk, egy
|
|
105
|
+
// KLÓNON (a hívó `data`-ja plaintext marad). NO-OP, ha nincs encrypt:true mező.
|
|
106
|
+
const encData: T = DyNTS_FieldEncryption_Util.encryptDoc(data, this.dataParams.properties);
|
|
107
|
+
|
|
108
|
+
const dataModel = new this.dataModel(encData);
|
|
104
109
|
const newData: T = await dataModel.save().then((res): T => {
|
|
105
110
|
if (res) {
|
|
106
111
|
return res?.toObject() as T;
|
|
@@ -157,12 +162,18 @@ export class DyNTS_DBService<T extends DyFM_Metadata> {
|
|
|
157
162
|
data.__lastModifiedBy = issuer;
|
|
158
163
|
}
|
|
159
164
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
// AT-REST field-encryption (opt-in): az encrypt:true mezőket a persist ELŐTT titkosítjuk, egy
|
|
166
|
+
// KLÓNON. KRITIKUS: a `findByIdAndUpdate` MEGKERÜLI a mongoose settereket/pre-save-hookokat, ezért
|
|
167
|
+
// a titkosítás itt, app-rétegben történik (nem mongoose-hookban). A hívó `data`-ja plaintext marad,
|
|
168
|
+
// így a `stringifyDataId(data)` alant no-op decrypttel a plaintextet adja vissza. NO-OP enc-mező nélkül.
|
|
169
|
+
const encData: T = DyNTS_FieldEncryption_Util.encryptDoc(data, this.dataParams.properties);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* EZ A SZAR TELJESEN SZAR, nem friss, nem a db-be mentett adatokat ad vissza,
|
|
173
|
+
* átír random value-kat össze vissza, WTF
|
|
163
174
|
* */
|
|
164
|
-
/* let newData: T = */
|
|
165
|
-
await this.dataModel.findByIdAndUpdate(data._id,
|
|
175
|
+
/* let newData: T = */
|
|
176
|
+
await this.dataModel.findByIdAndUpdate(data._id, encData)/* .then((res) => {
|
|
166
177
|
if (res) {
|
|
167
178
|
//return res?.toObject() as T;
|
|
168
179
|
} else {
|
|
@@ -1277,6 +1288,11 @@ export class DyNTS_DBService<T extends DyFM_Metadata> {
|
|
|
1277
1288
|
// PRIVATE FUNCTIONS
|
|
1278
1289
|
|
|
1279
1290
|
private stringifyDataId(data: T, fnName: string): T {
|
|
1291
|
+
// AT-REST field-decryption (opt-in): ez a KÖZÖS read+write post-processor (minden read + a
|
|
1292
|
+
// create/modify EREDMÉNYE is átmegy rajta), ezért itt fejtjük vissza az encrypt:true envelope-
|
|
1293
|
+
// mezőket load után. NO-OP, ha nincs encrypt:true mező (ugyanaz a referencia — nulla változás).
|
|
1294
|
+
data = DyNTS_FieldEncryption_Util.decryptDoc(data, this.dataParams.properties);
|
|
1295
|
+
|
|
1280
1296
|
if (data?._id && (typeof data._id !== 'string' || typeof data._id === 'object')) {
|
|
1281
1297
|
data._id = `${data._id}`;
|
|
1282
1298
|
|