UNPKG

16.4 kBPlain TextView Raw
1import * as AWS from 'aws-sdk';
2import {response, IResponse} from './tools';
3
4AWS.config.update({region: 'us-east-1'}); // Virginia
5const kms = new AWS.KMS({apiVersion: '2014-11-01'});
6// const kmsKeyId = process.env.KMS_PLAYER_ARN_KEY;
7// let kmsKeyId;
8
9/**
10 *
11 * @param {string} data
12 * @param {string} kmsKeyId
13 * @returns {Promise<any>}
14 */
15function encrypt(data: string, kmsKeyId: string): Promise<any> {
16 return new Promise((res, rej) => {
17 try {
18 if (!data || data === '') {
19 // data is null/undefined so we are not going to encrypt it
20 return res(null);
21 }
22
23 kms.encrypt({KeyId: kmsKeyId, Plaintext: data}, function (err, _data) {
24 if (err) {
25 rej(err);
26 } else {
27 res(_data);
28 }
29 });
30 } catch (error) {
31 rej(error.message);
32 }
33 });
34}
35
36/**
37 *
38 * @param blob
39 * @param {string} kmsKeyId
40 * @returns {Promise<any>}
41 */
42function decrypt(blob: any, kmsKeyId: string): Promise<any> {
43 return new Promise((res, rej) => {
44 try {
45 if ((blob && !blob.CiphertextBlob) || !blob) {
46 // blob is just a non-encrypted string
47 return res(blob);
48 }
49 kms.decrypt({CiphertextBlob: blob.CiphertextBlob}, function (err, _data) {
50 if (err) {
51 console.log(err);
52 rej(err);
53 } else {
54 res(_data);
55 }
56 });
57 } catch (error) {
58 console.log(143, error.message);
59 rej(error.message);
60 }
61 });
62}
63
64export interface IDynamoParams {
65 TableName: string;
66 IndexName?: string;
67 Key?: any;
68 Item?: any;
69 KeyConditionExpression?: string;
70 ConditionExpression?: string;
71 ExpressionAttributeValues?: any;
72 ExpressionAttributeNames?: any;
73 UpdateExpression?: string;
74 FilterExpression?: string;
75 Limit?: number;
76 LastEvaluatedKey?: any;
77 ExclusiveStartKey?: any;
78 ScanIndexForward?: boolean;
79 ProjectionExpression?: string;
80 force?: string; // for delete purposes
81 ScannedCount?: any;
82}
83
84export class DynamoDbApi {
85 dynamoDb: any;
86 kmsKeyId: string;
87
88 constructor(dynamoDb: any, _kmsKeyId?: string) {
89 this.dynamoDb = dynamoDb;
90 this.kmsKeyId = _kmsKeyId;
91 process.setMaxListeners(Infinity);
92 }
93
94 /**
95 *
96 * @param item
97 * @param {Array<string>} decryptFields
98 * @returns {Promise<any>}
99 */
100 async decryptItem(item: any, decryptFields: Array<string>): Promise<any> {
101 try {
102 const Keys = Object.keys(item);
103 for (let i = 0; i < Keys.length; i++) {
104 // is it an encrypted data, and do we want to decrypt it
105 if (item[Keys[i]] && item[Keys[i]]['CiphertextBlob'] && decryptFields.indexOf(Keys[i]) > -1) {
106 const decryptedTmp = await decrypt(item[Keys[i]], this.kmsKeyId);
107
108 if (decryptedTmp && decryptedTmp.Plaintext) {
109 const val = decryptedTmp.Plaintext.toString();
110 item[Keys[i]] = val.indexOf('{') > -1 ? JSON.parse(val) : val;
111 }
112 }
113 }
114 } catch (error) {
115 console.log('decryptItem():', error);
116 }
117 return item;
118 }
119
120 /**
121 *
122 * @param item
123 * @param {Array<string>} encryptFields
124 * @returns {Promise<any>}
125 */
126 async encryptItem(item: any, encryptFields: Array<string>): Promise<any> {
127 try {
128 const Keys = Object.keys(item);
129 for (let i = 0; i < Keys.length; i++) {
130 // is it an encrypted data, and do we want to decrypt it
131 if (item[Keys[i]] && encryptFields.indexOf(Keys[i]) > -1) {
132 const encryptedTmp = await encrypt(typeof item[Keys[i]] === 'object' ? JSON.stringify(item[Keys[i]]) : item[Keys[i]],
133 this.kmsKeyId);
134
135 if (encryptedTmp) {
136 item[Keys[i]] = encryptedTmp;
137 }
138 }
139 }
140 } catch (error) {
141 console.log('tools.encryptItem() error:', error.message);
142 } finally {
143 return item;
144 }
145 }
146
147 /**
148 *
149 * @param {IDynamoParams} params
150 * @param {string} context
151 * @param {Array<any>} decryptFields
152 * @returns {Promise<IResponse>}
153 */
154 get(params: IDynamoParams, context: string, decryptFields?: Array<any>): Promise<IResponse> {
155 const dynamoDb = this.dynamoDb;
156
157 return new Promise<IResponse>((res, rej) => {
158 if (!dynamoDb) {
159 return rej(response(false, 'dynamoDbTable undefined'));
160 }
161
162 try {
163 dynamoDb.get(params, async (error, result) => {
164 if (error) {
165 return rej(response(false, error, `Could not get '${context}' info`));
166 }
167 else if (result.Item) {
168
169 if (decryptFields && decryptFields.length) {
170 result.Item = await this.decryptItem(result.Item, decryptFields);
171 }
172
173 return res(response(true, null, result.Item));
174 }
175 else {
176 return res(response(false, `'${context}' info not found`));
177 }
178 });
179 } catch (error) {
180 console.log(479, error.message);
181 rej(response(false, error));
182 }
183 })
184 }
185
186 /**
187 *
188 * @param dynamoDb
189 * @param {IDynamoParams} params
190 * @param {string} context
191 * @param {Array<any>} decryptFields
192 * @returns {Promise<IResponse>}
193 * @private
194 */
195 private async _scan(dynamoDb: any, params: IDynamoParams, context: string, decryptFields?: Array<any>): Promise<IResponse> {
196 if (!dynamoDb) {
197 return response(false, 'dynamoDbTable undefined');
198 }
199 try {
200 let count = 0;
201 const result = await dynamoDb.scan(params).promise();
202 if (result.Items && result.Items.length) {
203 if (decryptFields && decryptFields.length) {
204 for (let i = 0; i < result.Items.length; i++) {
205 result.Items[i] = await this.decryptItem(result.Items[i], decryptFields);
206 }
207 }
208 return response(true, null, result);
209 }
210 else {
211 return response(false, `${context} info not found`, result);
212 }
213
214 } catch (error) {
215 console.log('263 Error tools.db._scan()', error.message, params);
216 return response(false, error);
217 }
218 }
219
220 /**
221 *
222 * @param {IDynamoParams} params
223 * @param {string} context
224 * @param {Array<any>} decryptFields
225 * @returns {Promise<IResponse>}
226 */
227 async scan(params: IDynamoParams, context: string, decryptFields?: Array<any>): Promise<IResponse> {
228 const dynamoDb = this.dynamoDb;
229
230 try {
231 let items = [], result, whileMax = 1000, i = 0, count = 0, done = false, LastEvaluatedKey = null;
232
233 while (!done && (i < whileMax)) {
234 result = await this._scan(dynamoDb, params, context, decryptFields);
235
236 if (!result.success && !params.Limit) {
237 done = true;
238 return result;
239 } else {
240 if (params.Limit) {
241 if (result.data) {
242 count += result.data.Count;
243 if (!result.data.LastEvaluatedKey) {
244 done = true;
245 items.push(...result.data.Items);
246 } else {
247 if (count === params.Limit) {
248 done = true;
249 items.push(...result.data.Items);
250 LastEvaluatedKey = result.data.LastEvaluatedKey;
251 } else if (count > params.Limit) {
252 done = true;
253 const keys = Object.keys(result.data.LastEvaluatedKey);
254 items.push(...result.data.Items.slice(0, (count - params.Limit)));
255
256 for (let j = 0; j < keys.length; j++) {
257 LastEvaluatedKey[keys[j]] = result.data.Items[count - params.Limit - 1][keys[j]];
258 }
259 } else {
260 LastEvaluatedKey = result.data.LastEvaluatedKey;
261 params['ExclusiveStartKey'] = LastEvaluatedKey;
262 items.push(...result.data.Items);
263 // let's go for a new loop then
264 }
265 }
266 } else {
267 // let's go for a new loop then
268 LastEvaluatedKey = result.data.LastEvaluatedKey;
269 params['ExclusiveStartKey'] = LastEvaluatedKey;
270 }
271 } else {
272 done = true;
273 LastEvaluatedKey = result.data.LastEvaluatedKey;
274 items.push(...result.data.Items);
275 }
276 }
277 i++;
278 }
279
280 return response(true, null, {
281 Items: items,
282 Count: items.length,
283 CountV2: result.data.ScannedCount,
284 LastEvaluatedKey: LastEvaluatedKey,
285 Loops: i
286 });
287 } catch (error) {
288 return response(false, error);
289 }
290 }
291
292 /**
293 *
294 * @param {IDynamoParams} params
295 * @param {string} context
296 * @param {Array<any>} decryptFields
297 * @returns {Promise<IResponse>}
298 */
299 async query(params: IDynamoParams, context: string, decryptFields?: Array<any>): Promise<IResponse> {
300 const dynamoDb = this.dynamoDb;
301
302 if (!dynamoDb) {
303 return response(false, 'dynamoDbTable undefined');
304 }
305 try {
306 const result = await dynamoDb.query(params).promise();
307
308 if (result.Items && result.Items.length) {
309 if (decryptFields && decryptFields.length) {
310 for (let i = 0; i < result.Items.length; i++) {
311 result.Items[i] = await this.decryptItem(result.Items[i], decryptFields);
312 }
313 }
314
315 return response(true, null, result.Items.length > 1 ? result.Items : result.Items[0]);
316 } else {
317 return response(false, `'${context}' info not found`);
318 }
319 } catch (error) {
320 console.log('263 Error tools.db.query()', error.message, params);
321 return response(false, error.message, `Could not get '${context}' info`);
322 }
323 }
324
325 /**
326 * Return the raw result from dynamodb, instead only "Items"
327 *
328 * @param {IDynamoParams} params
329 * @param {string} context
330 * @param {Array<any>} decryptFields
331 * @returns {Promise<IResponse>}
332 */
333 async queryRAW(params: IDynamoParams, context: string, decryptFields?: Array<any>): Promise<IResponse> {
334 const dynamoDb = this.dynamoDb;
335
336 if (!dynamoDb) {
337 return response(false, 'dynamoDbTable undefined');
338 }
339 try {
340 let result = await dynamoDb.query(params).promise();
341
342 if(!result){
343 return response(false, `'${context}' - Error on dynamoDb.query(params).promise().`);
344 }
345
346 let promises = [];
347
348 if (result.Items && result.Items.length) {
349 if (decryptFields && decryptFields.length) {
350 for (let i = 0; i < result.Items.length; i++) {
351 promises.push(this.decryptItem(result.Items[i], decryptFields))
352 }
353 result = await Promise.all(promises).then(results => {
354 delete result.Items;
355 result['Items'] = results;
356 return result;
357 });
358 }
359
360 return response(true, null, result);
361
362 } else {
363 return response(false, `'${context}' info not found`, result);
364 }
365 } catch (error) {
366 console.log('263 Error tools.db.query()', error.message, params);
367 return response(false, error.message, `Could not get '${context}' info`);
368 }
369 }
370
371 /**
372 *
373 * @param {IDynamoParams} params
374 * @param {string} context
375 * @param {Array<any>} decryptFields
376 * @returns {Promise<IResponse>}
377 */
378 delete(params: IDynamoParams, context: string, decryptFields?: Array<any>): Promise<IResponse> {
379 const dynamoDb = this.dynamoDb;
380
381 return new Promise<IResponse>((res, rej) => {
382 if (!dynamoDb) {
383 return rej(response(false, 'dynamoDbTable undefined'));
384 }
385
386 try {
387 if (params.force) {
388 dynamoDb.delete(params, async (error, result) => {
389 if (error) {
390 return rej(response(false, error, `Could not delete '${context}' info`));
391 }
392 return res(response(true, null, result));
393 });
394 } else {
395
396 if (!params.Key) {
397 return response(false, 'Key is required. Cannot delete item');
398 }
399
400 // building a clean query
401 const _params = {
402 TableName: params.TableName,
403 Key: params.Key,
404 UpdateExpression: "set deleted =:d",
405 ExpressionAttributeValues: {
406 ":d": true
407 }
408 };
409
410 dynamoDb.update(_params, (error, result) => {
411 if (error) {
412 console.log(params, error);
413 return rej(response(false, error, `Could not update '${context}' item to 'deleted' state`));
414 }
415 else {
416 return res(response(true, null, result));
417 }
418 });
419 }
420 } catch (error) {
421 console.log(479, error.message);
422 rej(response(false, error));
423 }
424 })
425 }
426
427 /**
428 *
429 * @param {IDynamoParams} params
430 * @param {string} context
431 * @param {Array<any>} encryptFields
432 * @returns {Promise<IResponse>}
433 */
434 put(params: IDynamoParams, context: string, encryptFields?: Array<any>): Promise<IResponse> {
435 return new Promise<IResponse>(async (res, rej) => {
436 const dynamoDb = this.dynamoDb;
437
438 try {
439 if (!dynamoDb) {
440 return rej(response(false, 'dynamoDbTable undefined'));
441 }
442
443 if (encryptFields && encryptFields.length) {
444 params.Item = await this.encryptItem(params.Item, encryptFields);
445 }
446
447 //Adding created and updated
448 params.Item.updatedAt = new Date().toISOString();
449 params.Item.createdAt = new Date().toISOString();
450
451 dynamoDb.put(params, (error, result) => {
452 if (error) {
453 console.log(params, error);
454 return rej(response(false, error, `Could not insert '${context}' item`));
455 }
456 else {
457 return res(response(true, null, result));
458 }
459 });
460 } catch (error) {
461 console.log('295 tools.db.put()', error);
462 rej(response(false, error.message));
463 }
464 });
465 }
466
467 /**
468 *
469 * @param item
470 * @param {Array<any>} encryptFields
471 * @returns {Promise<{expressions: any; updates: any}>}
472 */
473 async getUpdateExpression(item: any, encryptFields?: Array<any>): Promise<{ expressions: any, updates: any }> {
474 let expressions = {}, updates = '';
475 if (encryptFields) {
476 item = await this.encryptItem(item, encryptFields);
477 }
478
479 const Keys = Object.keys(item);
480 for (let i = 0; i < Keys.length; i++) {
481 // if (Keys[i] === 'id')
482 expressions[`:v${i}`] = item[Keys[i]];
483 updates += `${Keys[i]} =:v${i},`;
484 }
485
486 updates = updates.replace(/(^,)|(,$)/g, "");
487
488 return {
489 expressions: expressions,
490 updates: updates
491 };
492 }
493
494 /**
495 *
496 *
497 *
498 * @param item
499 * @param {Array<any>} encryptFields
500 * @returns {Promise<{expressions: any; updates: any}>}
501 */
502 async getUpdateExpressionV2(item: any, encryptFields?: Array<any>): Promise<{ attributesValue: any, updates: any, attributesName: any }> {
503 let attributesValue = {}, attributesName = {}, updates = '';
504 if (encryptFields) {
505 item = await this.encryptItem(item, encryptFields);
506 }
507
508 const Keys = Object.keys(item);
509 for (let i = 0; i < Keys.length; i++) {
510 // if (Keys[i] === 'id')
511 attributesValue[`:v${i}`] = item[Keys[i]];
512 attributesName[`#a${i}`] = Keys[i];
513 updates += `#a${i} =:v${i},`;
514 }
515
516 updates = updates.replace(/(^,)|(,$)/g, "");
517
518 return {
519 attributesValue: attributesValue,
520 updates: updates,
521 attributesName: attributesName
522
523 };
524 }
525
526 /**
527 *
528 * @param {IDynamoParams} params
529 * @param {string} context
530 * @param {Array<any>} encryptFields
531 * @returns {Promise<IResponse>}
532 */
533 update(params: IDynamoParams, context: string, encryptFields?: Array<any>): Promise<IResponse> {
534 const dynamoDb = this.dynamoDb;
535
536 return new Promise<IResponse>((res, rej) => {
537 if (!dynamoDb) {
538 return rej(response(false, 'dynamoDbTable undefined'));
539 }
540 try {
541 // Adding updatedAt get the time at insert.
542 params.ExpressionAttributeValues[':upd'] = new Date().toISOString();
543 params.UpdateExpression += ', updatedAt =:upd';
544
545 dynamoDb.update(params, (error, result) => {
546 if (error) {
547 console.log(params, error);
548 return rej(response(false, error));
549 }
550 else {
551 return res(response(true, null, result));
552 }
553 });
554 } catch (error) {
555 console.log(error)
556 rej(response(false, error));
557 }
558 });
559 }
560}
561
\No newline at end of file