Browse Source

Merge pull request #3206 from weseek/imprv/4614-4689-cash-one-time-url

Imprv/4614 4689 cash one time url
itizawa 5 years ago
parent
commit
851e03eef0

+ 24 - 0
src/server/models/attachment.js

@@ -8,6 +8,7 @@ const path = require('path');
 const mongoose = require('mongoose');
 const mongoose = require('mongoose');
 const uniqueValidator = require('mongoose-unique-validator');
 const uniqueValidator = require('mongoose-unique-validator');
 const mongoosePaginate = require('mongoose-paginate-v2');
 const mongoosePaginate = require('mongoose-paginate-v2');
+const { addSeconds } = require('date-fns');
 
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
 const ObjectId = mongoose.Schema.Types.ObjectId;
 
 
@@ -28,6 +29,8 @@ module.exports = function(crowi) {
     fileFormat: { type: String, required: true },
     fileFormat: { type: String, required: true },
     fileSize: { type: Number, default: 0 },
     fileSize: { type: Number, default: 0 },
     createdAt: { type: Date, default: Date.now },
     createdAt: { type: Date, default: Date.now },
+    temporaryUrlCached: { type: String },
+    temporaryUrlExpiredAt: { type: Date },
   });
   });
   attachmentSchema.plugin(uniqueValidator);
   attachmentSchema.plugin(uniqueValidator);
   attachmentSchema.plugin(mongoosePaginate);
   attachmentSchema.plugin(mongoosePaginate);
@@ -66,5 +69,26 @@ module.exports = function(crowi) {
   };
   };
 
 
 
 
+  attachmentSchema.methods.getValidTemporaryUrl = function() {
+    if (this.temporaryUrlExpiredAt == null) {
+      return null;
+    }
+    // return null when expired url
+    if (this.temporaryUrlExpiredAt.getTime() < new Date().getTime()) {
+      return null;
+    }
+    return this.temporaryUrlCached;
+  };
+
+  attachmentSchema.methods.cashTemporaryUrlByProvideSec = function(temporaryUrl, provideSec) {
+    if (temporaryUrl == null) {
+      throw new Error('url is required.');
+    }
+    this.temporaryUrlCached = temporaryUrl;
+    this.temporaryUrlExpiredAt = addSeconds(new Date(), provideSec);
+
+    return this.save();
+  };
+
   return mongoose.model('Attachment', attachmentSchema);
   return mongoose.model('Attachment', attachmentSchema);
 };
 };

+ 12 - 0
src/server/service/config-loader.js

@@ -338,6 +338,12 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    TYPES.BOOLEAN,
     type:    TYPES.BOOLEAN,
     default: false,
     default: false,
   },
   },
+  S3_PROVIDE_SEC_FOR_TEMPORARY_URL: {
+    ns:      'crowi',
+    key:     'aws:provideSecForTemporaryUrl',
+    type:    TYPES.NUMBER,
+    default: 120,
+  },
   GCS_API_KEY_JSON_PATH: {
   GCS_API_KEY_JSON_PATH: {
     ns:      'crowi',
     ns:      'crowi',
     key:     'gcs:apiKeyJsonPath',
     key:     'gcs:apiKeyJsonPath',
@@ -368,6 +374,12 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    TYPES.BOOLEAN,
     type:    TYPES.BOOLEAN,
     default: false,
     default: false,
   },
   },
+  GCS_PROVIDE_SEC_FOR_TEMPORARY_URL: {
+    ns:      'crowi',
+    key:     'gcs:provideSecForTemporaryUrl',
+    type:    TYPES.NUMBER,
+    default: 120,
+  },
 };
 };
 
 
 class ConfigLoader {
 class ConfigLoader {

+ 16 - 3
src/server/service/file-uploader/aws.js

@@ -80,21 +80,34 @@ module.exports = function(crowi) {
     if (!this.getIsUploadable()) {
     if (!this.getIsUploadable()) {
       throw new Error('AWS is not configured.');
       throw new Error('AWS is not configured.');
     }
     }
+    const temporaryUrl = attachment.getValidTemporaryUrl();
+    if (temporaryUrl != null) {
+      return res.redirect(temporaryUrl);
+    }
 
 
     const s3 = S3Factory();
     const s3 = S3Factory();
     const awsConfig = getAwsConfig();
     const awsConfig = getAwsConfig();
     const filePath = getFilePathOnStorage(attachment);
     const filePath = getFilePathOnStorage(attachment);
+    const provideSecForTemporaryUrl = this.configManager.getConfig('crowi', 'aws:provideSecForTemporaryUrl');
 
 
-    // issue signed url for 30 seconds
+    // issue signed url (default: expires 120 seconds)
     // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
     // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
     const params = {
     const params = {
       Bucket: awsConfig.bucket,
       Bucket: awsConfig.bucket,
       Key: filePath,
       Key: filePath,
-      Expires: 30,
+      Expires: provideSecForTemporaryUrl,
     };
     };
     const signedUrl = s3.getSignedUrl('getObject', params);
     const signedUrl = s3.getSignedUrl('getObject', params);
 
 
-    return res.redirect(signedUrl);
+    res.redirect(signedUrl);
+
+    try {
+      return attachment.cashTemporaryUrlByProvideSec(signedUrl, provideSecForTemporaryUrl);
+    }
+    catch (err) {
+      logger.error(err);
+    }
+
   };
   };
 
 
   lib.deleteFile = async function(attachment) {
   lib.deleteFile = async function(attachment) {

+ 16 - 3
src/server/service/file-uploader/gcs.js

@@ -58,20 +58,33 @@ module.exports = function(crowi) {
     if (!this.getIsUploadable()) {
     if (!this.getIsUploadable()) {
       throw new Error('GCS is not configured.');
       throw new Error('GCS is not configured.');
     }
     }
+    const temporaryUrl = attachment.getValidTemporaryUrl();
+    if (temporaryUrl != null) {
+      return res.redirect(temporaryUrl);
+    }
 
 
     const gcs = getGcsInstance();
     const gcs = getGcsInstance();
     const myBucket = gcs.bucket(getGcsBucket());
     const myBucket = gcs.bucket(getGcsBucket());
     const filePath = getFilePathOnStorage(attachment);
     const filePath = getFilePathOnStorage(attachment);
     const file = myBucket.file(filePath);
     const file = myBucket.file(filePath);
+    const provideSecForTemporaryUrl = this.configManager.getConfig('crowi', 'gcs:provideSecForTemporaryUrl');
 
 
-    // issue signed url for 30 seconds
+    // issue signed url (default: expires 120 seconds)
     // https://cloud.google.com/storage/docs/access-control/signed-urls
     // https://cloud.google.com/storage/docs/access-control/signed-urls
     const signedUrl = await file.getSignedUrl({
     const signedUrl = await file.getSignedUrl({
       action: 'read',
       action: 'read',
-      expires: Date.now() + 30 * 1000,
+      expires: Date.now() + provideSecForTemporaryUrl * 1000,
     });
     });
 
 
-    return res.redirect(signedUrl);
+    res.redirect(signedUrl);
+
+    try {
+      return attachment.cashTemporaryUrlByProvideSec(signedUrl, provideSecForTemporaryUrl);
+    }
+    catch (err) {
+      logger.error(err);
+    }
+
   };
   };
 
 
   lib.deleteFile = async function(attachment) {
   lib.deleteFile = async function(attachment) {