eip-cloud-services 1.0.21 → 1.1.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/src/s3.js CHANGED
@@ -39,20 +39,27 @@
39
39
  */
40
40
 
41
41
  const { S3Client, HeadObjectCommand, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, CopyObjectCommand, DeleteObjectsCommand } = require ( '@aws-sdk/client-s3' );
42
- const config = require ( 'config' );
42
+ const fs = require ( 'fs' );
43
+ let config = {};
44
+ const configDirPath = `${ process.cwd ()}/config`;
45
+ if ( fs.existsSync ( configDirPath ) && fs.statSync ( configDirPath ).isDirectory () ) {
46
+ config = require ( 'config' ); // require the config directory if it exists
47
+ }
43
48
  const zlib = require ( 'zlib' );
44
-
49
+ const crypto = require ( 'crypto' );
50
+ const { cwd } = require ( 'process' );
51
+ const { log } = config?.s3?.logsFunction ? require ( `${ cwd ()}/${config?.s3?.logsFunction}` ) : console;
45
52
  const S3 = new S3Client ( { region: 'eu-west-1' } );
46
53
 
47
54
  /**
48
55
  * Check if an object exists in S3.
49
56
  *
50
57
  * @param {string} key - The object key.
51
- * @param {string} [bucket=config.s3.Bucket] - The bucket name. Defaults to the configured bucket.
58
+ * @param {string} [bucket=config?.s3?.Bucket] - The bucket name. Defaults to the configured bucket.
52
59
  * @returns {Promise<boolean>} A promise that resolves to true if the object exists, false otherwise.
53
60
  * @description Checks if an object with the specified key exists in S3.
54
61
  */
55
- exports.exists = async ( key, bucket = config.s3.Bucket ) => {
62
+ exports.exists = async ( key, bucket = config?.s3?.Bucket ) => {
56
63
  try {
57
64
  const command = new HeadObjectCommand ( {
58
65
  Bucket: bucket,
@@ -60,10 +67,16 @@ exports.exists = async ( key, bucket = config.s3.Bucket ) => {
60
67
  } );
61
68
 
62
69
  await S3.send ( command );
70
+
71
+ if ( config?.s3?.logs === 'verbose' )
72
+ log ( `S3 [EXISTS]: ${key} on ${bucket} - Exists` );
63
73
 
64
74
  return true;
65
75
  }
66
76
  catch ( error ) {
77
+ if ( config?.s3?.logs === 'verbose' )
78
+ log ( `S3 [EXISTS]: ${key} on ${bucket} - Does not exist` );
79
+
67
80
  return false;
68
81
  }
69
82
  };
@@ -72,26 +85,61 @@ exports.exists = async ( key, bucket = config.s3.Bucket ) => {
72
85
  * Get an object from S3.
73
86
  *
74
87
  * @param {string} key - The object key.
88
+ * @param {string} [bucket=config?.s3?.Bucket] - The bucket name. Defaults to the configured bucket.
75
89
  * @returns {Promise} A promise that resolves to the retrieved object.
76
90
  * @description Retrieves an object from S3 based on the provided key.
77
91
  */
78
- exports.get = async ( key ) => {
92
+ exports.get = async ( key, bucket = config?.s3?.Bucket ) => {
79
93
  try {
80
94
  const command = new GetObjectCommand ( {
81
- Bucket: config.s3.Bucket,
95
+ Bucket: bucket,
82
96
  Key: key
83
97
  } );
84
98
 
99
+ if ( config?.s3?.logs === 'verbose' )
100
+ log ( `S3 [GET]: Getting ${bucket}/${key}.` );
101
+
85
102
  const response = await S3.send ( command );
86
103
  let data = await streamToBuffer ( response.Body );
87
104
 
88
105
  if ( response.ContentEncoding && response.ContentEncoding === 'gzip' ) {
106
+
107
+ if ( config?.s3?.logs === 'verbose' )
108
+ log ( `S3 [GET]: ${key} on ${bucket} was unzipped (was gzipped).` );
109
+
89
110
  data = zlib.unzipSync ( data );
90
111
  }
91
112
 
92
- if ( response.ContentType !== 'application/json' ) {
113
+ if ( response.ContentType !== 'application/json' && !response.Metadata[ 'tmg-json' ] ) {
114
+ if ( config?.s3?.logs === 'output' )
115
+ log ( `S3 [GET]: Returned ${response.ContentType} from ${bucket}/${key}.` );
116
+
93
117
  return data.toString ( 'utf8' );
94
118
  }
119
+
120
+ if ( ( response.ContentType === 'application/json' || response.Metadata[ 'tmg-json' ] ) && response.Metadata[ 'tmg-crypt' ] && response.Metadata[ 'tmg-crypt-vec' ] ) {
121
+ const key = await crypto.subtle.importKey (
122
+ 'raw',
123
+ Buffer.from ( response.Metadata[ 'tmg-crypt' ], 'base64' ),
124
+ { name: 'AES-CBC', length: 256 },
125
+ false,
126
+ [ 'decrypt' ]
127
+ );
128
+ const iv = Buffer.from ( response.Metadata[ 'tmg-crypt-vec' ], 'base64' );
129
+ const decryptedArrayBuffer = await crypto.subtle.decrypt (
130
+ { name: 'AES-CBC', iv },
131
+ key,
132
+ Buffer.from ( data.toString (), 'base64' )
133
+ );
134
+
135
+ data = Buffer.from ( decryptedArrayBuffer ).toString ( 'utf8' );
136
+
137
+ if ( config?.s3?.logs === 'verbose' )
138
+ log ( `S3 [GET]: ${key} on ${bucket} - JSON content was decrypted.` );
139
+ }
140
+
141
+ if ( config?.s3?.logs === 'output' )
142
+ log ( `S3 [GET]: ${bucket}/${key} - JSON content was returned.` );
95
143
 
96
144
  return JSON.parse ( data.toString ( 'utf8' ) );
97
145
  }
@@ -110,21 +158,52 @@ exports.get = async ( key ) => {
110
158
  * @param {string} key - The object key.
111
159
  * @param {Buffer|Uint8Array|Blob|string} body - The object body.
112
160
  * @param {object} [options] - The optional parameters for setting the object.
113
- * @param {string} [options.bucket=config.s3.Bucket] - The bucket name. Defaults to the configured bucket.
161
+ * @param {string} [options.bucket=config?.s3?.Bucket] - The bucket name. Defaults to the configured bucket.
114
162
  * @param {string} [options.contentType='application/json'] - The content type of the object. Defaults to 'application/json'.
115
163
  * @param {string} [options.acl='public-read'] - The ACL (Access Control List) of the object. Defaults to 'public-read'.
116
164
  * @param {string} [options.cacheControl='max-age=25,s-maxage=30,must-revalidate'] - Sets cache control for the object.
165
+ * @param {boolean} [options.encrypt=false] - When storing JSON parsing this as true will encrypt the data with a random uuid stored in the metadata of the object.
166
+ * @param {object} [options.metadata={}] - Sets metadata for the object.
117
167
  * @returns {Promise} A promise that resolves when the object is successfully set in S3.
118
168
  * @description Sets an object in S3 with the provided key, body, and optional parameters.
119
169
  */
120
170
  exports.set = async ( key, body, options = {} ) => {
121
171
  const {
122
- bucket = config.s3.Bucket,
172
+ bucket = config?.s3?.Bucket,
123
173
  contentType = 'application/json',
124
174
  acl = 'public-read',
125
- cacheControl = 'max-age=25,s-maxage=30,must-revalidate'
175
+ cacheControl = 'max-age=25,s-maxage=30,must-revalidate',
176
+ encrypt = false,
177
+ metadata = {}
126
178
  } = options;
127
-
179
+
180
+ if ( encrypt && ( contentType === 'application/json' || contentType === 'text/plain' ) ) {
181
+
182
+ if ( config?.s3?.logs === 'verbose' )
183
+ log ( `S3 [SET]: ${bucket}/${key} - Encrypting.` );
184
+
185
+ const encoder = new TextEncoder ();
186
+ const data = encoder.encode ( body );
187
+
188
+ const encryptionKey = await crypto.subtle.generateKey (
189
+ { name: 'AES-CBC', length: 256 },
190
+ true,
191
+ [ 'encrypt', 'decrypt' ]
192
+ );
193
+ const iv = crypto.randomBytes ( 16 );
194
+ const exportedKey = await crypto.subtle.exportKey ( 'raw', encryptionKey );
195
+ const exportedIV = iv.toString ( 'base64' );
196
+ metadata[ 'Tmg-Crypt' ] = Buffer.from ( exportedKey ).toString ( 'base64' );
197
+ metadata[ 'Tmg-Crypt-Vec' ] = exportedIV;
198
+
199
+ const encryptedData = await crypto.subtle.encrypt (
200
+ { name: 'AES-CBC', iv },
201
+ encryptionKey,
202
+ data
203
+ );
204
+ body = Buffer.from ( encryptedData ).toString ( 'base64' );
205
+ }
206
+
128
207
  try {
129
208
  const command = new PutObjectCommand ( {
130
209
  Bucket: bucket,
@@ -132,14 +211,20 @@ exports.set = async ( key, body, options = {} ) => {
132
211
  Body: body,
133
212
  ContentType: contentType,
134
213
  ACL: acl,
135
- CacheControl: cacheControl
214
+ CacheControl: cacheControl,
215
+ Metadata: metadata,
216
+ ContentLength: Buffer.byteLength ( body )
136
217
  } );
137
218
 
138
219
  const data = await S3.send ( command );
220
+
221
+ if ( config?.s3?.logs === 'outputs' || config?.s3?.logs === 'verbose' )
222
+ log ( `S3 [SET]: ${bucket}/${key} - Stored.` );
139
223
 
140
224
  return data;
141
225
  }
142
226
  catch ( error ) {
227
+ console.log ( error );
143
228
  throw error;
144
229
  }
145
230
  };
@@ -148,11 +233,11 @@ exports.set = async ( key, body, options = {} ) => {
148
233
  * Delete an object from S3.
149
234
  *
150
235
  * @param {string} key - The object key.
151
- * @param {string} [bucket=config.s3.Bucket] - The bucket name. Defaults to the configured bucket.
236
+ * @param {string} [bucket=config?.s3?.Bucket] - The bucket name. Defaults to the configured bucket.
152
237
  * @returns {Promise} A promise that resolves when the object is successfully deleted from S3.
153
238
  * @description Deletes an object from S3 based on the provided key.
154
239
  */
155
- exports.del = async ( key, bucket = config.s3.Bucket ) => {
240
+ exports.del = async ( key, bucket = config?.s3?.Bucket ) => {
156
241
  try {
157
242
  const command = new DeleteObjectCommand ( {
158
243
  Bucket: bucket,
@@ -161,6 +246,9 @@ exports.del = async ( key, bucket = config.s3.Bucket ) => {
161
246
 
162
247
  const data = await S3.send ( command );
163
248
 
249
+ if ( config?.s3?.logs === 'outputs' || config?.s3?.logs === 'verbose' )
250
+ log ( `S3 [DELETE]: ${key} on ${bucket} - Deleted.` );
251
+
164
252
  return data;
165
253
  }
166
254
  catch ( error ) {
@@ -169,16 +257,16 @@ exports.del = async ( key, bucket = config.s3.Bucket ) => {
169
257
  };
170
258
 
171
259
  /**
172
- * Move an object within S3 to a different location.
260
+ * Copy an object within S3 to a different location. (This keep the original like a COPY / PASTE operation)
173
261
  *
174
262
  * @param {string} sourceKey - The source object key.
175
263
  * @param {string} destinationKey - The destination object key.
176
- * @param {string} [sourceBucket=config.s3.Bucket] - The source bucket name. Defaults to the configured bucket.
177
- * @param {string} [destinationBucket=config.s3.Bucket] - The destination bucket name. Defaults to the configured bucket.
264
+ * @param {string} [sourceBucket=config?.s3?.Bucket] - The source bucket name. Defaults to the configured bucket.
265
+ * @param {string} [destinationBucket=config?.s3?.Bucket] - The destination bucket name. Defaults to the configured bucket.
178
266
  * @returns {Promise} A promise that resolves when the object is successfully moved in S3.
179
267
  * @description Moves an object from the source location to the destination location within S3.
180
268
  */
181
- exports.move = async ( sourceKey, destinationKey, sourceBucket = config.s3.Bucket, destinationBucket = config.s3.Bucket ) => {
269
+ exports.copy = async ( sourceKey, destinationKey, sourceBucket = config?.s3?.Bucket, destinationBucket = config?.s3?.Bucket ) => {
182
270
  try {
183
271
  // Copy the object to the destination location
184
272
  const copyCommand = new CopyObjectCommand ( {
@@ -190,19 +278,48 @@ exports.move = async ( sourceKey, destinationKey, sourceBucket = config.s3.Bucke
190
278
  } );
191
279
 
192
280
  await S3.send ( copyCommand );
281
+
282
+ if ( config?.s3?.logs === 'outputs' || config?.s3?.logs === 'verbose' ){
283
+ if ( sourceBucket === destinationBucket ){
284
+ log ( `S3 [COPY]: ${sourceKey} moved to ${destinationKey} on ${sourceBucket}.` );
285
+ }
286
+ else {
287
+ log ( `S3 [COPY]: ${sourceKey} on ${sourceBucket} moved to ${destinationKey} on ${destinationBucket}.` );
288
+ }
289
+ }
290
+ }
291
+ catch ( error ) {
292
+ throw error;
293
+ }
294
+ };
295
+
296
+ /**
297
+ * Move an object within S3 to a different location. (This deletes the original like a CUT / PASTE operation)
298
+ *
299
+ * @param {string} sourceKey - The source object key.
300
+ * @param {string} destinationKey - The destination object key.
301
+ * @param {string} [sourceBucket=config?.s3?.Bucket] - The source bucket name. Defaults to the configured bucket.
302
+ * @param {string} [destinationBucket=config?.s3?.Bucket] - The destination bucket name. Defaults to the configured bucket.
303
+ * @returns {Promise} A promise that resolves when the object is successfully moved in S3.
304
+ * @description Moves an object from the source location to the destination location within S3.
305
+ */
306
+ exports.move = async ( sourceKey, destinationKey, sourceBucket = config?.s3?.Bucket, destinationBucket = config?.s3?.Bucket ) => {
307
+ try {
308
+
309
+ // Copy the object to the destination location
310
+ await this.copy ( sourceKey, destinationKey, sourceBucket, destinationBucket );
193
311
 
194
312
  // Delete the object from the source location
195
- const deleteCommand = new DeleteObjectsCommand ( {
196
- Bucket: sourceBucket,
197
- Delete: {
198
- Objects: [
199
- { Key: sourceKey }
200
- ],
201
- Quiet: true
313
+ await this.del ( sourceKey, sourceBucket );
314
+
315
+ if ( config?.s3?.logs === 'outputs' || config?.s3?.logs === 'verbose' ){
316
+ if ( sourceBucket === destinationBucket ){
317
+ log ( `S3 [MOVE]: ${sourceKey} moved to ${destinationKey} on ${sourceBucket}.` );
202
318
  }
203
- } );
204
-
205
- await S3.send ( deleteCommand );
319
+ else {
320
+ log ( `S3 [MOVE]: ${sourceKey} on ${sourceBucket} moved to ${destinationKey} on ${destinationBucket}.` );
321
+ }
322
+ }
206
323
  }
207
324
  catch ( error ) {
208
325
  throw error;
@@ -0,0 +1,3 @@
1
+ exports.log = log => {
2
+ console.log ( log );
3
+ };