TASK22102: slice4 larger file set + production detail suppression
This commit is contained in:
@@ -1403,3 +1403,37 @@ Validation:
|
|||||||
Follow-ups:
|
Follow-ups:
|
||||||
|
|
||||||
- Next slice should target non-proxy file handlers that still return raw `error` payloads.
|
- Next slice should target non-proxy file handlers that still return raw `error` payloads.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CL-038: TASK22102 API contract consistency — Slice 4 (security hardening + larger non-proxy file set)
|
||||||
|
|
||||||
|
date: 2026-03-17
|
||||||
|
author: Cline
|
||||||
|
scope: `pages/api/middleware/apiResponse.js`, `pages/api/file/{createappealcompletemessage_api,updatecase_api,createcaseinvolvement_api,createrepinvolvement_api,createcase_api,generatepdf}.js`
|
||||||
|
type: change
|
||||||
|
rationale: Apply requested security hardening for error detail exposure and deliver a larger non-proxy file-handler slice to improve codex usage efficiency while keeping contract risk low.
|
||||||
|
impact: Suppresses sensitive `details` payloads in production, improves structured API consistency in selected non-proxy file handlers, and preserves existing success-path payload compatibility.
|
||||||
|
status: completed
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- Hardened shared error helper:
|
||||||
|
- `respondError` now omits `error.details` when `NODE_ENV === "production"`.
|
||||||
|
- debug detail visibility remains available in non-production environments.
|
||||||
|
- Migrated a larger batch of non-proxy file handlers to helper-based responses:
|
||||||
|
- `createappealcompletemessage_api`
|
||||||
|
- `updatecase_api`
|
||||||
|
- `createcaseinvolvement_api`
|
||||||
|
- `createrepinvolvement_api`
|
||||||
|
- `createcase_api`
|
||||||
|
- `generatepdf`
|
||||||
|
- Standardized missing-input/hash/failure paths to structured `respondError` codes/messages while retaining success bodies and existing 200 semantics (including 412->record exists handling in involvement endpoints).
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
- `npx next lint --file pages/api/middleware/apiResponse.js --file pages/api/file/createappealcompletemessage_api.js --file pages/api/file/updatecase_api.js --file pages/api/file/createcaseinvolvement_api.js --file pages/api/file/createrepinvolvement_api.js --file pages/api/file/createcase_api.js --file pages/api/file/generatepdf.js` -> pass (no warnings/errors)
|
||||||
|
|
||||||
|
Follow-ups:
|
||||||
|
|
||||||
|
- Continue next larger slice on remaining non-proxy file handlers still returning raw error payloads.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import _ from "lodash";
|
|||||||
|
|
||||||
import nextConnect from "next-connect";
|
import nextConnect from "next-connect";
|
||||||
import middleware from "../middleware/middleware";
|
import middleware from "../middleware/middleware";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const ApiProxy = nextConnect();
|
const ApiProxy = nextConnect();
|
||||||
ApiProxy.use(middleware);
|
ApiProxy.use(middleware);
|
||||||
@@ -30,7 +31,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
typeof checkHash === "undefined" ||
|
typeof checkHash === "undefined" ||
|
||||||
checkHash.length === 0
|
checkHash.length === 0
|
||||||
) {
|
) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "MISSING_REQUIRED_QUERY",
|
||||||
|
message: "container, tempcaseref and hash are required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkquerypath =
|
var checkquerypath =
|
||||||
@@ -40,7 +45,11 @@ ApiProxy.get(async (req, res) => {
|
|||||||
tempCaseRef;
|
tempCaseRef;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "INVALID_HASH",
|
||||||
|
message: "Invalid hash"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const blobProgress = await getProgressBlobs(
|
const blobProgress = await getProgressBlobs(
|
||||||
@@ -107,14 +116,18 @@ ApiProxy.get(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createCaseCompleteMessage(containerName, tempCaseRef);
|
createCaseCompleteMessage(containerName, tempCaseRef);
|
||||||
return res.status(200).json({
|
return respondSuccess(res, {
|
||||||
status: "success"
|
status: "success"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
return res.status(400).json(error);
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "CREATE_APPEAL_COMPLETE_MESSAGE_FAILED",
|
||||||
|
message: "Failed to create appeal complete message"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getCaseBlob, createBlob } from "../../../actions/azurestorage";
|
|||||||
import { getTempCaseRef } from "../../../components/utils";
|
import { getTempCaseRef } from "../../../components/utils";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const WEBAPI_URL =
|
const WEBAPI_URL =
|
||||||
process.env.RELAY_ROOT ||
|
process.env.RELAY_ROOT ||
|
||||||
@@ -33,7 +34,12 @@ export default async function ApiProxy(req, res) {
|
|||||||
typeof lpaID === "undefined" ||
|
typeof lpaID === "undefined" ||
|
||||||
lpaID.length === 0
|
lpaID.length === 0
|
||||||
) {
|
) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "MISSING_REQUIRED_QUERY",
|
||||||
|
message:
|
||||||
|
"contactid, appealTypeId, containername and lpaID are required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
@@ -73,10 +79,14 @@ export default async function ApiProxy(req, res) {
|
|||||||
// data: JSON.stringify(newData),
|
// data: JSON.stringify(newData),
|
||||||
// };
|
// };
|
||||||
var apiResponse = _.isEmpty(req.query)
|
var apiResponse = _.isEmpty(req.query)
|
||||||
? res.status(400).json()
|
? respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "MISSING_REQUIRED_QUERY",
|
||||||
|
message: "Query parameters are required"
|
||||||
|
})
|
||||||
: (getCaseBlob(containerName, tempCaseRef, newData),
|
: (getCaseBlob(containerName, tempCaseRef, newData),
|
||||||
createBlob(JSON.stringify(newAppealObj), containerName),
|
createBlob(JSON.stringify(newAppealObj), containerName),
|
||||||
res.status(200).json(newData));
|
respondSuccess(res, newData));
|
||||||
|
|
||||||
return apiResponse;
|
return apiResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import CryptoJS from "crypto-js";
|
|||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import { consoleLogger } from "../../../actions/core/logger";
|
||||||
import { getToken } from "../../../actions/core/token";
|
import { getToken } from "../../../actions/core/token";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const WEBAPI_URL =
|
const WEBAPI_URL =
|
||||||
process.env.RELAY_ROOT ||
|
process.env.RELAY_ROOT ||
|
||||||
@@ -28,7 +29,11 @@ export default async function ApiProxy(req, res) {
|
|||||||
typeof req.body.incidentid === "undefined" ||
|
typeof req.body.incidentid === "undefined" ||
|
||||||
req.body.incidentid.length === 0
|
req.body.incidentid.length === 0
|
||||||
) {
|
) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "MISSING_REQUIRED_BODY",
|
||||||
|
message: "contactid and incidentid are required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await getToken();
|
var token = await getToken();
|
||||||
@@ -61,12 +66,16 @@ export default async function ApiProxy(req, res) {
|
|||||||
|
|
||||||
return axios(config)
|
return axios(config)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
res.status(200).json(data);
|
return respondSuccess(res, data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return error.status == 412
|
return error.status == 412
|
||||||
? res.status(200).json({ "record": "exists" })
|
? respondSuccess(res, { "record": "exists" })
|
||||||
: (consoleLogger(Object.assign(error, data)),
|
: (consoleLogger(Object.assign(error, data)),
|
||||||
res.status(400).json(error));
|
respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "CREATE_CASE_INVOLVEMENT_FAILED",
|
||||||
|
message: "Failed to create case involvement"
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import CryptoJS from "crypto-js";
|
|||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import { consoleLogger } from "../../../actions/core/logger";
|
||||||
import { getToken } from "../../../actions/core/token";
|
import { getToken } from "../../../actions/core/token";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const WEBAPI_URL =
|
const WEBAPI_URL =
|
||||||
process.env.RELAY_ROOT ||
|
process.env.RELAY_ROOT ||
|
||||||
@@ -28,7 +29,11 @@ export default async function ApiProxy(req, res) {
|
|||||||
typeof req.body.incidentid === "undefined" ||
|
typeof req.body.incidentid === "undefined" ||
|
||||||
req.body.incidentid.length === 0
|
req.body.incidentid.length === 0
|
||||||
) {
|
) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "MISSING_REQUIRED_BODY",
|
||||||
|
message: "contactid and incidentid are required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await getToken();
|
var token = await getToken();
|
||||||
@@ -63,12 +68,16 @@ export default async function ApiProxy(req, res) {
|
|||||||
|
|
||||||
return axios(config)
|
return axios(config)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
res.status(200).json(data);
|
return respondSuccess(res, data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return error.status == 412
|
return error.status == 412
|
||||||
? res.status(200).json({ "record": "exists" })
|
? respondSuccess(res, { "record": "exists" })
|
||||||
: (consoleLogger(Object.assign(error, data)),
|
: (consoleLogger(Object.assign(error, data)),
|
||||||
res.status(400).json(error));
|
respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "CREATE_REP_INVOLVEMENT_FAILED",
|
||||||
|
message: "Failed to create representation involvement"
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ import { statement_pdf } from "../../../components/pdftemplates/statement_pdf";
|
|||||||
import { writtenStatement_pdf } from "../../../components/pdftemplates/writtenStatement_pdf";
|
import { writtenStatement_pdf } from "../../../components/pdftemplates/writtenStatement_pdf";
|
||||||
import { other_pdf } from "../../../components/pdftemplates/other_pdf";
|
import { other_pdf } from "../../../components/pdftemplates/other_pdf";
|
||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import { consoleLogger } from "../../../actions/core/logger";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
//const ApiProxy = nextConnect();
|
//const ApiProxy = nextConnect();
|
||||||
// ApiProxy.use(middleware);
|
// ApiProxy.use(middleware);
|
||||||
|
|
||||||
@@ -183,14 +184,22 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof checkHash === "undefined" || checkHash.length === 0) {
|
if (typeof checkHash === "undefined" || checkHash.length === 0) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "HASH_REQUIRED",
|
||||||
|
message: "hash is required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hashAPIPath(checkquerypath) !=
|
hashAPIPath(checkquerypath) !=
|
||||||
(checkquerypath.indexOf("?") > -1 ? "&hash=" : "?hash=") + checkHash
|
(checkquerypath.indexOf("?") > -1 ? "&hash=" : "?hash=") + checkHash
|
||||||
) {
|
) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "INVALID_HASH",
|
||||||
|
message: "Invalid hash"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("-------", checkHash);
|
//console.log("-------", checkHash);
|
||||||
@@ -349,7 +358,7 @@ export default async function handler(req, res) {
|
|||||||
return res.status(200).send(renderedPDFBuffer);
|
return res.status(200).send(renderedPDFBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return respondSuccess(res, {
|
||||||
status: "success",
|
status: "success",
|
||||||
data: data,
|
data: data,
|
||||||
path: `${caseRef}/${reqBodyobj.repfile_name}/files/${reqBodyobj.repfile_name}.pdf`
|
path: `${caseRef}/${reqBodyobj.repfile_name}/files/${reqBodyobj.repfile_name}.pdf`
|
||||||
@@ -357,6 +366,10 @@ export default async function handler(req, res) {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
res.status(400).json(error);
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "GENERATE_PDF_FAILED",
|
||||||
|
message: "Failed to generate PDF"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import CryptoJS from "crypto-js";
|
|||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import { consoleLogger } from "../../../actions/core/logger";
|
||||||
import { getToken } from "../../../actions/core/token";
|
import { getToken } from "../../../actions/core/token";
|
||||||
import { hashAPIPath } from "../../../actions/core/hash";
|
import { hashAPIPath } from "../../../actions/core/hash";
|
||||||
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const WEBAPI_URL =
|
const WEBAPI_URL =
|
||||||
process.env.RELAY_ROOT ||
|
process.env.RELAY_ROOT ||
|
||||||
@@ -26,7 +27,12 @@ export default async function ApiProxy(req, res) {
|
|||||||
typeof req.body === "undefined" ||
|
typeof req.body === "undefined" ||
|
||||||
req.body === null
|
req.body === null
|
||||||
) {
|
) {
|
||||||
return res.status(400).json();
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "MISSING_REQUIRED_QUERY",
|
||||||
|
message:
|
||||||
|
"updateFormCollection, appealObj, incident and body are required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await getToken();
|
var token = await getToken();
|
||||||
@@ -49,10 +55,14 @@ export default async function ApiProxy(req, res) {
|
|||||||
|
|
||||||
return axios(config)
|
return axios(config)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
res.status(200).json(data);
|
return respondSuccess(res, data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
res.status(400).json(error);
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "UPDATE_CASE_FAILED",
|
||||||
|
message: "Failed to update case"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const respondError = (
|
|||||||
details
|
details
|
||||||
} = {}
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
const payload = {
|
const payload = {
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
@@ -19,7 +20,7 @@ export const respondError = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof details !== "undefined") {
|
if (!isProduction && typeof details !== "undefined") {
|
||||||
payload.error.details = details;
|
payload.error.details = details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user