diff --git a/actions/services/documentDirectService.js b/actions/services/documentDirectService.js index d44bac8c..870bd8bc 100644 --- a/actions/services/documentDirectService.js +++ b/actions/services/documentDirectService.js @@ -134,7 +134,7 @@ export const uploadFiles = async ( const config = { method: "post", - url: queryUrl, + url: queryUrl + hashAPIPath(queryUrl), data: formData, headers: { "content-type": "multipart/form-data" } }; @@ -162,7 +162,7 @@ export const uploadSingleFile = async (filesObj, containerID, casefolderID) => { const config = { method: "post", - url: queryUrl, + url: queryUrl + hashAPIPath(queryUrl), data: formData, headers: { "content-type": "multipart/form-data" } }; @@ -191,7 +191,7 @@ export const uploadRepFiles = async ( const config = { method: "post", - url: queryUrl, + url: queryUrl + hashAPIPath(queryUrl), data: formData, headers: { "content-type": "multipart/form-data" } }; diff --git a/actions/services/portalDirectService.js b/actions/services/portalDirectService.js index 39b7b937..63acc1b2 100644 --- a/actions/services/portalDirectService.js +++ b/actions/services/portalDirectService.js @@ -280,7 +280,7 @@ export const sendRepCompleteMessage = async ( caseReference, fileName ) => { - var queryUrl = + var hashQueryPath = "/api/file/createrepcompletemessage_api?container=" + containerID + "&tempcaseref=" + @@ -288,6 +288,8 @@ export const sendRepCompleteMessage = async ( "&repid=" + fileName; + var queryUrl = hashQueryPath + hashAPIPath(hashQueryPath); + var config = { method: "get", url: queryUrl diff --git a/pages/api/file/createrepcompletemessage_api.js b/pages/api/file/createrepcompletemessage_api.js index e55ac766..1f30911a 100644 --- a/pages/api/file/createrepcompletemessage_api.js +++ b/pages/api/file/createrepcompletemessage_api.js @@ -2,6 +2,8 @@ import { getBlobs, createRepCompleteMessage } from "../../../actions/azurestorage"; +import { hashAPIPath } from "../../../actions/core/hash"; +import { consoleLogger } from "../../../actions/core/logger"; import nextConnect from "next-connect"; import middleware from "../middleware/middleware"; @@ -13,26 +15,41 @@ ApiProxy.get(async (req, res) => { var containerName = req.query.container; var tempCaseRef = req.query.tempcaseref; var filename = req.query.repid; + var checkHash = req.query.hash; - //console.log(hashAPIPath(checkquerypath), checkHash); - //console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash); + if ( + typeof containerName === "undefined" || + containerName.length === 0 || + typeof tempCaseRef === "undefined" || + tempCaseRef.length === 0 || + typeof filename === "undefined" || + filename.length === 0 || + typeof checkHash === "undefined" || + checkHash.length === 0 + ) { + return res.status(400).json(); + } - //if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) { - await createRepCompleteMessage(containerName, tempCaseRef, filename).then( - (data) => { - console.log( - "/////Create Rep complete Message:\n" + tempCaseRef, - "\n" + "insertedOn:" + data.insertedOn, - "\n" + "messageId:" + data.messageId, - "\n" + "response.status:" + data._response.status, - "\n//////////////" - ); + var checkquerypath = + "/api/file/createrepcompletemessage_api?container=" + + containerName + + "&tempcaseref=" + + tempCaseRef + + "&repid=" + + filename; + + if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) { + return res.status(400).json(); + } + + await createRepCompleteMessage(containerName, tempCaseRef, filename) + .then((data) => { return res.status(200).json(data); - } - ); - // } else { - // return res.status(400).json(); - // } + }) + .catch((error) => { + consoleLogger(error); + return res.status(400).json(); + }); }); export const config = { diff --git a/pages/api/file/setupcontainer.js b/pages/api/file/setupcontainer.js index 86aa9bf0..1d251c2f 100644 --- a/pages/api/file/setupcontainer.js +++ b/pages/api/file/setupcontainer.js @@ -17,10 +17,17 @@ ApiProxy.use(middleware); ApiProxy.get(async (req, res) => { var containerName = req.query.ident; var checkHash = req.query.hash; - var checkquerypath = "/api/file/setupcontainer?ident=" + containerName; - //console.log(checkquerypath, hashAPIPath(checkquerypath), checkHash); - //console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash); + if ( + typeof containerName === "undefined" || + containerName.length === 0 || + typeof checkHash === "undefined" || + checkHash.length === 0 + ) { + return res.status(400).json(); + } + + var checkquerypath = "/api/file/setupcontainer?ident=" + containerName; if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) { await createContainer(containerName) @@ -32,11 +39,6 @@ ApiProxy.get(async (req, res) => { res.status(400).json(error); }); } else { - consoleLogger({ - name: "setupcontainer", - code: "bad hash", - query: JSON.stringify(req.query) - }); return res.status(400).json(); } }); diff --git a/pages/api/file/upload.js b/pages/api/file/upload.js index 4219319a..2d8afbd2 100644 --- a/pages/api/file/upload.js +++ b/pages/api/file/upload.js @@ -1,8 +1,9 @@ import { createBlob, createRepBlob, - uploadFile, + uploadFile } from "../../../actions/azurestorage"; +import { hashAPIPath } from "../../../actions/core/hash"; import nextConnect from "next-connect"; import middleware from "../middleware/middleware"; @@ -12,26 +13,26 @@ ApiProxy.use(middleware); ApiProxy.post(async (req, res) => { var checkHash = req.query.hash; - //console.log(JSON.stringify(req.body)); - //console.log(JSON.stringify(req.body.appealData)); - //console.log(req.files); + + var checkquerypath = "/api/file/upload"; + + if (hashAPIPath(checkquerypath) != "?hash=" + checkHash) { + return res.status(400).json(); + } const appealData = req.body.appealData; - const containerID = req.body.containerID[0]; - const casefolderID = req.body.casefolderID[0]; + const containerID = req.body?.containerID?.[0]; + const casefolderID = req.body?.casefolderID?.[0]; const repOrAppeal = req.body.repOrAppeal || false; - console.log("there are files:", Object.keys(req.files).length); - - // var checkquerypath = "/api/file/upload"; - - //console.log(hashAPIPath(checkquerypath), checkHash); - //console.log(hashAPIPath(checkquerypath) == "?hash=" + checkHash); - - // if (hashAPIPath(checkquerypath) == "?hash=" + checkHash) { - //createContainer(containerID).then((containerName) => { - - console.log("does this get folder name:", containerID, casefolderID); + if ( + typeof containerID === "undefined" || + containerID.length === 0 || + typeof casefolderID === "undefined" || + casefolderID.length === 0 + ) { + return res.status(400).json(); + } repOrAppeal ? createRepBlob(appealData, containerID, casefolderID).then((data) => { @@ -41,7 +42,6 @@ ApiProxy.post(async (req, res) => { // return res.status(200).json({ data }); // } // ); - console.log("================================\nRepfile updated"); return res.status(200).json({ data }); }) : createBlob(appealData, containerID, casefolderID).then((data) => { @@ -51,22 +51,14 @@ ApiProxy.post(async (req, res) => { // return res.status(200).json({ data }); // } // ); - console.log( - "================================\nAppeal file updated" - ); return res.status(200).json({ data }); }); - //}); - - // } else { - // return res.status(400).json(); - // } }); export const config = { api: { - bodyParser: false, - }, + bodyParser: false + } }; export default ApiProxy; diff --git a/pages/api/file/uploadsinglefile.js b/pages/api/file/uploadsinglefile.js index cd8e45ff..e0ebc854 100644 --- a/pages/api/file/uploadsinglefile.js +++ b/pages/api/file/uploadsinglefile.js @@ -7,6 +7,7 @@ import { import nextConnect from "next-connect"; import middleware from "../middleware/middleware"; import { consoleLogger } from "../../../actions/core/logger"; +import { hashAPIPath } from "../../../actions/core/hash"; import { fileTypeFromBuffer } from "file-type"; import fs from "fs"; import path from "path"; @@ -36,11 +37,25 @@ ApiProxy.use(middleware); ApiProxy.post(async (req, res) => { var checkHash = req.query.hash; + var checkquerypath = "/api/file/uploadsinglefile"; - const containerID = req.body.containerID[0]; - const casefolderID = req.body.casefolderID[0]; + if (hashAPIPath(checkquerypath) != "?hash=" + checkHash) { + return res.status(400).json(); + } - console.log("there are files:", Object.keys(req.files).length); + const containerID = req.body?.containerID?.[0]; + const casefolderID = req.body?.casefolderID?.[0]; + + if ( + typeof containerID === "undefined" || + containerID.length === 0 || + typeof casefolderID === "undefined" || + casefolderID.length === 0 + ) { + return res.status(400).json(); + } + + const uploadedFiles = req.files || {}; // Add other mimetypes here const allowedMimeTypes = [ @@ -57,7 +72,7 @@ ApiProxy.post(async (req, res) => { var allowedFilesFormData = {}; var invalidFiles = []; // To store the names of invalid files - for (const [fileName, fileDetails] of Object.entries(req.files)) { + for (const [fileName, fileDetails] of Object.entries(uploadedFiles)) { const file = fileDetails[0]; const filePath = file.path; @@ -65,9 +80,6 @@ ApiProxy.post(async (req, res) => { const fnCheck = validateFilenameServer(file.originalFilename); if (!fnCheck.ok) { - console.log( - `File ${file.originalFilename} rejected: ${fnCheck.reason}` - ); invalidFiles.push(`${file.originalFilename} - Invalid filename`); continue; // do not process further } @@ -80,7 +92,6 @@ ApiProxy.post(async (req, res) => { try { const type = await fileTypeFromBuffer(buffer); // Correct usage of fileTypeFromBuffer - console.log("checking mime type ", type); if (type && allowedMimeTypes.includes(type.mime)) { // If the MIME type from the file signature matches the allowed list allowedFilesFormData[fileName] = fileDetails.map( @@ -94,21 +105,12 @@ ApiProxy.post(async (req, res) => { }) ); } else { - console.log( - `\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n\nFile ${fileName} has an invalid MIME type based on its content.\n\n\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` - ); invalidFiles.push(fileName); // Track invalid file } } catch (error) { - console.log( - `Error reading file ${fileName} for MIME type validation`, - error - ); + consoleLogger(error); } } else { - console.log( - `\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n\nFile ${fileName} has an unsupported MIME type: ${fileMimeType}\n\n\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` - ); invalidFiles.push(fileName); // Track invalid file } } diff --git a/tests/phase9/service-behaviour.test.cjs b/tests/phase9/service-behaviour.test.cjs new file mode 100644 index 00000000..c5059efe --- /dev/null +++ b/tests/phase9/service-behaviour.test.cjs @@ -0,0 +1,250 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const assert = require("assert"); + +const rootDir = path.resolve(__dirname, "..", ".."); + +const loadModule = (relativePath, injected = {}) => { + const filePath = path.join(rootDir, relativePath); + let source = fs.readFileSync(filePath, "utf8"); + + source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); + source = source.replace( + /export default async function\s+(\w+)\s*\(/, + "async function $1(" + ); + source = source.replace(/export const\s+/g, "const "); + source = source.replace( + /export default\s+(\w+);/g, + "module.exports.default = $1;" + ); + + source += + '\nif (typeof ApiProxy !== "undefined" && !module.exports.default) module.exports.default = ApiProxy;\n'; + + const context = { + module: { exports: {} }, + exports: {}, + require, + process, + console: { + log: () => {}, + info: () => {}, + warn: () => {}, + error: () => {} + }, + ...injected + }; + + vm.runInNewContext(source, context, { filename: filePath }); + return context.module.exports; +}; + +const createNextConnectMock = () => { + const router = { + handler: null, + use: () => {}, + get(fn) { + this.handler = fn; + }, + post(fn) { + this.handler = fn; + } + }; + + return () => router; +}; + +const createRes = () => { + const state = { + statusCode: null, + jsonBody: undefined + }; + + return { + state, + status(code) { + state.statusCode = code; + return this; + }, + json(payload) { + state.jsonBody = payload; + return payload; + } + }; +}; + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +test("createrepcompletemessage_api rejects missing hash with 400", async () => { + const calls = []; + const nextConnect = createNextConnectMock(); + + const mod = loadModule("pages/api/file/createrepcompletemessage_api.js", { + hashAPIPath: () => "&hash=expected", + consoleLogger: () => {}, + createRepCompleteMessage: async (...args) => { + calls.push(args); + return { ok: true }; + }, + nextConnect, + middleware: () => {} + }); + + const req = { + query: { + container: "c1", + tempcaseref: "temp-1", + repid: "rep-1" + } + }; + const res = createRes(); + + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(calls.length, 0); +}); + +test("upload rejects missing hash with 400", async () => { + const blobCalls = []; + const nextConnect = createNextConnectMock(); + + const mod = loadModule("pages/api/file/upload.js", { + hashAPIPath: () => "?hash=expected", + createBlob: async (...args) => { + blobCalls.push(args); + return { ok: true }; + }, + createRepBlob: async (...args) => { + blobCalls.push(args); + return { ok: true }; + }, + uploadFile: async () => {}, + nextConnect, + middleware: () => {} + }); + + const req = { + query: {}, + body: { + appealData: {}, + containerID: ["c1"], + casefolderID: ["case-1"] + }, + files: {} + }; + const res = createRes(); + + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(blobCalls.length, 0); +}); + +test("uploadsinglefile rejects invalid hash with 400", async () => { + const uploadCalls = []; + const nextConnect = createNextConnectMock(); + + const mod = loadModule("pages/api/file/uploadsinglefile.js", { + hashAPIPath: () => "?hash=expected", + uploadSingleFile: async (...args) => { + uploadCalls.push(args); + return { ok: true }; + }, + consoleLogger: () => {}, + fileTypeFromBuffer: async () => ({ mime: "application/pdf" }), + fs: { readFileSync: () => Buffer.from("file") }, + path: { basename: (value) => value }, + nextConnect, + middleware: () => {} + }); + + const req = { + query: { hash: "wrong" }, + body: { containerID: ["c1"], casefolderID: ["case-1"] }, + files: {} + }; + const res = createRes(); + + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(uploadCalls.length, 0); +}); + +test("setupcontainer rejects missing ident with 400", async () => { + const createCalls = []; + const nextConnect = createNextConnectMock(); + + const mod = loadModule("pages/api/file/setupcontainer.js", { + hashAPIPath: () => "&hash=expected", + createContainer: async (...args) => { + createCalls.push(args); + return { ok: true }; + }, + createContainerSas: async () => {}, + getContainers: async () => {}, + getBlobs: async () => {}, + uploadFile: async () => {}, + consoleLogger: () => {}, + nextConnect, + middleware: () => {} + }); + + const req = { query: { hash: "expected" } }; + const res = createRes(); + + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(createCalls.length, 0); +}); + +test("setupcontainer rejects invalid hash with 400", async () => { + const createCalls = []; + const nextConnect = createNextConnectMock(); + + const mod = loadModule("pages/api/file/setupcontainer.js", { + hashAPIPath: () => "&hash=expected", + createContainer: async (...args) => { + createCalls.push(args); + return { ok: true }; + }, + createContainerSas: async () => {}, + getContainers: async () => {}, + getBlobs: async () => {}, + uploadFile: async () => {}, + consoleLogger: () => {}, + nextConnect, + middleware: () => {} + }); + + const req = { query: { ident: "container-1", hash: "wrong" } }; + const res = createRes(); + + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(createCalls.length, 0); +}); + +const run = async () => { + let passed = 0; + + for (const currentTest of tests) { + await currentTest.fn(); + passed += 1; + } + + console.log( + `Phase 9 behavioural tests passed (${passed}/${tests.length}).` + ); +}; + +run().catch((error) => { + console.error(error); + process.exit(1); +});