diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts index 1a7eec1c73..a9fb308224 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts @@ -634,7 +634,7 @@ export const handleStorageSubmission = [ EncryptSubmissionMiddleware.validateStorageSubmissionParams, EncryptSubmissionMiddleware.createFormsgAndRetrieveForm, EncryptSubmissionMiddleware.checkNewBoundaryEnabled, - EncryptSubmissionMiddleware.checkAttachmentQuarantineKeys, + EncryptSubmissionMiddleware.scanAttachments, EncryptSubmissionMiddleware.validateStorageSubmission, EncryptSubmissionMiddleware.encryptSubmission, submitEncryptModeForm, diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.errors.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.errors.ts index bbc0b25b6e..1f2f481ed7 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.errors.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.errors.ts @@ -49,3 +49,9 @@ export class FeatureDisabledError extends ApplicationError { super(message) } } + +export class VirusScanFailedError extends ApplicationError { + constructor(message = 'Virus scan failed. Please try again.') { + super(message) + } +} diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts index 764cc4e9f8..a14521bd84 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts @@ -3,6 +3,7 @@ import { celebrate, Joi, Segments } from 'celebrate' import { NextFunction } from 'express' import { StatusCodes } from 'http-status-codes' import { chain, omit } from 'lodash' +import { okAsync, ResultAsync } from 'neverthrow' import { featureFlags } from '../../../../../shared/constants' import { @@ -25,7 +26,10 @@ import * as FormService from '../../form/form.service' import ParsedResponsesObject from '../ParsedResponsesObject.class' import { sharedSubmissionParams } from '../submission.constants' import * as SubmissionService from '../submission.service' -import { isAttachmentResponse } from '../submission.utils' +import { + isAttachmentResponse, + isQuarantinedAttachmentResponse, +} from '../submission.utils' import { EncryptedPayloadExistsError, @@ -183,7 +187,7 @@ export const checkNewBoundaryEnabled = async ( * Guardrail to prevent virus scanner code from being run if not enabled on frontend. * TODO (FRM-1232): remove this guardrail when encryption boundary is shifted. */ -export const checkAttachmentQuarantineKeys = async ( +export const scanAttachments = async ( req: StorageSubmissionMiddlewareHandlerRequest, res: Parameters[1], next: NextFunction, @@ -208,25 +212,49 @@ export const checkAttachmentQuarantineKeys = async ( return next() } - // // Step 2: If virus scanner is enabled, check if quarantine keys for attachments are present. Quarantine keys - // // should only be present if the virus scanner is enabled on the frontend. - // // If not, skip this middleware. - - // let quarantineKeysPresent = false + // Step 2: If virus scanner is enabled, check if storage submission v2.1+. Storage submission v2.1 onwards + // should have virus scanning enabled. If not, skip this middleware. + // Note: Version number is sent by the frontend and should only be >=2.1 if virus scanning is enabled on the frontend. - // for (const response of req.body.responses) { - // if (isQuarantinedAttachmentResponse(response)) quarantineKeysPresent = true - // } + if (req.body.version < 2.1) return next() - // if (!quarantineKeysPresent) return next() - - // At this point, virus scanner is enabled and quarantine keys are present. This means that both the FE and BE + // At this point, virus scanner is enabled and storage submission v2.1+. This means that both the FE and BE // have virus scanning enabled. // Step 3: Trigger lambda to scan attachments. - triggerVirusScanning('16ca3303-743a-4cec-ab20-3d10042d88ce') + const triggerLambdaResult = await ResultAsync.combine( + req.body.responses.map((response) => { + if (isQuarantinedAttachmentResponse(response)) { + return triggerVirusScanning(response.answer).map((data) => { + logger.info({ + message: 'Successfully invoked lambda function', + meta: { + ...logMeta, + responseMetadata: data?.$metadata, + }, + }) + return okAsync(true) + }) + } + return okAsync(true) + }), + ) + + // If any of the lambda invocations failed, return an errored response. + if (triggerLambdaResult.isErr()) { + logger.error({ + message: 'Error scanning attachments', + meta: logMeta, + error: triggerLambdaResult.error, + }) + + return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send({ + message: + 'Something went wrong while scanning your attachments. Please try again.', + }) + } - // Step 4: Retrieve attachments from the clean bucket. + // TODO(FRM-1302): Step 4: Retrieve attachments from the clean bucket. return next() } 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 08e093cdd8..f635b41e82 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts @@ -60,6 +60,7 @@ import { PRESIGNED_ATTACHMENT_POST_EXPIRY_SECS } from './encrypt-submission.cons import { AttachmentSizeLimitExceededError, InvalidFieldIdError, + VirusScanFailedError, } from './encrypt-submission.errors' import { AttachmentMetadata, @@ -580,28 +581,21 @@ export const getQuarantinePresignedPostData = ( } export const triggerVirusScanning = (quarantineFileKey: string) => { - return AwsConfig.virusScannerLambda.invoke( - { + return ResultAsync.fromPromise( + AwsConfig.virusScannerLambda.invoke({ FunctionName: AwsConfig.virusScannerLambdaFunctionName, Payload: JSON.stringify({ key: quarantineFileKey }), - }, - (error, data) => { - if (error) { - logger.error({ - message: 'Error invoking lambda function', - meta: { - action: 'triggerVirusScanning', - }, - error, - }) - } - logger.info({ - message: 'Successfully invoked lambda function', + }), + (error) => { + logger.error({ + message: 'Error encountered when invoking virus scanning lambda', meta: { action: 'triggerVirusScanning', - data, }, + error, }) + + return new VirusScanFailedError() }, ) } diff --git a/src/app/modules/submission/receiver/receiver.middleware.ts b/src/app/modules/submission/receiver/receiver.middleware.ts index 2850c8f405..114c36d509 100644 --- a/src/app/modules/submission/receiver/receiver.middleware.ts +++ b/src/app/modules/submission/receiver/receiver.middleware.ts @@ -52,8 +52,22 @@ export const receiveEmailSubmission: ControllerHandler< export const receiveStorageSubmission: ControllerHandler< unknown, { message: string }, - { responses: FieldResponse[] } + { responses: FieldResponse[]; version: number } > = async (req, res, next) => { + if (req.body.version >= 2.1) { + const logMeta = { + action: 'receiveSubmission', + ...createReqMeta(req), + } + + logger.info({ + message: 'Skipping multipart receiver since storage submission v2.1+', + meta: logMeta, + }) + + return next() + } + return receiveSubmission( req, res, diff --git a/src/app/modules/submission/submission.utils.ts b/src/app/modules/submission/submission.utils.ts index 7bb8443853..f68353ed27 100644 --- a/src/app/modules/submission/submission.utils.ts +++ b/src/app/modules/submission/submission.utils.ts @@ -201,10 +201,7 @@ export const isAttachmentResponse = ( export const isQuarantinedAttachmentResponse = ( response: ParsedClearFormFieldResponse, ): response is ParsedClearAttachmentResponse => { - return ( - response.fieldType === BasicField.Attachment && - response.fileKey !== undefined - ) + return response.fieldType === BasicField.Attachment } /** diff --git a/src/types/api/submission.ts b/src/types/api/submission.ts index f7597d2ef5..089f7558e2 100644 --- a/src/types/api/submission.ts +++ b/src/types/api/submission.ts @@ -6,7 +6,6 @@ import { AttachmentResponse, FieldResponse } from '../../../shared/types' export type ParsedClearAttachmentResponse = AttachmentResponse & { filename: string content: Buffer - fileKey?: string } export type ParsedClearFormFieldResponse =