aws.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. const logger = require('@alias/logger')('growi:service:fileUploaderAws');
  2. const urljoin = require('url-join');
  3. const aws = require('aws-sdk');
  4. module.exports = function(crowi) {
  5. const Uploader = require('./uploader');
  6. const { configManager } = crowi;
  7. const lib = new Uploader(crowi);
  8. function getAwsConfig() {
  9. return {
  10. accessKeyId: configManager.getConfig('crowi', 'aws:s3AccessKeyId'),
  11. secretAccessKey: configManager.getConfig('crowi', 'aws:s3SecretAccessKey'),
  12. region: configManager.getConfig('crowi', 'aws:s3Region'),
  13. bucket: configManager.getConfig('crowi', 'aws:s3Bucket'),
  14. customEndpoint: configManager.getConfig('crowi', 'aws:s3CustomEndpoint'),
  15. };
  16. }
  17. function S3Factory(isUploadable) {
  18. const awsConfig = getAwsConfig();
  19. if (!isUploadable) {
  20. throw new Error('AWS is not configured.');
  21. }
  22. aws.config.update({
  23. accessKeyId: awsConfig.accessKeyId,
  24. secretAccessKey: awsConfig.secretAccessKey,
  25. region: awsConfig.region,
  26. s3ForcePathStyle: awsConfig.customEndpoint ? true : undefined,
  27. });
  28. // undefined & null & '' => default endpoint (genuine S3)
  29. return new aws.S3({ endpoint: awsConfig.customEndpoint || undefined });
  30. }
  31. function getFilePathOnStorage(attachment) {
  32. if (attachment.filePath != null) { // backward compatibility for v3.3.x or below
  33. return attachment.filePath;
  34. }
  35. const dirName = (attachment.page != null)
  36. ? 'attachment'
  37. : 'user';
  38. const filePath = urljoin(dirName, attachment.fileName);
  39. return filePath;
  40. }
  41. async function isFileExists(s3, params) {
  42. // check file exists
  43. try {
  44. await s3.headObject(params).promise();
  45. }
  46. catch (err) {
  47. if (err != null && err.code === 'NotFound') {
  48. return false;
  49. }
  50. // error except for 'NotFound
  51. throw err;
  52. }
  53. return true;
  54. }
  55. lib.isValidUploadSettings = function() {
  56. return this.configManager.getConfig('crowi', 'aws:s3AccessKeyId') != null
  57. && this.configManager.getConfig('crowi', 'aws:s3SecretAccessKey') != null
  58. && (
  59. this.configManager.getConfig('crowi', 'aws:s3Region') != null
  60. || this.configManager.getConfig('crowi', 'aws:s3CustomEndpoint') != null
  61. )
  62. && this.configManager.getConfig('crowi', 'aws:s3Bucket') != null;
  63. };
  64. lib.deleteFile = async function(attachment) {
  65. const filePath = getFilePathOnStorage(attachment);
  66. return lib.deleteFileByFilePath(filePath);
  67. };
  68. lib.deleteFileByFilePath = async function(filePath) {
  69. const s3 = S3Factory(this.getIsUploadable());
  70. const awsConfig = getAwsConfig();
  71. const params = {
  72. Bucket: awsConfig.bucket,
  73. Key: filePath,
  74. };
  75. // check file exists
  76. const isExists = await isFileExists(s3, params);
  77. if (!isExists) {
  78. logger.warn(`Any object that relate to the Attachment (${filePath}) does not exist in AWS S3`);
  79. return;
  80. }
  81. return s3.deleteObject(params).promise();
  82. };
  83. lib.uploadFile = function(fileStream, attachment) {
  84. logger.debug(`File uploading: fileName=${attachment.fileName}`);
  85. const s3 = S3Factory(this.getIsUploadable());
  86. const awsConfig = getAwsConfig();
  87. const filePath = getFilePathOnStorage(attachment);
  88. const params = {
  89. Bucket: awsConfig.bucket,
  90. ContentType: attachment.fileFormat,
  91. Key: filePath,
  92. Body: fileStream,
  93. ACL: 'public-read',
  94. };
  95. return s3.upload(params).promise();
  96. };
  97. /**
  98. * Find data substance
  99. *
  100. * @param {Attachment} attachment
  101. * @return {stream.Readable} readable stream
  102. */
  103. lib.findDeliveryFile = async function(attachment) {
  104. const s3 = S3Factory(this.getIsUploadable());
  105. const awsConfig = getAwsConfig();
  106. const filePath = getFilePathOnStorage(attachment);
  107. const params = {
  108. Bucket: awsConfig.bucket,
  109. Key: filePath,
  110. };
  111. // check file exists
  112. const isExists = await isFileExists(s3, params);
  113. if (!isExists) {
  114. throw new Error(`Any object that relate to the Attachment (${filePath}) does not exist in AWS S3`);
  115. }
  116. let stream;
  117. try {
  118. stream = s3.getObject(params).createReadStream();
  119. }
  120. catch (err) {
  121. logger.error(err);
  122. throw new Error(`Coudn't get file from AWS for the Attachment (${attachment._id.toString()})`);
  123. }
  124. // return stream.Readable
  125. return stream;
  126. };
  127. /**
  128. * check the file size limit
  129. *
  130. * In detail, the followings are checked.
  131. * - per-file size limit (specified by MAX_FILE_SIZE)
  132. */
  133. lib.checkLimit = async(uploadFileSize) => {
  134. const maxFileSize = crowi.configManager.getConfig('crowi', 'app:maxFileSize');
  135. const totalLimit = crowi.configManager.getConfig('crowi', 'app:fileUploadTotalLimit');
  136. return lib.doCheckLimit(uploadFileSize, maxFileSize, totalLimit);
  137. };
  138. return lib;
  139. };