"use strict";
const moment = require('moment');
const Promise = require('bluebird');
const AWS = require('aws-sdk');
const crypto = require('crypto');
/** Class representing S3 functions. Standard environment variables the AWS-SDK expects. need to be present.
* @param AWS_ACCESS_KEY_ID - should be set via env variable
* @param AWS_SECRET_ACCESS_KEY - should be set via env variable
*
* @class S3
*/
class S3 {
constructor(bucket) {
this.AWS_BUCKET = bucket || process.env.AWS_BUCKET;
}
/**
* Get expiration time for S3 policy
*
* @returns {string} returns expiration time for S3 policy
*
* @memberOf S3
*/
getExpiryTime() {
let _date = new Date();
return '' + (_date.getFullYear()) + '-' + (_date.getMonth() + 1) + '-' +
(_date.getDate() + 1) + 'T' + (_date.getHours() + 3) + ':' + '00:00.000Z';
}
/**
* Generates a policy for browser uploads
*
* @param {string} contentType - Mime type for file to be uploaded.
* @param {string} acl - acl type for file to be uploaded. either 'public-read' or 'private' @default is 'private'
* @param {string} bucket - Optional. Will use bucket if provided, or will use bucket passed into constructor if passed. - Optional. Will use bucket if provided, or will use bucket passed into constructor if passed.
* @returns {Promise} Base64 encoded representation of policy
*
* @memberOf S3
*/
createS3Policy(contentType, acl, bucket) {
let _bucket = bucket || this.AWS_BUCKET;
let _exp = this.getExpiryTime()
return new Promise(function (resolve, reject) {
if (!_bucket) {
reject('No default bucket set. Please pass in a bucket as an argument.');
}
if (!contentType || contentType === null) {
reject('Error, no content type (MIME) specified.');
}
let _contentType = contentType || "";
let _acl = acl || 'private';
let date = new Date();
var s3Policy = {
'expiration': _exp,
'conditions': [ //https://aws.amazon.com/articles/1434/
['starts-with', '$key', ''], {
'bucket': _bucket
}, {
'acl': _acl
}, {
'success_action_status': '201'
},
["starts-with", "$Content-Type", _contentType],
["starts-with", "$filename", ""],
["content-length-range", 0, 1048576 * 10]
]
};
// stringify and encode the policy
let stringPolicy = JSON.stringify(s3Policy);
let base64Policy = new Buffer(stringPolicy, 'utf-8').toString('base64');
// sign the base64 encoded policy
let signature = crypto.createHmac('sha1', process.env.AWS_SECRET_ACCESS_KEY)
.update(new Buffer(base64Policy, 'utf-8')).digest('base64');
// build the results object
let s3Credentials = {
s3Policy: base64Policy,
s3Signature: signature,
AWSAccessKeyId: process.env.AWS_ACCESS_KEY_ID
};
// send it back
resolve(s3Credentials);
});
}
/**
*
*
* @param {string} key - Required
* @param {string} bucket - Optional. Will use bucket if provided, or will use bucket passed into constructor if passed. - Optional. Will use bucket if provided, or will use bucket passed into constructor if passed.
* @returns {Promise}
*
* @memberOf S3
*/
deleteS3Object(key, bucket) {
let _bucket = bucket || this.AWS_BUCKET;
return new Promise(function (resolve, reject) {
if (!_bucket) {
reject('No default bucket set. Please pass in a bucket as an argument.');
}
if (!key) {
reject('Error, no key specified');
}
let s3 = new AWS.S3();
s3.deleteObjects({
Bucket: _bucket,
Delete: {
Objects: [{
Key: key
}]
}
}, function (err, data) {
if (err) {
reject(err);
}
resolve(data);
});
});
}
/**
*
*
* @param {buffer} body - Buffer of the file to be uploaded. Must be a valid buffer.
* @param {string} key - key of the file to be uploaded.
* @param {string} contentType - mime type of the file to be upload.
* @param {string} bucket - Optional. Will use bucket if provided, or will use bucket passed into constructor if passed.
* @param {string} acl - Optional. ACL to use. Defaults to private.
* @returns {Promise}
*
* @memberOf S3
*/
upload(body, key, contentType, bucket, acl) {
let _bucket = bucket || this.AWS_BUCKET;
return new Promise(function (resolve, reject) {
if (!_bucket) {
reject('No default bucket set. Please pass in a bucket as an argument.');
}
if (!body || body === null) {
reject('Nothing to upload. Please include a buffer.');
}
if (!Buffer.isBuffer(body)) {
reject(`body is a ${typeof(body)}. Please provide a valid buffer.`);
}
if (!key || key === null) {
reject('Error, no key specified');
}
if (!contentType || contentType === null) {
reject('No Content Type (MIME) specified.');
}
let _acl = acl || 'private';
let s3obj = new AWS.S3({
params: {
Bucket: _bucket,
Key: key,
ACL: _acl,
contentType: contentType
}
});
s3obj.upload({ // upload the deliverable to S3
Body: body
})
.send(function (err, data) {
if (err) {
reject(err);
}
resolve(data);
});
});
}
/**
*
*
* @param {string} key - file key. ex: 'deliverables/myfile.docx'
* @param {number} expiration - How long the signed url should last in minutes. leave null for 20 minutes.
* @param {string} bucket - Optional. Will use bucket if provided, or will use bucket passed into constructor.
* @returns {Promise} - Signed URL
*
* @memberOf S3
*/
getSignedUrl(key, expiration, bucket) {
let _bucket = bucket || this.AWS_BUCKET;
return new Promise(function (resolve, reject) {
if (!_bucket) {
reject('No default bucket set. Please pass in a bucket as an argument.');
}
if (!key || key === null) {
reject('Error, no key specified');
}
let _defaultExp = 60 * 20 // 20 minutes.
let _exp = expiration ? expiration * 60 : _defaultExp;
let s3 = new AWS.S3();
let url = s3.getSignedUrl('getObject', { // get a signed URL that will last for 20 minutes.
Bucket: _bucket,
Key: key,
Expires: _exp
});
resolve(url);
});
}
/**
*
*
* @param {string} key ex: 'processed/kitty.png'. Required.
* @param {string} bucket - Optional. Will use bucket if provided, or will use bucket passed into constructor if passed.
* @returns {Promise} - Resolves to promise wih with buffer representation of download. Buffer can be used as a stream or written to a file.
*
* @memberOf S3
*/
download(key, bucket) {
let _bucket = bucket || this.AWS_BUCKET;
return new Promise(function (resolve, reject) {
if (!_bucket) {
reject('No default bucket set. Please pass in a bucket as an argument.');
}
if (!key || key === null) {
reject('No key specified.');
}
let s3 = new AWS.S3();
let params = {
Bucket: _bucket,
Key: key
};
s3.getObject(params, (err, data) => {
if (err) {
reject(err);
}
if (data === null) {
reject('Key not found. No data was returned.');
}
resolve(data);
});
});
}
}
module.exports = S3;