TASK22102: slice4 larger file set + production detail suppression
This commit is contained in:
@@ -1403,3 +1403,37 @@ Validation:
|
||||
Follow-ups:
|
||||
|
||||
- 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 middleware from "../middleware/middleware";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
|
||||
const ApiProxy = nextConnect();
|
||||
ApiProxy.use(middleware);
|
||||
@@ -30,7 +31,11 @@ ApiProxy.get(async (req, res) => {
|
||||
typeof checkHash === "undefined" ||
|
||||
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 =
|
||||
@@ -40,7 +45,11 @@ ApiProxy.get(async (req, res) => {
|
||||
tempCaseRef;
|
||||
|
||||
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(
|
||||
@@ -107,14 +116,18 @@ ApiProxy.get(async (req, res) => {
|
||||
}
|
||||
|
||||
createCaseCompleteMessage(containerName, tempCaseRef);
|
||||
return res.status(200).json({
|
||||
return respondSuccess(res, {
|
||||
status: "success"
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((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 { v4 as uuidv4 } from "uuid";
|
||||
import { hashAPIPath } from "../../../actions/core/hash";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
@@ -33,7 +34,12 @@ export default async function ApiProxy(req, res) {
|
||||
typeof lpaID === "undefined" ||
|
||||
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 = {
|
||||
@@ -73,10 +79,14 @@ export default async function ApiProxy(req, res) {
|
||||
// data: JSON.stringify(newData),
|
||||
// };
|
||||
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),
|
||||
createBlob(JSON.stringify(newAppealObj), containerName),
|
||||
res.status(200).json(newData));
|
||||
respondSuccess(res, newData));
|
||||
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import CryptoJS from "crypto-js";
|
||||
import { consoleLogger } from "../../../actions/core/logger";
|
||||
import { getToken } from "../../../actions/core/token";
|
||||
import { hashAPIPath } from "../../../actions/core/hash";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
@@ -28,7 +29,11 @@ export default async function ApiProxy(req, res) {
|
||||
typeof req.body.incidentid === "undefined" ||
|
||||
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();
|
||||
@@ -61,12 +66,16 @@ export default async function ApiProxy(req, res) {
|
||||
|
||||
return axios(config)
|
||||
.then(({ data }) => {
|
||||
res.status(200).json(data);
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((error) => {
|
||||
return error.status == 412
|
||||
? res.status(200).json({ "record": "exists" })
|
||||
? respondSuccess(res, { "record": "exists" })
|
||||
: (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 { getToken } from "../../../actions/core/token";
|
||||
import { hashAPIPath } from "../../../actions/core/hash";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
@@ -28,7 +29,11 @@ export default async function ApiProxy(req, res) {
|
||||
typeof req.body.incidentid === "undefined" ||
|
||||
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();
|
||||
@@ -63,12 +68,16 @@ export default async function ApiProxy(req, res) {
|
||||
|
||||
return axios(config)
|
||||
.then(({ data }) => {
|
||||
res.status(200).json(data);
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((error) => {
|
||||
return error.status == 412
|
||||
? res.status(200).json({ "record": "exists" })
|
||||
? respondSuccess(res, { "record": "exists" })
|
||||
: (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 { other_pdf } from "../../../components/pdftemplates/other_pdf";
|
||||
import { consoleLogger } from "../../../actions/core/logger";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
//const ApiProxy = nextConnect();
|
||||
// ApiProxy.use(middleware);
|
||||
|
||||
@@ -183,14 +184,22 @@ export default async function handler(req, res) {
|
||||
}
|
||||
|
||||
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 (
|
||||
hashAPIPath(checkquerypath) !=
|
||||
(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);
|
||||
@@ -349,7 +358,7 @@ export default async function handler(req, res) {
|
||||
return res.status(200).send(renderedPDFBuffer);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
return respondSuccess(res, {
|
||||
status: "success",
|
||||
data: data,
|
||||
path: `${caseRef}/${reqBodyobj.repfile_name}/files/${reqBodyobj.repfile_name}.pdf`
|
||||
@@ -357,6 +366,10 @@ export default async function handler(req, res) {
|
||||
})
|
||||
.catch((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 { getToken } from "../../../actions/core/token";
|
||||
import { hashAPIPath } from "../../../actions/core/hash";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
@@ -26,7 +27,12 @@ export default async function ApiProxy(req, res) {
|
||||
typeof req.body === "undefined" ||
|
||||
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();
|
||||
@@ -49,10 +55,14 @@ export default async function ApiProxy(req, res) {
|
||||
|
||||
return axios(config)
|
||||
.then(({ data }) => {
|
||||
res.status(200).json(data);
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((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
|
||||
} = {}
|
||||
) => {
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const payload = {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -19,7 +20,7 @@ export const respondError = (
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof details !== "undefined") {
|
||||
if (!isProduction && typeof details !== "undefined") {
|
||||
payload.error.details = details;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user