| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import {
- S3Client, HeadObjectCommand, GetObjectCommand, DeleteObjectsCommand, PutObjectCommand,
- } from '@aws-sdk/client-s3';
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
- import urljoin from 'url-join';
- import loggerFactory from '~/utils/logger';
- const logger = loggerFactory('growi:service:fileUploaderAws');
- type AwsCredential = {
- accessKeyId: string,
- secretAccessKey: string
- }
- type AwsConfig = {
- credentials: AwsCredential,
- region: string,
- endpoint: string,
- bucket: string,
- forcePathStyle?: boolean
- }
- module.exports = (crowi) => {
- const Uploader = require('./uploader');
- const { configManager } = crowi;
- const lib = new Uploader(crowi);
- const getAwsConfig = (): AwsConfig => {
- return {
- credentials: {
- accessKeyId: configManager.getConfig('crowi', 'aws:s3AccessKeyId'),
- secretAccessKey: configManager.getConfig('crowi', 'aws:s3SecretAccessKey'),
- },
- region: configManager.getConfig('crowi', 'aws:s3Region'),
- endpoint: configManager.getConfig('crowi', 'aws:s3CustomEndpoint'),
- bucket: configManager.getConfig('crowi', 'aws:s3Bucket'),
- forcePathStyle: configManager.getConfig('crowi', 'aws:s3Bucket') != null,
- };
- };
- const S3Factory = () => {
- const config = getAwsConfig();
- return new S3Client(config);
- };
- const getFilePathOnStorage = (attachment) => {
- if (attachment.filePath != null) {
- return attachment.filePath;
- }
- const dirName = (attachment.page != null)
- ? 'attachment'
- : 'user';
- const filePath = urljoin(dirName, attachment.fileName);
- return filePath;
- };
- const isFileExists = async(s3, params) => {
- try {
- await s3.send(new HeadObjectCommand(params));
- }
- catch (err) {
- if (err != null && err.code === 'NotFound') {
- return false;
- }
- throw err;
- }
- return true;
- };
- lib.isValidUploadSettings = () => {
- return configManager.getConfig('crowi', 'aws:s3AccessKeyId') != null
- && configManager.getConfig('crowi', 'aws:s3SecretAccessKey') != null
- && (
- configManager.getConfig('crowi', 'aws:s3Region') != null
- || configManager.getConfig('crowi', 'aws:s3CustomEndpoint') != null
- )
- && configManager.getConfig('crowi', 'aws:s3Bucket') != null;
- };
- lib.canRespond = () => {
- return !configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode');
- };
- lib.respond = async(res, attachment) => {
- if (!lib.getIsUploadable()) {
- throw new Error('AWS is not configured.');
- }
- const temporaryUrl = attachment.getValidTemporaryUrl();
- if (temporaryUrl != null) {
- return res.redirect(temporaryUrl);
- }
- const s3 = S3Factory();
- const awsConfig = getAwsConfig();
- const filePath = getFilePathOnStorage(attachment);
- const lifetimeSecForTemporaryUrl = configManager.getConfig('crowi', 'aws:lifetimeSecForTemporaryUrl');
- // issue signed url (default: expires 120 seconds)
- // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
- const params = {
- Bucket: awsConfig.bucket,
- Key: filePath,
- Expires: lifetimeSecForTemporaryUrl,
- };
- const signedUrl = await getSignedUrl(s3, new GetObjectCommand(params));
- res.redirect(signedUrl);
- try {
- return attachment.cashTemporaryUrlByProvideSec(signedUrl, lifetimeSecForTemporaryUrl);
- }
- catch (err) {
- logger.error(err);
- }
- };
- lib.deleteFile = async(attachment) => {
- const filePath = getFilePathOnStorage(attachment);
- return lib.deleteFileByFilePath(filePath);
- };
- lib.deleteFiles = async(attachments) => {
- if (!lib.getIsUploadable()) {
- throw new Error('AWS is not configured.');
- }
- const s3 = S3Factory();
- const awsConfig = getAwsConfig();
- const filePaths = attachments.map((attachment) => {
- return { Key: getFilePathOnStorage(attachment) };
- });
- const totalParams = {
- Bucket: awsConfig.bucket,
- Delete: { Objects: filePaths },
- };
- await s3.send(new DeleteObjectsCommand(totalParams));
- };
- lib.uploadFile = async(fileStream, attachment) => {
- if (!lib.getIsUploadable()) {
- throw new Error('AWS is not configured.');
- }
- logger.debug(`File uploading: fileName=${attachment.fileName}`);
- const s3 = S3Factory();
- const awsConfig = getAwsConfig();
- const filePath = getFilePathOnStorage(attachment);
- const params = {
- Bucket: awsConfig.bucket,
- ContentType: attachment.fileFormat,
- Key: filePath,
- Body: fileStream,
- ACL: 'public-read',
- };
- await s3.send(new PutObjectCommand(params));
- };
- lib.findDeliveryFile = async(attachment) => {
- if (!lib.getIsReadable()) {
- throw new Error('AWS is not configured.');
- }
- const s3 = S3Factory();
- const awsConfig = getAwsConfig();
- const filePath = getFilePathOnStorage(attachment);
- const params = {
- Bucket: awsConfig.bucket,
- Key: filePath,
- };
- // check file exists
- const isExists = await isFileExists(s3, params);
- if (!isExists) {
- throw new Error(`Any object that relate to the Attachment (${filePath}) does not exist in AWS S3`);
- }
- let stream;
- try {
- stream = s3.send(new GetObjectCommand(params));
- }
- catch (err) {
- logger.error(err);
- throw new Error(`Coudn't get file from AWS for the Attachment (${attachment._id.toString()})`);
- }
- // return stream.Readable
- return stream;
- };
- lib.checkLimit = async(uploadFileSize) => {
- const maxFileSize = crowi.configManager.getConfig('crowi', 'app:maxFileSize');
- const totalLimit = crowi.configManager.getConfig('crowi', 'app:fileUploadTotalLimit');
- return lib.doCheckLimit(uploadFileSize, maxFileSize, totalLimit);
- };
- return lib;
- };
|