diff --git a/src/app/modules/form/admin-form/admin-form.service.ts b/src/app/modules/form/admin-form/admin-form.service.ts index 037c9f904b..9092f41567 100644 --- a/src/app/modules/form/admin-form/admin-form.service.ts +++ b/src/app/modules/form/admin-form/admin-form.service.ts @@ -10,6 +10,8 @@ import mongoose, { ClientSession } from 'mongoose' import { err, errAsync, ok, okAsync, Result, ResultAsync } from 'neverthrow' import type { Except, Merge } from 'type-fest' +import { createPresignedPostDataPromise } from 'src/app/utils/aws-s3' + import { MAX_UPLOAD_FILE_SIZE, VALID_UPLOAD_FILE_TYPES, @@ -175,34 +177,16 @@ const createPresignedPostUrl = ( ) } - const presignedPostUrlPromise = new Promise( - (resolve, reject) => { - AwsConfig.s3.createPresignedPost( - { - Bucket: bucketName, - Expires: PRESIGNED_POST_EXPIRY_SECS, - Conditions: [ - // Content length restrictions: 0 to MAX_UPLOAD_FILE_SIZE. - ['content-length-range', 0, MAX_UPLOAD_FILE_SIZE], - ], - Fields: { - acl: 'public-read', - key: fileId, - 'Content-MD5': fileMd5Hash, - 'Content-Type': fileType, - }, - }, - (err, data) => { - if (err) { - return reject(err) - } - return resolve(data) - }, - ) - }, - ) + const presignedPostUrlPromise = createPresignedPostDataPromise({ + bucketName, + expiresSeconds: PRESIGNED_POST_EXPIRY_SECS, + size: MAX_UPLOAD_FILE_SIZE, + key: fileId, + fileMd5Hash, + fileType, + }) - return ResultAsync.fromPromise(presignedPostUrlPromise, (error) => { + return presignedPostUrlPromise.mapErr((error) => { logger.error({ message: 'Error encountered when creating presigned POST URL', meta: { diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts index eebdeec74d..b3d0ad4df6 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts @@ -1,4 +1,4 @@ -import { ManagedUpload, PresignedPost } from 'aws-sdk/clients/s3' +import { ManagedUpload } from 'aws-sdk/clients/s3' import Bluebird from 'bluebird' import crypto from 'crypto' import moment from 'moment' @@ -6,6 +6,8 @@ import mongoose from 'mongoose' import { err, errAsync, ok, okAsync, Result, ResultAsync } from 'neverthrow' import { Transform } from 'stream' +import { createPresignedPostDataPromise } from 'src/app/utils/aws-s3' + import { FormResponseMode, StorageModeSubmissionMetadata, @@ -541,41 +543,6 @@ export const performEncryptPostSubmissionActions = ( }) } -const createPresignedPostDataPromise = (size: number) => { - return ResultAsync.fromPromise( - new Promise((resolve, reject) => { - AwsConfig.s3.createPresignedPost( - { - Bucket: AwsConfig.virusScannerQuarantineS3Bucket, - Expires: 1 * 60, // expires in 1 minutes, - Conditions: [ - // Content length restrictions: 0 to MAX_UPLOAD_FILE_SIZE. - ['content-length-range', 0, size], - ], - Fields: { key: crypto.randomUUID() }, - }, - (err, data) => { - if (err) { - return reject(err) - } - return resolve(data) - }, - ) - }), - (error) => { - logger.error({ - message: 'Error encountered when creating presigned POST data', - meta: { - action: 'createPresignedPostDataPromise', - }, - error, - }) - - return new CreatePresignedPostError() - }, - ) -} - export const getQuarantinePresignedPostData = async ( attachmentSizes: AttachmentSizeMapType[], ): Promise< @@ -593,7 +560,11 @@ export const getQuarantinePresignedPostData = async ( if (totalAttachmentSize > totalAttachmentSizeLimit) return err(new AttachmentSizeLimitExceededError()) - const presignedPostDataResult = await createPresignedPostDataPromise(size) + const presignedPostDataResult = await createPresignedPostDataPromise({ + bucketName: AwsConfig.virusScannerQuarantineS3Bucket, + expiresSeconds: 1 * 60, // expires in 1 minute, + size, + }) if (presignedPostDataResult.isErr()) return err(presignedPostDataResult.error) diff --git a/src/app/utils/aws-s3.ts b/src/app/utils/aws-s3.ts new file mode 100644 index 0000000000..7c44f2b1a6 --- /dev/null +++ b/src/app/utils/aws-s3.ts @@ -0,0 +1,70 @@ +import { PresignedPost } from 'aws-sdk/clients/s3' +import { ResultAsync } from 'neverthrow' + +import { aws as AwsConfig } from '../config/config' +import { createLoggerWithLabel } from '../config/logger' +import { ApplicationError } from '../modules/core/core.errors' + +const logger = createLoggerWithLabel(module) + +class CreatePresignedPostError extends ApplicationError { + constructor( + message = 'Could not create presigned post data. Please try again.', + ) { + super(message) + } +} + +type CreatePresignedPostDataParams = { + bucketName: string + expiresSeconds: number + size: number + key?: string + fileMd5Hash?: string + fileType?: string +} + +export const createPresignedPostDataPromise = ( + params: CreatePresignedPostDataParams, +) => { + return ResultAsync.fromPromise( + new Promise((resolve, reject) => { + AwsConfig.s3.createPresignedPost( + { + Bucket: params.bucketName, + Expires: params.expiresSeconds, + Conditions: [ + // Content length restrictions: 0 to MAX_UPLOAD_FILE_SIZE. + ['content-length-range', 0, params.size], + ], + Fields: { + key: params.key ?? crypto.randomUUID(), + ...(params.fileMd5Hash + ? { 'Content-MD5': params.fileMd5Hash } + : undefined), + ...(params.fileType + ? { 'Content-Type': params.fileType } + : undefined), + }, + }, + (err, data) => { + if (err) { + return reject(err) + } + return resolve(data) + }, + ) + }), + (error) => { + logger.error({ + message: 'Error encountered when creating presigned POST data', + meta: { + action: 'createPresignedPostDataPromise', + }, + error, + }) + + return new CreatePresignedPostError() + }, + ) +}