@memberjunction/encryption 0.0.1 → 2.130.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.
- package/README.md +391 -28
- package/dist/EncryptionEngine.d.ts +351 -0
- package/dist/EncryptionEngine.d.ts.map +1 -0
- package/dist/EncryptionEngine.js +683 -0
- package/dist/EncryptionEngine.js.map +1 -0
- package/dist/EncryptionKeySourceBase.d.ts +203 -0
- package/dist/EncryptionKeySourceBase.d.ts.map +1 -0
- package/dist/EncryptionKeySourceBase.js +133 -0
- package/dist/EncryptionKeySourceBase.js.map +1 -0
- package/dist/actions/EnableFieldEncryptionAction.d.ts +87 -0
- package/dist/actions/EnableFieldEncryptionAction.d.ts.map +1 -0
- package/dist/actions/EnableFieldEncryptionAction.js +308 -0
- package/dist/actions/EnableFieldEncryptionAction.js.map +1 -0
- package/dist/actions/RotateEncryptionKeyAction.d.ts +79 -0
- package/dist/actions/RotateEncryptionKeyAction.d.ts.map +1 -0
- package/dist/actions/RotateEncryptionKeyAction.js +343 -0
- package/dist/actions/RotateEncryptionKeyAction.js.map +1 -0
- package/dist/actions/index.d.ts +12 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +17 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +216 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +15 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/providers/AWSKMSKeySource.d.ts +110 -0
- package/dist/providers/AWSKMSKeySource.d.ts.map +1 -0
- package/dist/providers/AWSKMSKeySource.js +245 -0
- package/dist/providers/AWSKMSKeySource.js.map +1 -0
- package/dist/providers/AzureKeyVaultKeySource.d.ts +109 -0
- package/dist/providers/AzureKeyVaultKeySource.d.ts.map +1 -0
- package/dist/providers/AzureKeyVaultKeySource.js +268 -0
- package/dist/providers/AzureKeyVaultKeySource.js.map +1 -0
- package/dist/providers/ConfigFileKeySource.d.ts +173 -0
- package/dist/providers/ConfigFileKeySource.d.ts.map +1 -0
- package/dist/providers/ConfigFileKeySource.js +310 -0
- package/dist/providers/ConfigFileKeySource.js.map +1 -0
- package/dist/providers/EnvVarKeySource.d.ts +152 -0
- package/dist/providers/EnvVarKeySource.d.ts.map +1 -0
- package/dist/providers/EnvVarKeySource.js +251 -0
- package/dist/providers/EnvVarKeySource.js.map +1 -0
- package/package.json +65 -6
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Action for enabling encryption on an existing entity field.
|
|
4
|
+
*
|
|
5
|
+
* When encryption is enabled on a field that already has data, this action:
|
|
6
|
+
* 1. Verifies the encryption key is valid and accessible
|
|
7
|
+
* 2. Loads existing records in batches
|
|
8
|
+
* 3. Encrypts all non-null values
|
|
9
|
+
* 4. Saves the encrypted values back to the database
|
|
10
|
+
*
|
|
11
|
+
* ## Usage
|
|
12
|
+
*
|
|
13
|
+
* This action is typically invoked after updating the EntityField metadata
|
|
14
|
+
* to enable encryption:
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // First, update the EntityField to enable encryption
|
|
18
|
+
* entityField.Encrypt = true;
|
|
19
|
+
* entityField.EncryptionKeyID = 'key-uuid';
|
|
20
|
+
* await entityField.Save();
|
|
21
|
+
*
|
|
22
|
+
* // Then, encrypt existing data
|
|
23
|
+
* const result = await actionEngine.RunAction({
|
|
24
|
+
* ActionName: 'Enable Field Encryption',
|
|
25
|
+
* Params: [
|
|
26
|
+
* { Name: 'EntityFieldID', Value: entityField.ID },
|
|
27
|
+
* { Name: 'BatchSize', Value: 100 }
|
|
28
|
+
* ],
|
|
29
|
+
* ContextUser: currentUser
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* ## Security Considerations
|
|
34
|
+
*
|
|
35
|
+
* - This is a one-way operation - plaintext is replaced with ciphertext
|
|
36
|
+
* - Ensure backups exist before running
|
|
37
|
+
* - Values that are already encrypted are skipped
|
|
38
|
+
* - Empty/null values are not encrypted
|
|
39
|
+
*
|
|
40
|
+
* @module @memberjunction/encryption
|
|
41
|
+
*/
|
|
42
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
43
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
44
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
45
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
46
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
47
|
+
};
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.EnableFieldEncryptionAction = void 0;
|
|
50
|
+
const global_1 = require("@memberjunction/global");
|
|
51
|
+
const core_1 = require("@memberjunction/core");
|
|
52
|
+
const EncryptionEngine_1 = require("../EncryptionEngine");
|
|
53
|
+
/**
|
|
54
|
+
* Action for encrypting existing data when encryption is enabled on a field.
|
|
55
|
+
*
|
|
56
|
+
* This action handles the initial encryption of existing plaintext data
|
|
57
|
+
* after the Encrypt flag is set on an EntityField.
|
|
58
|
+
*
|
|
59
|
+
* ## Process
|
|
60
|
+
*
|
|
61
|
+
* 1. Loads the EntityField metadata to get encryption settings
|
|
62
|
+
* 2. Validates the encryption key is accessible
|
|
63
|
+
* 3. Queries for all records where the field is not null
|
|
64
|
+
* 4. For each record:
|
|
65
|
+
* - Skip if already encrypted
|
|
66
|
+
* - Encrypt the plaintext value
|
|
67
|
+
* - Save the encrypted value
|
|
68
|
+
* 5. Returns statistics on encrypted/skipped records
|
|
69
|
+
*
|
|
70
|
+
* ## Batch Processing
|
|
71
|
+
*
|
|
72
|
+
* Records are processed in configurable batches to manage memory
|
|
73
|
+
* and allow for progress tracking.
|
|
74
|
+
*
|
|
75
|
+
* @security This is a privileged operation that modifies data.
|
|
76
|
+
* Should be restricted to administrators.
|
|
77
|
+
*/
|
|
78
|
+
let EnableFieldEncryptionAction = class EnableFieldEncryptionAction {
|
|
79
|
+
/**
|
|
80
|
+
* Executes the field encryption operation.
|
|
81
|
+
*
|
|
82
|
+
* @param params - Action parameters including EntityFieldID and optional BatchSize
|
|
83
|
+
* @returns Result with counts of encrypted and skipped records
|
|
84
|
+
*/
|
|
85
|
+
async Run(params) {
|
|
86
|
+
const { Params, ContextUser } = params;
|
|
87
|
+
// Extract parameters
|
|
88
|
+
const entityFieldId = this.getParamValue(Params, 'EntityFieldID');
|
|
89
|
+
const batchSize = this.getParamValue(Params, 'BatchSize') || 100;
|
|
90
|
+
// Validate required parameters
|
|
91
|
+
if (!entityFieldId) {
|
|
92
|
+
return {
|
|
93
|
+
Success: false,
|
|
94
|
+
ResultCode: 'INVALID_PARAMS',
|
|
95
|
+
Message: 'EntityFieldID is required',
|
|
96
|
+
Params
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = await this.enableFieldEncryption({
|
|
101
|
+
entityFieldId: String(entityFieldId),
|
|
102
|
+
batchSize: Number(batchSize)
|
|
103
|
+
}, ContextUser);
|
|
104
|
+
// Update output parameters
|
|
105
|
+
const outputParams = [...Params];
|
|
106
|
+
const encryptedParam = outputParams.find(p => p.Name === 'RecordsEncrypted');
|
|
107
|
+
if (encryptedParam)
|
|
108
|
+
encryptedParam.Value = result.recordsEncrypted;
|
|
109
|
+
const skippedParam = outputParams.find(p => p.Name === 'RecordsSkipped');
|
|
110
|
+
if (skippedParam)
|
|
111
|
+
skippedParam.Value = result.recordsSkipped;
|
|
112
|
+
if (result.success) {
|
|
113
|
+
return {
|
|
114
|
+
Success: true,
|
|
115
|
+
ResultCode: 'SUCCESS',
|
|
116
|
+
Message: `Field encryption completed. Encrypted ${result.recordsEncrypted} records, skipped ${result.recordsSkipped} already encrypted.`,
|
|
117
|
+
Params: outputParams
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
return {
|
|
122
|
+
Success: false,
|
|
123
|
+
ResultCode: 'ENCRYPTION_FAILED',
|
|
124
|
+
Message: result.error || 'Field encryption failed',
|
|
125
|
+
Params: outputParams
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
(0, core_1.LogError)(`Enable field encryption failed: ${message}`);
|
|
132
|
+
return {
|
|
133
|
+
Success: false,
|
|
134
|
+
ResultCode: 'ERROR',
|
|
135
|
+
Message: `Enable field encryption failed: ${message}`,
|
|
136
|
+
Params
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Performs the actual field encryption operation.
|
|
142
|
+
*
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
async enableFieldEncryption(params, contextUser) {
|
|
146
|
+
const { entityFieldId, batchSize = 100 } = params;
|
|
147
|
+
const engine = EncryptionEngine_1.EncryptionEngine.Instance;
|
|
148
|
+
await engine.Config(false, contextUser);
|
|
149
|
+
const md = new core_1.Metadata();
|
|
150
|
+
const rv = new core_1.RunView();
|
|
151
|
+
let recordsEncrypted = 0;
|
|
152
|
+
let recordsSkipped = 0;
|
|
153
|
+
try {
|
|
154
|
+
// Step 1: Load the EntityField metadata
|
|
155
|
+
const fieldResult = await rv.RunView({
|
|
156
|
+
EntityName: 'Entity Fields',
|
|
157
|
+
ExtraFilter: `ID = '${entityFieldId}'`,
|
|
158
|
+
ResultType: 'simple'
|
|
159
|
+
}, contextUser);
|
|
160
|
+
if (!fieldResult.Success || fieldResult.Results.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
recordsEncrypted: 0,
|
|
164
|
+
recordsSkipped: 0,
|
|
165
|
+
error: `EntityField not found: ${entityFieldId}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const fieldInfo = fieldResult.Results[0];
|
|
169
|
+
const entityName = fieldInfo.Entity;
|
|
170
|
+
const fieldName = fieldInfo.Name;
|
|
171
|
+
const encryptionKeyId = fieldInfo.EncryptionKeyID;
|
|
172
|
+
// Validate that encryption is enabled
|
|
173
|
+
if (!fieldInfo.Encrypt) {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
recordsEncrypted: 0,
|
|
177
|
+
recordsSkipped: 0,
|
|
178
|
+
error: `Field ${entityName}.${fieldName} does not have encryption enabled. Set Encrypt=true first.`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (!encryptionKeyId) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
recordsEncrypted: 0,
|
|
185
|
+
recordsSkipped: 0,
|
|
186
|
+
error: `Field ${entityName}.${fieldName} does not have an EncryptionKeyID set.`
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
(0, core_1.LogStatus)(`Enabling encryption for field: ${entityName}.${fieldName}`);
|
|
190
|
+
// Step 2: Validate the encryption key is accessible
|
|
191
|
+
// This will throw if the key is not valid
|
|
192
|
+
await engine.ValidateKeyMaterial(fieldInfo.KeyLookupValue || fieldInfo.EncryptionKeyID, // fallback to key ID if no lookup value in field
|
|
193
|
+
encryptionKeyId, contextUser);
|
|
194
|
+
(0, core_1.LogStatus)('Encryption key validated successfully');
|
|
195
|
+
// Step 3: Get entity info for proper querying
|
|
196
|
+
const entityInfo = md.Entities.find(e => e.Name === entityName);
|
|
197
|
+
if (!entityInfo) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
recordsEncrypted: 0,
|
|
201
|
+
recordsSkipped: 0,
|
|
202
|
+
error: `Entity not found: ${entityName}`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Step 4: Process records in batches
|
|
206
|
+
let hasMore = true;
|
|
207
|
+
let offset = 0;
|
|
208
|
+
while (hasMore) {
|
|
209
|
+
// Query for records where field is not null and not already encrypted
|
|
210
|
+
// We check for NOT LIKE '$ENC$%' to find unencrypted values
|
|
211
|
+
const batchResult = await rv.RunView({
|
|
212
|
+
EntityName: entityName,
|
|
213
|
+
ExtraFilter: `${fieldName} IS NOT NULL AND ${fieldName} NOT LIKE '$ENC$%'`,
|
|
214
|
+
OrderBy: entityInfo.PrimaryKeys.map(pk => pk.Name).join(', '),
|
|
215
|
+
MaxRows: batchSize,
|
|
216
|
+
ResultType: 'entity_object'
|
|
217
|
+
}, contextUser);
|
|
218
|
+
if (!batchResult.Success) {
|
|
219
|
+
(0, core_1.LogError)(`Failed to query records for ${entityName}.${fieldName}`);
|
|
220
|
+
hasMore = false;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const records = batchResult.Results;
|
|
224
|
+
hasMore = records.length === batchSize;
|
|
225
|
+
if (records.length === 0) {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
(0, core_1.LogStatus)(`Processing batch of ${records.length} records (offset: ${offset})`);
|
|
229
|
+
// Encrypt each record
|
|
230
|
+
for (const record of records) {
|
|
231
|
+
const plainValue = record.Get(fieldName);
|
|
232
|
+
// Double-check: skip null/empty values
|
|
233
|
+
if (plainValue === null || plainValue === undefined || plainValue === '') {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Double-check: skip if somehow already encrypted
|
|
237
|
+
if (typeof plainValue === 'string' && engine.IsEncrypted(plainValue)) {
|
|
238
|
+
recordsSkipped++;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
// Convert to string if needed
|
|
243
|
+
const stringValue = typeof plainValue === 'string'
|
|
244
|
+
? plainValue
|
|
245
|
+
: JSON.stringify(plainValue);
|
|
246
|
+
// Encrypt the value
|
|
247
|
+
const encryptedValue = await engine.Encrypt(stringValue, encryptionKeyId, contextUser);
|
|
248
|
+
// Update and save the record
|
|
249
|
+
record.Set(fieldName, encryptedValue);
|
|
250
|
+
const saveResult = await record.Save();
|
|
251
|
+
if (saveResult) {
|
|
252
|
+
recordsEncrypted++;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
(0, core_1.LogError)(`Failed to save encrypted record in ${entityName}.${fieldName}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (recordError) {
|
|
259
|
+
const msg = recordError instanceof Error ? recordError.message : String(recordError);
|
|
260
|
+
(0, core_1.LogError)(`Failed to encrypt record in ${entityName}.${fieldName}: ${msg}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
offset += batchSize;
|
|
264
|
+
}
|
|
265
|
+
// Also count records that were already encrypted (query separately)
|
|
266
|
+
const alreadyEncryptedResult = await rv.RunView({
|
|
267
|
+
EntityName: entityName,
|
|
268
|
+
ExtraFilter: `${fieldName} LIKE '$ENC$%'`,
|
|
269
|
+
MaxRows: 1, // Just checking count
|
|
270
|
+
ResultType: 'simple'
|
|
271
|
+
}, contextUser);
|
|
272
|
+
if (alreadyEncryptedResult.Success) {
|
|
273
|
+
// The query itself would return count, but for simplicity we note there are some
|
|
274
|
+
(0, core_1.LogStatus)(`Some records in ${entityName}.${fieldName} were already encrypted`);
|
|
275
|
+
}
|
|
276
|
+
(0, core_1.LogStatus)(`Field encryption completed for ${entityName}.${fieldName}. ` +
|
|
277
|
+
`Encrypted: ${recordsEncrypted}, Skipped: ${recordsSkipped}`);
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
recordsEncrypted,
|
|
281
|
+
recordsSkipped
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
286
|
+
(0, core_1.LogError)(`Enable field encryption error: ${message}`);
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
recordsEncrypted,
|
|
290
|
+
recordsSkipped,
|
|
291
|
+
error: message
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Helper to extract parameter value by name.
|
|
297
|
+
* @private
|
|
298
|
+
*/
|
|
299
|
+
getParamValue(params, name) {
|
|
300
|
+
const param = params.find(p => p.Name === name);
|
|
301
|
+
return param?.Value ?? null;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
exports.EnableFieldEncryptionAction = EnableFieldEncryptionAction;
|
|
305
|
+
exports.EnableFieldEncryptionAction = EnableFieldEncryptionAction = __decorate([
|
|
306
|
+
(0, global_1.RegisterClass)(Object, 'Enable Field Encryption')
|
|
307
|
+
], EnableFieldEncryptionAction);
|
|
308
|
+
//# sourceMappingURL=EnableFieldEncryptionAction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnableFieldEncryptionAction.js","sourceRoot":"","sources":["../../src/actions/EnableFieldEncryptionAction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Action for rotating encryption keys with full data re-encryption.
|
|
3
|
+
*
|
|
4
|
+
* Key rotation is a critical security operation that:
|
|
5
|
+
* 1. Validates the new key material is accessible
|
|
6
|
+
* 2. Decrypts all data encrypted with the old key
|
|
7
|
+
* 3. Re-encrypts with the new key in a transactional manner
|
|
8
|
+
* 4. Updates the key metadata to point to the new key material
|
|
9
|
+
*
|
|
10
|
+
* ## Usage
|
|
11
|
+
*
|
|
12
|
+
* This action is typically invoked via the MemberJunction Actions framework:
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const result = await actionEngine.RunAction({
|
|
16
|
+
* ActionName: 'Rotate Encryption Key',
|
|
17
|
+
* Params: [
|
|
18
|
+
* { Name: 'EncryptionKeyID', Value: 'key-uuid' },
|
|
19
|
+
* { Name: 'NewKeyLookupValue', Value: 'MJ_ENCRYPTION_KEY_PII_V2' },
|
|
20
|
+
* { Name: 'BatchSize', Value: 100 }
|
|
21
|
+
* ],
|
|
22
|
+
* ContextUser: currentUser
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* ## Security Considerations
|
|
27
|
+
*
|
|
28
|
+
* - The new key must be accessible before starting rotation
|
|
29
|
+
* - Rotation is transactional - all or nothing
|
|
30
|
+
* - Key status is set to 'Rotating' during the operation
|
|
31
|
+
* - On success, key version is incremented
|
|
32
|
+
* - On failure, original key continues to work
|
|
33
|
+
*
|
|
34
|
+
* @module @memberjunction/encryption
|
|
35
|
+
*/
|
|
36
|
+
import { ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
|
|
37
|
+
/**
|
|
38
|
+
* Action for rotating encryption keys with full re-encryption of affected data.
|
|
39
|
+
*
|
|
40
|
+
* Key rotation involves:
|
|
41
|
+
* 1. Validating the new key is accessible
|
|
42
|
+
* 2. Setting key status to 'Rotating'
|
|
43
|
+
* 3. For each entity field using this key:
|
|
44
|
+
* - Loading all records in batches
|
|
45
|
+
* - Decrypting with old key
|
|
46
|
+
* - Re-encrypting with new key
|
|
47
|
+
* - Updating records
|
|
48
|
+
* 4. Updating key metadata (lookup value, version)
|
|
49
|
+
* 5. Setting key status back to 'Active'
|
|
50
|
+
*
|
|
51
|
+
* ## Transaction Safety
|
|
52
|
+
*
|
|
53
|
+
* - Each batch is processed within a transaction
|
|
54
|
+
* - On batch failure, the entire rotation is rolled back
|
|
55
|
+
* - Key status is reset to 'Active' on failure
|
|
56
|
+
*
|
|
57
|
+
* @security This is a privileged operation that should be restricted to administrators.
|
|
58
|
+
*/
|
|
59
|
+
export declare class RotateEncryptionKeyAction {
|
|
60
|
+
/**
|
|
61
|
+
* Executes the key rotation operation.
|
|
62
|
+
*
|
|
63
|
+
* @param params - Action parameters including EncryptionKeyID and NewKeyLookupValue
|
|
64
|
+
* @returns Result with success status and details about rotated fields/records
|
|
65
|
+
*/
|
|
66
|
+
Run(params: RunActionParams): Promise<ActionResultSimple>;
|
|
67
|
+
/**
|
|
68
|
+
* Performs the actual key rotation operation.
|
|
69
|
+
*
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
private rotateKey;
|
|
73
|
+
/**
|
|
74
|
+
* Helper to extract parameter value by name.
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
private getParamValue;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=RotateEncryptionKeyAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RotateEncryptionKeyAction.d.ts","sourceRoot":"","sources":["../../src/actions/RotateEncryptionKeyAction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAe,MAAM,8BAA8B,CAAC;AAKhG;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,yBAAyB;IAClC;;;;;OAKG;IACU,GAAG,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyEtE;;;;OAIG;YACW,SAAS;IAyNvB;;;OAGG;IACH,OAAO,CAAC,aAAa;CAIxB"}
|