Merged PR 2167: APi hardening

Related work items: #22019
This commit is contained in:
Robert Bond
2026-03-13 14:24:39 +00:00
34 changed files with 4360 additions and 144 deletions
+21 -1
View File
@@ -3,6 +3,26 @@ import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { hashAPIPath } from "../core/hash";
const buildHashedQueryUrl = async (queryUrl) => {
try {
const signRes = await axios.get(
"/api/endpoint/gethash_api?path=" + encodeURIComponent(queryUrl)
);
if (!signRes?.data?.hash) {
throw new Error("Hash signature unavailable");
}
return queryUrl + signRes.data.hash;
} catch (error) {
if (typeof window === "undefined" && process.env.HASHKEY) {
return queryUrl + hashAPIPath(queryUrl);
}
throw error;
}
};
export const getPersonalAccount = (contactid) => {
return axios
.get(
@@ -105,7 +125,7 @@ export const getPortalLogin = async (emailAddress) => {
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
return axios
.get(BASE_URL + queryUrl + hashAPIPath(queryUrl))
.get(BASE_URL + (await buildHashedQueryUrl(queryUrl)))
.then((res) => res.data)
.catch((error) => {
consoleLogger(error);
+29 -3
View File
@@ -3,6 +3,26 @@ import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { hashAPIPath } from "../core/hash";
const buildHashedQueryUrl = async (queryUrl) => {
try {
const signRes = await axios.get(
"/api/endpoint/gethash_api?path=" + encodeURIComponent(queryUrl)
);
if (!signRes?.data?.hash) {
throw new Error("Hash signature unavailable");
}
return queryUrl + signRes.data.hash;
} catch (error) {
if (typeof window === "undefined" && process.env.HASHKEY) {
return queryUrl + hashAPIPath(queryUrl);
}
throw error;
}
};
export const getAwaitingSubmissionFromBlob = (containerName) => {
return axios
.get(
@@ -132,9 +152,11 @@ export const uploadFiles = async (
var queryUrl = "/api/file/upload";
const hashedUrl = await buildHashedQueryUrl(queryUrl);
const config = {
method: "post",
url: queryUrl,
url: hashedUrl,
data: formData,
headers: { "content-type": "multipart/form-data" }
};
@@ -160,9 +182,11 @@ export const uploadSingleFile = async (filesObj, containerID, casefolderID) => {
var queryUrl = "/api/file/uploadsinglefile";
const hashedUrl = await buildHashedQueryUrl(queryUrl);
const config = {
method: "post",
url: queryUrl,
url: hashedUrl,
data: formData,
headers: { "content-type": "multipart/form-data" }
};
@@ -189,9 +213,11 @@ export const uploadRepFiles = async (
var queryUrl = "/api/file/upload";
const hashedUrl = await buildHashedQueryUrl(queryUrl);
const config = {
method: "post",
url: queryUrl,
url: hashedUrl,
data: formData,
headers: { "content-type": "multipart/form-data" }
};
+28 -3
View File
@@ -3,6 +3,26 @@ import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { hashAPIPath } from "../core/hash";
const buildHashedQueryUrl = async (queryUrl) => {
try {
const signRes = await axios.get(
"/api/endpoint/gethash_api?path=" + encodeURIComponent(queryUrl)
);
if (!signRes?.data?.hash) {
throw new Error("Hash signature unavailable");
}
return queryUrl + signRes.data.hash;
} catch (error) {
if (typeof window === "undefined" && process.env.HASHKEY) {
return queryUrl + hashAPIPath(queryUrl);
}
throw error;
}
};
export const getMyCases = (loggedInUserId) => {
return axios
.get(
@@ -234,8 +254,11 @@ export const sendCaseCompleteMessage = async (
"&tempcaseref=" +
caseReference +
"&inv=" +
inv +
hashAPIPath(hashQueryPath);
inv;
var signedQueryUrl = await buildHashedQueryUrl(hashQueryPath);
queryUrl = queryUrl + signedQueryUrl.replace(hashQueryPath, "");
var config = {
method: "get",
@@ -280,7 +303,7 @@ export const sendRepCompleteMessage = async (
caseReference,
fileName
) => {
var queryUrl =
var hashQueryPath =
"/api/file/createrepcompletemessage_api?container=" +
containerID +
"&tempcaseref=" +
@@ -288,6 +311,8 @@ export const sendRepCompleteMessage = async (
"&repid=" +
fileName;
var queryUrl = await buildHashedQueryUrl(hashQueryPath);
var config = {
method: "get",
url: queryUrl
+5 -1
View File
@@ -20,7 +20,11 @@ import fieldLookup from "../../data/crmfieldlookuptranslations.json";
import pickListLookup from "../../data/picklistLookups.json";
import dynamic from "next/dynamic";
import { bytesToSize, getThumbnailIconByExtension } from "../utils";
import {
bytesToSize,
getThumbnailIconByExtension,
updateLinks
} from "../utils";
import { setFileCount } from "../../store/appealType/action";
import {
+479
View File
@@ -0,0 +1,479 @@
# Active Context — PEDW FrontEnd
## Current development focus (from recent commits)
- Search/case navigation correctness, especially breadcrumb and back-link behavior.
- My Portal "view all" and DNS application path handling.
- Welsh/English email behavior for specific notification templates.
- PDF output formatting and hyperlink behavior.
## Recently changed areas (high signal)
- `components/breadcrumbs.js`
- `components/case/summary.js`
- `components/search/searchresults.js`
- `components/search/addresssearchresults.js`
- `components/myportal/viewall.js`
- `pages/myportal/case/[ticketnumber].js`
- `pages/api/email/notify.js`
- `actions/index.js`
- `actions/core/env.js`
- `actions/core/logger.js`
- `actions/core/hash.js`
- `actions/core/token.js`
- `actions/core/headers.js`
- `actions/services/legacyActionsService.js`
- `i18n.js`
## Active concerns
- Breadcrumb and route-state logic is complex and query-dependent (`va`, `adv`, `ads`, `key`), so regressions are easy.
- Locale-specific route and label behavior is still a high-risk area due to rewrite + component logic coupling.
- Sensitive logging remains present in auth/email/actions paths and should be reduced/redacted over time.
- Hash validation on some file endpoints appears inconsistent (some checks active, some commented), requiring careful change discipline.
## Refactor status update (2026-03-12)
- Priority 1 refactor (Phase 1) is in place:
- `actions/index.js` now acts as a compatibility barrel.
- Core helper concerns were extracted into `actions/core/*`.
- Existing API/file/domain wrappers were moved into `actions/services/legacyActionsService.js` with backward-compatible exports retained via the barrel.
- No intentional behavior changes were introduced in this phase; focus was structural risk reduction.
- Priority 1 refactor (Phase 2) is now in place:
- Domain service grouping modules added under `actions/services/*`.
- `actions/services/index.js` now provides grouped service barrel exports.
- `actions/index.js` now re-exports from `./services` and `./core/*` while preserving existing public action names.
- `legacyActionsService` remains as a compatibility backing module and should be slimmed in later increments as consumers move to direct service imports.
## Refactor status update (Priority 1 execution pass — 2026-03-12)
- Completed an additional Priority 1 consumer-migration pass focused on high-use portal/form helpers.
- Updated these files to use focused service/core imports instead of broad `actions` barrel imports:
- `components/case/representation/representationElements.js`
- `components/elements/index.js`
- `components/utils/index.js`
- `lib/newappeal/loadNewAppealPage.js`
- `lib/myportal/loadMyPortalAppealPage.js`
- `components/myportal/topthree.js`
- `components/myportal/topthree_reps.js`
- `components/myportal/awaitingsubmissionfromblob.js`
- Compatibility model remains in place via `actions/index.js`; migration is incremental and non-breaking by design.
## Refactor status update (Priority 1 execution pass — viewall/search/unsubscribe)
- Completed another focused import migration pass to reduce broad `actions` barrel coupling in user-facing high-use areas:
- `components/myportal/viewall.js`
- `components/search/addresssearchresults.js`
- `pages/unsubscribe/[watchlistid].js`
- `pages/unsubscribeall/[watchlistid].js`
- These files now use explicit imports from `actions/services/*` and `actions/core/*` modules.
- Remaining non-comment broad `actions` imports are now primarily in selected `pages/api/endpoint/*` and `pages/api/file/*` handlers.
## Refactor status update (Priority 1 execution pass — endpoint/file proxies)
- Completed focused import migration for selected endpoint and file proxy handlers:
- `pages/api/endpoint/getmylpacases_api.js`
- `pages/api/endpoint/getbasicdnssearchpaged_api.js`
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
- Replaced broad `../../../actions` imports with focused `actions/core/*` and service-module imports.
- Eliminated duplicated local hash helper in `getmylpacases_api.js` by reusing shared `actions/core/hash`.
- Current broad-import scan indicates only comment-only legacy references remain.
## Delivery handover status (2026-03-13)
- Current import-migration wave has been checked in and completed through PR.
- This branch is now treated as the completed migration baseline.
- Next implementation chunk should start on a **new branch** and target decomposition of `actions/services/legacyActionsService.js`.
- Immediate focus for next branch:
1. extract low-risk reference/search wrappers into direct service/client modules,
2. migrate portal/document internals in small parity-checked batches,
3. slim compatibility layer once parity is proven.
## Phase 5 kickoff status (2026-03-13)
- Branch in progress: `TASK21997-phase5-legacyactions-split`.
- First decomposition slice completed:
- search wrappers extracted to `actions/services/searchDirectService.js`
- reference-data wrappers extracted to `actions/services/referenceDataDirectService.js`
- `searchService` and `referenceDataService` now route through these direct modules.
- `legacyActionsService` remains in place for non-migrated domains (portal/document/account/case/admin/notify/integration) pending next slices.
## Phase 5 status update (2026-03-13 — document slice)
- Added `actions/services/documentDirectService.js` and migrated `documentService.js` to route through it.
- `legacyActionsService` now remains for:
- portal, account, case, admin, notify, integration domains.
- Search/reference/document grouped services now point at dedicated direct modules.
## Phase 5 completion status (2026-03-13)
- Grouped services now route through focused direct modules for all domains:
- search, reference-data, document, portal, account, case, admin, integration, notify.
- `actions/services/legacyActionsService.js` has been removed.
- Current architecture baseline:
- `actions/index.js` -> compatibility barrel re-exporting `actions/services` + core modules
- `actions/services/*Service.js` -> grouped public service boundaries
- `actions/services/*DirectService.js` -> implementation modules per domain
- Validation checkpoints completed:
- zero `legacyActionsService` imports in `actions/services/*.js`
- lint passes with warnings only
- build succeeds
## Phase 6 hardening status (2026-03-13 — in progress)
- Working branch: `TASK21998-phase6-postphase5-hardening` (created from `SIPS-Development`).
- Main hardening actions completed so far:
- Added focused parity test harness:
- `tests/phase6/service-parity.test.cjs`
- verifies grouped service import/export parity and `actions/services/index.js` re-export stability.
- Added shared error-path helper:
- `actions/services/httpServiceUtils.js`
- consolidates repeated `consoleLogger + error.response` and `ErrResponse` generation patterns.
- Applied safe duplication reduction in:
- `searchDirectService.js`
- `referenceDataDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- Logging cleanup for sensitive/noisy service paths (removed debug `console.log` usage) in:
- `searchDirectService.js`
- `documentDirectService.js`
- `portalDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
### Current validation evidence
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `npm run lint` -> warnings only
- Targeted local smoke snapshots:
- `GET /advancedsearch` and `GET /cy/advancedsearch` -> 200
- `GET /case` and `GET /cy/case` -> 200
- `GET /myportal` and `GET /cy/myportal` -> 307 redirect to `/auth/signin` (unauthenticated negative path)
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> 400 (negative path)
- `POST /api/email/notify` with empty payload -> 400 (negative path)
### Active caveat
- `GET /searchresults` and `GET /cy/searchresults` return 500 in current local dev run due pre-existing SSR serialization issue:
- `initialState.search.searchString` is undefined in `getServerSideProps` payload.
- captured in `/tmp/phase6-dev.log`.
## Phase 7 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK21988a-phase7-postphase6-hardening`
- Pre-existing search results SSR serialization issue now addressed:
- `pages/searchresults.js`
- changed `setSearch(query.q)` to `setSearch(query?.q || "")` in `getServerSideProps`.
- local smoke result: both `/searchresults` and `/cy/searchresults` return 200 in this run.
- Behavioural test expansion delivered for remaining service domains:
- `tests/phase7/service-behaviour.test.cjs`
- covers document, portal, account, notify, integration.
### Phase 7 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `npm run lint` -> warnings only (existing react-hooks dependency warnings; no new lint errors)
### Phase 7 smoke/negative-path snapshot
- Dev server auto-bound to `localhost:3001` (3000 already in use).
- `GET /searchresults`, `GET /cy/searchresults` -> 200
- `GET /case`, `GET /cy/case` -> 200
- `GET /myportal`, `GET /cy/myportal` -> 307 redirect to `/auth/signin`
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> 400
- `POST /api/email/notify` with `{}` -> 400
### Testing strategy decision (captured)
- Current phase includes **both**:
1. migration/parity guard tests,
2. focused behavioural unit tests for critical response/error contracts.
- Broader behavioural coverage remains a follow-on expansion item after this focused baseline.
## Phase 8 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK22017-phase8-hardening-slice`
- Sensitive API hardening slice completed across:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
- Hash/integrity posture updates:
- re-enabled hash validation on previously bypassed/commented file handlers
- added standardized early 400 handling for missing/invalid hash and missing key params
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed noisy direct `console.log` usage in `createappealcompletemessage_api`
- routed error paths through `consoleLogger` in updated sensitive flow
- Required consumer parity updates applied:
- `actions/services/documentDirectService.js`
- now appends hash for `deleteblobcase` and `deleteblobrep` calls
- `actions/services/portalDirectService.js`
- now appends hash for `createappealcompletemessage_api` call using existing hash path contract
### Phase 8 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 8 manual HTTP snapshot
- Dev server on `http://localhost:3001`.
- Negative-path checks for invalid/missing hash (and missing required params where relevant) return **400** across all 4 target handlers.
- Valid-hash spot-check for `getportallogin_api` reached hash-validated path but still returned **400** due local upstream relay/CRM dependency behavior.
## Phase 9 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
- Hash/integrity posture updates:
- re-enabled/enforced hash checks in handlers where hash validation was bypassed/commented
- standardized early 400 handling for missing/invalid hash and missing key query/body values
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed noisy `console.log` traces from sensitive upload/rep-complete message paths
- retained centralized error logging via `consoleLogger` where present
- Required caller parity updates applied:
- `actions/services/portalDirectService.js`
- `sendRepCompleteMessage` now appends hash
- `actions/services/documentDirectService.js`
- `uploadFiles`, `uploadRepFiles`, `uploadSingleFile` now append hash
### Phase 9 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 9 manual HTTP snapshot
- Negative-path checks for missing/invalid hash and invalid/missing key params on all 4 phase-9 handlers return **400** on local server (`localhost:3000`).
- Valid-hash happy-path spot-check completed using `.env.local` hash key:
- `POST /api/file/uploadsinglefile?hash=<valid>` -> **200**
## Phase 10 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query inputs and hash
- standardized early hash-mismatch 400 behaviour in all 4 handlers
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed old commented debug traces in touched handlers
- Caller parity:
- no additional service-layer updates required in this slice; existing direct services already append hash for these APIs.
### Phase 10 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 10 manual HTTP snapshot
- Missing-hash negative-path checks for all 4 handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `getbloblist` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 11 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query inputs and hash
- standardized early hash-mismatch 400 behaviour in all touched handlers
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed old commented debug traces in touched handlers
- Caller parity:
- no additional service-layer updates required in this slice.
### Phase 11 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 11 manual HTTP snapshot
- Missing/invalid-hash negative-path checks for selected handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `getbloblist` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 12 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query inputs
- standardized early hash-mismatch 400 behaviour in `downloadblob`
- preserved response-shape contracts and function signatures
### Phase 12 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 12 manual HTTP snapshot
- Missing/invalid-input negative-path checks for selected handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `downloadblob` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 13 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query/body inputs
- standardized early hash-mismatch 400 behaviour in delete-blob handlers
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed noisy direct body/query logging from involvement handlers
### Phase 13 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 13 manual HTTP snapshot
- Missing/invalid-input negative-path checks for selected handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `deleteblobcase` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 14 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Addressed browser runtime error in authenticated new-appeal journey:
- `TypeError` in `hashAPIPath` caused by client-side code attempting to parse server-only `HASHKEY`.
- Implemented server-side hash signing bridge for browser callers:
- new endpoint `pages/api/endpoint/gethash_api.js`
- session-gated (`getSession`), allow-listed supported API prefixes, returns `{ hash }`.
- Updated direct service callers to use signer endpoint instead of direct client hash computation:
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- Maintained contract compatibility for target file APIs and existing response shapes.
### Phase 14 validation evidence snapshot
- phase6 parity/behaviour tests -> pass
- phase7/8/9/10/11/12/13 behaviour tests -> pass
- phase14 behaviour tests -> pass (4/4)
- lint -> warnings only (pre-existing hook dependency warnings)
## Phase 14 closeout follow-up status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Browser-safe hash signing expanded for portal login flow:
- `actions/services/accountDirectService.js`
- `getPortalLogin` now requests signed hash from `/api/endpoint/gethash_api`
- server fallback retained for non-browser contexts when `HASHKEY` is present
- `pages/api/endpoint/gethash_api.js`
- allow-list now includes `/api/endpoint/getportallogin_api`
- Contract alignment + runtime fix updates:
- `pages/api/file/getrepsblob.js`
- removed hard requirement for `casefolderID` to match actual route contract (`container + hash`)
- `components/elements/index.js`
- restored missing `updateLinks` import required by Quill-related runtime path
### Follow-up validation evidence
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase14/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- full previously-run phase6phase14 suite remains passing
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` set)
### Active risk posture after follow-up
- Signer endpoint scope remains controlled by strict allow-list (single new path added).
- Response-shape contract stability preserved for touched handlers/services.
- Remaining risk is primarily external dependency behavior on valid-hash happy paths in local environments (relay/storage), not hash-bypass behavior.
## Likely next steps
1. Stabilize and simplify breadcrumb/back-link decision logic with focused regression checks.
2. Standardize hash-check enforcement patterns across file and endpoint APIs.
3. Reduce verbose logging in sensitive flows (auth/email/file/account).
4. Expand automated tests for navigation and representation eligibility edge-cases (currently minimal `tests/`).
See `memory-bank/refactor-backlog.md` for the prioritized refactor plan and sequencing.
Priority 1 detailed execution plan is documented in `memory-bank/refactor-plan-actions-index.md`.
## Areas currently important for contributors
- `components/breadcrumbs.js`
- `components/case/summary.js`
- `pages/api/endpoint/**`
- `pages/api/file/**`
- `pages/api/auth/[...nextauth].js`
- `pages/api/email/notify.js`
- `next.config.js`, `i18n.js`, `locales/**`
+931
View File
@@ -0,0 +1,931 @@
# Change Log (AI/Human Curated)
## Entry Template
```
date: YYYY-MM-DD
author: <agent|name>
scope: <files/routes/features>
type: change
rationale: <why change was made>
impact: <user/system/security/i18n/a11y>
status: completed|rolled-back|partial
Summary:
Validation:
Follow-ups:
```
---
### CL-001: Initial AI Enablement Bundle
date: 2026-03-11
author: Cline
scope: `.clinerules`, `ai-prompts/`, `context/`, `memory-bank/`
type: change
rationale: Establish consistent AI collaboration, delivery guardrails, and durable project context.
impact: Higher consistency/safety in future changes, especially around auth/i18n/public-service reliability.
status: completed
Summary:
Created repository-specific governance, prompt templates, context documentation, and memory protocols with seed entries.
Validation:
- File structure and contents created in-repo.
- Content aligned to detected stack and key paths (`next-auth`, Prisma, middleware, i18n rewrites, Redux HYDRATE/persist).
Follow-ups:
- Confirm owner assignments for open questions.
- Add automation checks to enforce key guardrails over time.
---
### CL-002: Priority 1 Refactor (Phase 1) — `actions/index.js` split foundation
date: 2026-03-12
author: Cline
scope: `actions/index.js`, `actions/core/*`, `actions/services/legacyActionsService.js`, `actions/clients/README.md`, `memory-bank/*`
type: change
rationale: Reduce coupling in the actions monolith by extracting core helpers while keeping backward-compatible exports for existing consumers.
impact: Lower regression blast radius for future actions-related changes; no intended functional behavior change.
status: completed
Summary:
Extracted helper concerns from `actions/index.js` into focused `actions/core` modules (`env`, `logger`, `hash`, `token`, `headers`). Replaced `actions/index.js` with a compatibility barrel and moved operational wrappers into `actions/services/legacyActionsService.js` with imports from core helpers.
Validation:
- Branch created: `0000-update-actions-monolith`.
- Compatibility preserved by re-exporting legacy service functions plus core exports from `actions/index.js`.
- Lint run for regression check.
Follow-ups:
- Phase 2: split `legacyActionsService` by domain into dedicated service/client modules.
- Add helper-focused automated tests for core modules and export parity checks.
---
### CL-003: Priority 1 Refactor (Phase 2) — service grouping via compatibility layer
date: 2026-03-12
author: Cline
scope: `actions/services/*`, `actions/index.js`, `memory-bank/*`
type: change
rationale: Continue reducing monolith risk by introducing domain service boundaries while preserving public action exports.
impact: Maintains backward compatibility for existing `../actions` imports; improves maintainability and safer future migration to targeted imports.
status: completed
Summary:
Added grouped service modules (`searchService`, `caseService`, `accountService`, `portalService`, `documentService`, `referenceDataService`, `notifyService`, `adminService`, `integrationService`) plus `actions/services/index.js` barrel. Updated `actions/index.js` to export from `./services` (and core modules), keeping legacy function names/signatures available.
Validation:
- Service export coverage check confirms all legacy exports are represented in grouped services.
- `npm run lint` still fails due legacy Next/ESLint option incompatibility (tooling issue, unchanged by this refactor).
Follow-ups:
- Incrementally migrate high-churn consumers to direct service imports.
- Add automated export-parity and helper unit tests.
---
### CL-004: Priority 1 Refactor (Phase 3) — targeted consumer import migration
date: 2026-03-12
author: Cline
scope: `components/case/summary.js`, `pages/api/email/notify.js`, selected `pages/api/endpoint/*`, `memory-bank/*`
type: change
rationale: Begin replacing broad `actions` barrel imports with focused service/core module imports in high-churn and representative endpoint areas.
impact: Lower coupling to monolithic action entrypoint while preserving behavior and public compatibility.
status: completed
Summary:
Migrated targeted consumers to direct imports from `actions/services/*` and `actions/core/*` without changing function signatures or route contracts.
Validation:
- Lint still blocked by repository ESLint/Next option compatibility issue (pre-existing tooling configuration).
- Manual testing remains required for search/case/myportal/auth/notify/file matrix.
Follow-ups:
- Continue phased import migration for additional `pages/api/endpoint/**` handlers.
- Add export-parity and helper-level tests as planned.
---
### CL-005: Priority 1 Refactor (Phase 4) — helper hardening + redacted logging baseline
date: 2026-03-12
author: Cline
scope: `actions/core/{guards,logger}.js`, `actions/index.js`, selected API handlers
type: change
rationale: Introduce low-risk guard and redaction primitives, then apply them to sensitive/high-churn API paths to reduce injection/privacy risk while preserving behavior.
impact: Better input hygiene and safer diagnostics in selected notify/login/contact flows; no intended contract changes.
status: completed
Summary:
Added `guards` helper module (`isNonEmptyString`, `sanitizeString`, `escapeODataString`) and logger redaction utility (`redactSensitive`). Adopted these helpers in `notify`, `getpreferredlanguage_api`, and `createcrmtask_api` handlers.
Validation:
- `npm run lint` remains blocked by existing Next/ESLint options incompatibility in repository tooling.
- Targeted diff review confirms changes are scoped to helper hardening and selected handlers.
Follow-ups:
- Continue Phase 4 adoption for additional sensitive file/email/auth handlers.
- Add focused unit tests for `guards` and `redactSensitive` behavior.
---
### CL-006: Priority 1 Refactor — consumer migration pass (portal/form/admin utilities)
date: 2026-03-12
author: Cline
scope: `components/admin/*`, `components/case/*`, `components/myportal/*`, `components/elements/index.js`, `components/utils/index.js`, `lib/*`, `memory-bank/*`
type: change
rationale: Continue Phase 3-style decoupling by replacing broad `actions` barrel imports with focused service/core imports in high-use consumer files while preserving behavior.
impact: Lower coupling to monolithic action barrel and clearer dependency boundaries; no intended functional behavior change.
status: completed
Summary:
Migrated a targeted set of portal/form/admin utility consumers from `../../actions`/`../../../actions` to explicit imports from `actions/services/*` and `actions/core/*`. Also resolved migration-side compile issues in `components/myportal/topthree_reps.js` (duplicate import source and invalid `res.status` usage in client context).
Validation:
- In-scope consumer files now have no broad `from "../../actions"` / `from "../../../actions"` imports.
- `npm run lint` re-run; result unchanged and blocked by pre-existing Next/ESLint option incompatibility.
Follow-ups:
- Continue migrating remaining broad `actions` imports in `components/myportal/viewall.js`, `components/search/addresssearchresults.js`, unsubscribe flows, and selected API endpoints.
- Add focused manual smoke checks for myportal representations and awaiting-submission deletion/edit flows after this import migration.
---
### CL-007: Priority 1 Refactor — consumer migration pass (viewall/search/unsubscribe)
date: 2026-03-12
author: Cline
scope: `components/myportal/viewall.js`, `components/search/addresssearchresults.js`, `pages/unsubscribe/[watchlistid].js`, `pages/unsubscribeall/[watchlistid].js`, `memory-bank/*`
type: change
rationale: Continue Phase 3 decoupling by removing additional broad `actions` barrel imports in high-use myportal/search/unsubscribe flows and replacing them with focused service/core imports.
impact: Reduced coupling to monolithic action barrel with intended behavior parity; no API contract changes intended.
status: completed
Summary:
Migrated four additional consumers away from `../../actions`:
- `components/myportal/viewall.js` -> `actions/services/documentService`, `actions/services/portalService`, `actions/core/logger`
- `components/search/addresssearchresults.js` -> `actions/services/portalService`
- `pages/unsubscribe/[watchlistid].js` -> `actions/core/{hash,token,headers,logger}`
- `pages/unsubscribeall/[watchlistid].js` -> `actions/core/{hash,token,headers,logger}`
Validation:
- Regex verification confirms these four files no longer import from the broad `actions` barrel.
- Repository-wide broad-import scan now reports 11 matches total, including comment-only occurrences and remaining endpoint/file handlers pending migration.
Follow-ups:
- Migrate remaining non-comment broad imports in selected `pages/api/endpoint/*` and `pages/api/file/*` handlers.
- Run focused manual smoke checks for myportal view-all watch/unwatch + unsubscribe routes (EN/CY path sanity and negative paths).
---
### CL-008: Priority 1 Refactor — endpoint/file proxy import migration chunk
date: 2026-03-12
author: Cline
scope: `pages/api/endpoint/*`, `pages/api/file/*` (selected handlers), `memory-bank/*`
type: change
rationale: Continue Phase 3 migration by replacing broad `actions` barrel imports in selected endpoint/file proxy handlers with focused `actions/core/*` and service module imports.
impact: Further reduces monolithic import coupling in API paths while preserving existing request/response behavior and hash/token/header patterns.
status: completed
Summary:
Migrated these handlers away from broad `../../../actions` imports:
- `pages/api/endpoint/getmylpacases_api.js`
- `getLPA` -> `actions/services/referenceDataService`
- `azureHeaders`, `consoleLogger`, `getToken`, `hashAPIPath` -> `actions/core/*`
- removed local duplicated `hashAPIPath` implementation
- `pages/api/endpoint/getbasicdnssearchpaged_api.js`
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
Validation:
- Regex scans over `pages/api/endpoint` and `pages/api/file` show **0** remaining broad `from "../../../actions"` imports.
- `git status --short` confirms this chunk is isolated to the targeted endpoint/file handlers.
Follow-ups:
- Run focused manual smoke checks for touched endpoint/file flows (paged search APIs, LPA case retrieval, blob proxy endpoints).
- Continue hardening pass for sensitive handlers with guard/redaction helpers where not yet applied.
---
### CL-009: Priority 1 import-migration wave checked in + PR completed; handover to next branch
date: 2026-03-13
author: Cline
scope: `actions/services/*` consumer and endpoint migrations (completed), planning handover docs
type: change
rationale: Record that the current import-migration wave has been checked in and completed via pull request, and set the next refactor slice to start on a new branch.
impact: Clear project continuity and reduced risk of overlap between completed import migration work and upcoming `legacyActionsService` decomposition.
status: completed
Summary:
- Import migration wave is now completed, checked in, and PRd.
- Latest commits in this wave include:
- `f0202bb` — viewall/search/unsubscribe focused imports
- `fa98535` — endpoint/file proxy focused imports
- `bef8f25` — stale legacy comment cleanup
- Next chunk is explicitly designated as a new-branch activity focused on splitting `actions/services/legacyActionsService.js` into smaller direct service/client modules.
Validation:
- Broad `.../actions` import scans now show no active matches in `.js` files (legacy references are comment-only/removed).
- Branch history confirms migration commits are present and ordered for safe rollback.
Follow-ups:
- Create new branch for Phase 5 decomposition work.
- Start with low-risk extraction slices from `legacyActionsService.js` (reference/search first), then portal/document batches.
---
### CL-010: Phase 5 kickoff — first legacy split slice (search/reference direct modules)
date: 2026-03-13
author: Cline
scope: `actions/services/searchDirectService.js`, `actions/services/referenceDataDirectService.js`, `actions/services/{searchService,referenceDataService}.js`
type: change
rationale: Start Phase 5 decomposition on a new branch by extracting low-risk search/reference wrappers from `legacyActionsService.js` into direct domain modules while keeping service exports stable.
impact: Search/reference service exports now route through focused direct modules; compatibility signatures preserved for callers importing from service barrels.
status: completed
Summary:
- Created branch `TASK21997-phase5-legacyactions-split` from `SIPS-Development`.
- Added `searchDirectService.js` containing extracted search/address/document-search wrapper implementations.
- Added `referenceDataDirectService.js` containing extracted reference-data wrapper implementations.
- Updated `searchService.js` and `referenceDataService.js` to import from the new direct modules instead of `legacyActionsService`.
Validation:
- `npm run lint` (result unchanged): still fails due pre-existing Next/ESLint options incompatibility in repository tooling.
- Import checks confirm:
- `searchService.js` -> `./searchDirectService`
- `referenceDataService.js` -> `./referenceDataDirectService`
- Branch/status check confirms only Phase 5 slice files are modified on the new branch.
Follow-ups:
- Continue portal/document extraction slices from `legacyActionsService.js` in small batches.
- After each slice, re-point corresponding grouped service modules and remove dead wrappers once no longer referenced.
---
### CL-011: Phase 5 document slice — direct document module extraction
date: 2026-03-13
author: Cline
scope: `actions/services/documentDirectService.js`, `actions/services/documentService.js`, `memory-bank/*`
type: change
rationale: Continue Phase 5 decomposition by extracting document/file wrappers from `legacyActionsService.js` into a dedicated direct module while preserving grouped service API compatibility.
impact: Document/file grouped service exports now route through focused direct implementation; legacy coupling reduced for document domain with no expected caller contract changes.
status: completed
Summary:
- Added `documentDirectService.js` with extracted blob/file upload, download, pdf, and container helpers.
- Re-pointed `documentService.js` imports to `./documentDirectService`.
- Kept export names and signatures unchanged at grouped service boundary.
Validation:
- Service import scan confirms `documentService.js` no longer imports from `legacyActionsService`.
- Remaining legacy-backed grouped modules: `portalService`, `accountService`, `caseService`, `adminService`, `notifyService`, `integrationService`.
- `npm run lint` completes with warnings only (no blocking errors).
Follow-ups:
- Next recommended slice: `portalDirectService` extraction, then `account/case`.
- Continue per-slice parity checks and legacy import reduction scan.
---
### CL-012: Phase 5 portal slice — direct portal module extraction
date: 2026-03-13
author: Cline
scope: `actions/services/portalDirectService.js`, `actions/services/portalService.js`
type: change
rationale: Continue Phase 5 decomposition by extracting portal-domain wrappers from `legacyActionsService` into a direct module while preserving grouped service contract stability.
impact: Portal grouped exports now route through a focused direct module; reduced coupling to legacy monolith.
status: completed
Summary:
- Added `portalDirectService.js`.
- Updated `portalService.js` to import from `./portalDirectService`.
Validation:
- Legacy import scan reduced remaining `legacyActionsService`-backed grouped modules.
- `npm run lint` completes with warnings only.
Follow-ups:
- Extract account + case direct modules.
---
### CL-013: Phase 5 account/case slice — direct module extraction
date: 2026-03-13
author: Cline
scope: `actions/services/{accountDirectService,caseDirectService,accountService,caseService}.js`
type: change
rationale: Continue Phase 5 by moving account and case wrappers out of legacy module into dedicated direct modules while keeping grouped service API stable.
impact: Account/case grouped services now point to direct implementations; further legacy decoupling.
status: completed
Summary:
- Added `accountDirectService.js` and `caseDirectService.js`.
- Re-pointed `accountService.js` and `caseService.js`.
Validation:
- Legacy import scan left only admin/integration/notify using legacy.
- `npm run lint` completes with warnings only.
Follow-ups:
- Extract admin + integration + notify direct modules.
---
### CL-014: Phase 5 final extraction slice — admin/integration/notify direct modules
date: 2026-03-13
author: Cline
scope: `actions/services/{adminDirectService,integrationDirectService,notifyDirectService,adminService,integrationService,notifyService}.js`
type: change
rationale: Complete Phase 5 domain decomposition by extracting final legacy-backed grouped services into direct modules.
impact: All grouped service modules now route through direct implementations.
status: completed
Summary:
- Added `adminDirectService.js`, `integrationDirectService.js`, `notifyDirectService.js`.
- Re-pointed `adminService.js`, `integrationService.js`, `notifyService.js`.
Validation:
- `actions/services` scan reports zero `./legacyActionsService` imports.
- `npm run lint` completes with warnings only.
Follow-ups:
- Remove dead `legacyActionsService.js` implementation.
---
### CL-015: Phase 5 cleanup — remove legacy actions service
date: 2026-03-13
author: Cline
scope: `actions/services/legacyActionsService.js` (deleted)
type: change
rationale: Finalize Phase 5 after full routing migration by removing dead legacy implementation module.
impact: Monolithic legacy service removed; architecture now uses grouped service façade + direct per-domain modules.
status: completed
Summary:
- Deleted `actions/services/legacyActionsService.js` after zero-reference verification.
Validation:
- Project scan confirms no `legacyActionsService` references in `.js` files.
- `npm run lint` completes with warnings only.
- `npm run build` succeeds.
Follow-ups:
- Begin next phase: post-split hardening/tests/logging cleanup.
---
### CL-016: Phase 6 hardening — parity tests, safe dedupe, and logging cleanup
date: 2026-03-13
author: Cline
scope: `actions/services/*DirectService.js`, `actions/services/httpServiceUtils.js`, `tests/phase6/service-parity.test.cjs`, `memory-bank/*`
type: change
rationale: Start post-Phase-5 hardening by adding focused parity checks, reducing repeated axios/error-handling patterns without contract changes, and removing noisy logs in sensitive service paths.
impact: Improved maintainability and safety posture in service layer with preserved grouped-service signatures/return shapes; added regression guard for service export stability.
status: completed
Summary:
- Added `tests/phase6/service-parity.test.cjs` to verify:
- grouped service import/export parity for search/reference/document/portal/account/case/admin/integration/notify
- `actions/services/index.js` re-export list stability
- Added `actions/services/httpServiceUtils.js` with shared helpers:
- `logAndReturnResponse`
- `buildEmptyValueErrorResponse`
- `logAndReturnEmptyValueErrorResponse`
- Applied low-risk dedupe refactors in:
- `searchDirectService.js`
- `referenceDataDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- Removed debug `console.log` traces from sensitive/noisy direct service flows in:
- `searchDirectService.js`
- `documentDirectService.js`
- `portalDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- Added focused behavioural service test pack:
- `tests/phase6/service-behaviour.test.cjs`
- mocked axios and logger checks to assert success/error contract handling for selected critical functions.
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `npm run lint` -> warnings only (pre-existing warning set)
- Targeted smoke/negative checks on local dev server:
- `GET /advancedsearch` and `GET /cy/advancedsearch` -> 200
- `GET /case` and `GET /cy/case` -> 200
- `GET /myportal` and `GET /cy/myportal` -> 307 to `/auth/signin` (unauthenticated negative path)
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> 400 (negative path)
- `POST /api/email/notify` with `{}` -> 400 (negative path)
- `GET /searchresults` and `/cy/searchresults` -> 500 due pre-existing SSR serialization issue (`initialState.search.searchString` undefined)
Follow-ups:
- Investigate/fix pre-existing `/searchresults` SSR serialization issue before broader search smoke confidence sign-off.
- Optional next increment: extend behavioural coverage to document/portal/account/notify/integration direct-service contracts.
---
### CL-017: Phase 7 hardening — search SSR fix + behavioural expansion for remaining service domains
date: 2026-03-13
author: Cline
scope: `pages/searchresults.js`, `tests/phase7/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Start Phase 7 from a new branch based on `origin/SIPS-Development`, fix known pre-existing `/searchresults` SSR serialization failure, and extend behavioural/negative-path coverage for remaining direct-service domains.
impact: Search results SSR now safely serializes when query `q` is absent; behavioural confidence improved across document/portal/account/notify/integration service contracts with no signature/return-shape changes.
status: completed
Summary:
- Created branch: `TASK21988a-phase7-postphase6-hardening` (from `origin/SIPS-Development`).
- Fixed pre-existing SSR issue in `pages/searchresults.js` by changing:
- `setSearch(query.q)` -> `setSearch(query?.q || "")`
- Added `tests/phase7/service-behaviour.test.cjs` with mocked-axios behavioural assertions for:
- document
- portal
- account
- notify
- integration
- Included feasible negative-path checks for relay/hash/token-sensitive behavior patterns (hashed URL composition and existing per-function error contracts).
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `npm run lint` -> warnings only, no new lint errors introduced
- Manual smoke/negative-path checks (local dev on `localhost:3001` due `3000` in use):
- `/searchresults`, `/cy/searchresults` -> 200
- `/case`, `/cy/case` -> 200
- `/myportal`, `/cy/myportal` -> 307 to `/auth/signin`
- `/api/file/getbloblistproxy?container=test&casefolderID=test` -> 400
- `POST /api/email/notify` with `{}` -> 400
Follow-ups:
- Continue incremental hardening in sensitive relay/hash/token flows without changing external contracts.
- Keep lint warning backlog separate from this refactor stream unless explicitly scoped.
Rollback plan:
- Revert `pages/searchresults.js` and remove `tests/phase7/service-behaviour.test.cjs` if regression is observed.
---
### CL-018: Phase 8 hardening — hash enforcement and negative-path standardization in sensitive APIs
date: 2026-03-13
author: Cline
scope: `pages/api/file/{deleteblobcase,deleteblobrep,createappealcompletemessage_api}.js`, `pages/api/endpoint/getportallogin_api.js`, `actions/services/{documentDirectService,portalDirectService}.js`, `tests/phase8/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Deliver a small reversible hardening slice by re-enforcing hash guards in high-risk handlers, standardizing invalid-input negative paths, and reducing noisy/sensitive logging while preserving external contracts.
impact: Stronger hash/integrity guard posture and consistent 400 negative paths for invalid/missing hash and required params; no intended response-shape or signature changes.
status: completed
Summary:
- Started from latest `origin/SIPS-Development` on new branch `TASK22017-phase8-hardening-slice`.
- Re-enabled/enforced hash validation in:
- `deleteblobcase`
- `deleteblobrep`
- `createappealcompletemessage_api`
- Standardized early 400 handling for missing required query params where feasible:
- container/casefolder/repfile/tempcaseref/emailAddress checks as applicable
- Hardened `getportallogin_api`:
- explicit early validation for email/hash before token fetch
- removed unnecessary lodash branch complexity while preserving output behavior
- Improved logging discipline in sensitive flow:
- removed noisy `console.log` traces from `createappealcompletemessage_api`
- switched error logging to `consoleLogger`
- Added parity updates required for existing callers after hash re-enforcement:
- `documentDirectService` now appends hash on `deleteblobcase` and `deleteblobrep`
- `portalDirectService` now appends hash for `createappealcompletemessage_api` call using existing hash path contract
- Added focused phase-8 tests:
- `tests/phase8/service-behaviour.test.cjs`
- covers invalid/missing hash and missing key inputs as applicable across the 4 target handlers
- includes one valid-hash happy-path contract check for `getportallogin_api` via mocks
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings; no new lint errors)
- Manual HTTP checks (local server `http://localhost:3001`):
- invalid/missing hash for all 4 handlers -> 400
- missing required key params where tested -> 400
- valid-hash spot-check (`getportallogin_api`) still returned 400 due upstream relay/CRM behavior in local env (hash gate passed, downstream dependency failed)
Follow-ups:
- Investigate local relay/CRM dependency behavior for `getportallogin_api` happy path in integrated environment.
- Continue incremental hash/negative-path hardening for other sensitive `pages/api/file/**` handlers still carrying commented bypass patterns.
Rollback plan:
- Revert the four hardened API files and the two direct-service hash append updates.
- Remove `tests/phase8/service-behaviour.test.cjs` if the full phase-8 slice is rolled back.
- Re-run phase6/phase7 baseline tests and lint to confirm rollback parity.
---
### CL-019: Phase 9 hardening — additional file-handler hash enforcement slice
date: 2026-03-13
author: Cline
scope: `pages/api/file/{createrepcompletemessage_api,upload,uploadsinglefile,setupcontainer}.js`, `actions/services/{portalDirectService,documentDirectService}.js`, `tests/phase9/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Continue incremental hardening with a small reversible slice focused on additional sensitive file handlers still showing bypassed/commented hash checks or inconsistent negative-path behavior.
impact: Stronger hash/integrity guard posture and standardized 400 negative paths for invalid/missing hash and key params; no intended signature or response-shape contract changes.
status: completed
Summary:
- Started from latest `origin/SIPS-Development` on branch `TASK22019-phase-9-hardening`.
- Re-enabled/enforced hash validation and early 400 guards in:
- `createrepcompletemessage_api`
- `upload`
- `uploadsinglefile`
- `setupcontainer`
- Reduced noisy logs in sensitive paths and retained structured error logging where applicable.
- Added required caller parity updates:
- `portalDirectService.sendRepCompleteMessage` now appends hash
- `documentDirectService.uploadFiles/uploadRepFiles/uploadSingleFile` now append hash
- Added focused phase-9 tests:
- `tests/phase9/service-behaviour.test.cjs`
- negative-path coverage for the 4 selected phase-9 handlers
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings; no new lint errors)
- Manual HTTP negative-path checks on `localhost:3000`:
- missing/invalid hash for each selected handler -> 400
- missing required key params where tested -> 400
- Valid-hash HTTP spot-check:
- `POST /api/file/uploadsinglefile?hash=<valid>` -> 200 (hash generated from `.env.local` key)
Follow-ups:
- Continue incremental hardening slices for remaining sensitive handlers with commented/bypassed hash checks.
Rollback plan:
- Revert the 4 hardened API handlers and 2 caller parity service files.
- Remove `tests/phase9/service-behaviour.test.cjs` if rolling back full phase-9 slice.
- Re-run phase6/7/8/9 tests and lint to confirm rollback parity.
---
### CL-020: Phase 10 hardening — file retrieval handlers negative-path standardization
date: 2026-03-13
author: Cline
scope: `pages/api/file/{getawaitingsubmissionfromblob,getprogressobjblob,getbloblist,getrepsblob}.js`, `tests/phase10/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Continue connected hardening slices on the same work-item branch, targeting additional sensitive file retrieval handlers with inconsistent early guards and legacy commented hash/debug handling.
impact: Stronger and more consistent 400 negative-path behavior for missing/invalid hash and missing key params, with no intended signature or response-shape contract changes.
status: completed
Summary:
- Continued from Phase 9 on branch `TASK22019-phase-9-hardening`.
- Hardened 4 additional handlers:
- `getawaitingsubmissionfromblob`
- `getprogressobjblob`
- `getbloblist`
- `getrepsblob`
- Added explicit early guards for required query/hash inputs.
- Standardized hash mismatch handling to early 400 return.
- Removed legacy commented debug blocks in touched handlers.
- Added focused phase-10 tests:
- `tests/phase10/service-behaviour.test.cjs`
- includes negative-path coverage for all 4 handlers and one valid-hash mocked happy path.
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings)
- Manual HTTP checks (`localhost:3000`):
- missing hash for each selected phase-10 handler -> 400
- valid hash `getbloblist` spot-check -> 500 due local downstream/storage dependency constraints
Follow-ups:
- Continue next incremental slice for remaining sensitive handlers with commented/bypassed hash checks.
Rollback plan:
- Revert the 4 hardened API handlers.
- Remove `tests/phase10/service-behaviour.test.cjs` if rolling back full phase-10 slice.
- Re-run phase6/7/8/9/10 tests + lint to confirm rollback parity.
---
### CL-021: Phase 11 hardening — delete/bloblist hash guard standardization
date: 2026-03-13
author: Cline
scope: `pages/api/file/{getbloblist,deleteblob,deleteawaitingsubmissionfromblob}.js`, `tests/phase11/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Continue connected hardening slices on the same work-item branch, targeting additional sensitive blob/delete handlers with inconsistent early guards and legacy commented traces.
impact: More consistent and explicit 400 negative-path handling for missing/invalid hash and key params, with no intended signature or response-shape contract changes.
status: completed
Summary:
- Continued on branch `TASK22019-phase-9-hardening`.
- Hardened 3 additional handlers:
- `getbloblist`
- `deleteblob`
- `deleteawaitingsubmissionfromblob`
- Added explicit early guards for required query/hash inputs.
- Standardized hash mismatch handling to early 400 return.
- Removed legacy commented debug traces in touched handlers.
- Added focused phase-11 tests:
- `tests/phase11/service-behaviour.test.cjs`
- includes negative-path coverage and one valid-hash mocked happy path.
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings)
- Manual HTTP checks (`localhost:3000`):
- missing/invalid hash for selected phase-11 handlers -> 400
- valid-hash `getbloblist` spot-check -> 500 due local downstream/storage dependency constraints
Follow-ups:
- Continue next incremental slice for remaining sensitive handlers with commented/bypassed hash checks.
Rollback plan:
- Revert the 3 hardened API handlers.
- Remove `tests/phase11/service-behaviour.test.cjs` if rolling back full phase-11 slice.
- Re-run phase6/7/8/9/10/11 tests + lint to confirm rollback parity.
---
### CL-022: Phase 12 hardening — download/proxy param and hash guard standardization
date: 2026-03-13
author: Cline
scope: `pages/api/file/{downloadblob,getbloblistproxy,getrepsblobproxy,getawaitingsubmissionfromblobproxy}.js`, `tests/phase12/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Continue connected hardening slices on the same work-item branch, targeting additional sensitive download/proxy handlers with inconsistent early guards.
impact: More consistent and explicit 400 negative-path handling for missing/invalid hash and key params, with no intended signature or response-shape contract changes.
status: completed
Summary:
- Continued on branch `TASK22019-phase-9-hardening`.
- Hardened 4 additional handlers:
- `downloadblob`
- `getbloblistproxy`
- `getrepsblobproxy`
- `getawaitingsubmissionfromblobproxy`
- Added explicit early guards for required query/hash inputs.
- Standardized hash mismatch handling to early 400 return for `downloadblob`.
- Added focused phase-12 tests:
- `tests/phase12/service-behaviour.test.cjs`
- includes negative-path coverage and one valid-hash mocked happy path.
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings)
- Manual HTTP checks (`localhost:3000`):
- missing/invalid inputs for selected phase-12 handlers -> 400
- valid-hash `downloadblob` spot-check -> 500 due local downstream/storage dependency constraints
Follow-ups:
- Continue next incremental slice for remaining sensitive handlers with commented/bypassed hash checks.
Rollback plan:
- Revert the 4 hardened API handlers.
- Remove `tests/phase12/service-behaviour.test.cjs` if rolling back full phase-12 slice.
- Re-run phase6/7/8/9/10/11/12 tests + lint to confirm rollback parity.
---
### CL-023: Phase 13 hardening — delete and involvement handler guard standardization
date: 2026-03-13
author: Cline
scope: `pages/api/file/{deleteblobcase,deleteblobrep,createcaseinvolvement_api,createrepinvolvement_api}.js`, `tests/phase13/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Continue connected hardening slices on the same work-item branch, targeting additional sensitive delete/involvement handlers with inconsistent early guard and logging behavior.
impact: More consistent and explicit 400 negative-path handling for missing/invalid hash and required body keys, with no intended signature or response-shape contract changes.
status: completed
Summary:
- Continued on branch `TASK22019-phase-9-hardening`.
- Hardened 4 additional handlers:
- `deleteblobcase`
- `deleteblobrep`
- `createcaseinvolvement_api`
- `createrepinvolvement_api`
- Added explicit early guards for required hash/body inputs.
- Standardized hash mismatch handling to early 400 return for delete handlers.
- Removed noisy body/query logs from involvement handlers.
- Added focused phase-13 tests:
- `tests/phase13/service-behaviour.test.cjs`
- includes negative-path coverage and valid-input mocked happy paths.
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings)
- Manual HTTP checks (`localhost:3000`):
- missing/invalid inputs for selected phase-13 handlers -> 400
- valid-hash `deleteblobcase` spot-check -> 500 due local downstream/storage dependency constraints
Follow-ups:
- Continue next incremental slice for remaining sensitive handlers with commented/bypassed hash checks.
Rollback plan:
- Revert the 4 hardened API handlers.
- Remove `tests/phase13/service-behaviour.test.cjs` if rolling back full phase-13 slice.
- Re-run phase6/7/8/9/10/11/12/13 tests + lint to confirm rollback parity.
---
### CL-024: Phase 14 fix — authenticated server-side hash signing for browser callers
date: 2026-03-13
author: Cline
scope: `actions/services/{documentDirectService,portalDirectService}.js`, `pages/api/endpoint/gethash_api.js`, `tests/phase14/service-behaviour.test.cjs`, `memory-bank/*`
type: change
rationale: Fix runtime break in authenticated new-appeal journey where client-side hash generation attempted to use server-only `HASHKEY`.
impact: Restores browser flow while preserving hash-guard posture by moving hash generation to authenticated server endpoint; no intended external API contract changes.
status: completed
Summary:
- Added `gethash_api` endpoint to sign allow-listed API paths server-side.
- Endpoint enforces authenticated session and rejects unsupported paths.
- Updated direct services to fetch hash from signer endpoint for browser calls.
- Added server-side fallback hashing only when `HASHKEY` exists (non-browser contexts).
- Added phase-14 tests for missing-path, invalid-path, unauthenticated, and happy-path signer behavior.
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass
- `node tests/phase7/service-behaviour.test.cjs` -> pass
- `node tests/phase8/service-behaviour.test.cjs` -> pass
- `node tests/phase9/service-behaviour.test.cjs` -> pass
- `node tests/phase10/service-behaviour.test.cjs` -> pass
- `node tests/phase11/service-behaviour.test.cjs` -> pass
- `node tests/phase12/service-behaviour.test.cjs` -> pass
- `node tests/phase13/service-behaviour.test.cjs` -> pass
- `node tests/phase14/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing warnings)
Follow-ups:
- Keep signer allow-list tight and expand only for explicitly required browser-side hash use cases.
- Continue removing legacy client-side direct hash assumptions as encountered.
---
### CL-025: Phase 14 closeout follow-up — portal-login signer adoption + contract alignment fixes
date: 2026-03-13
author: Cline
scope: `actions/services/accountDirectService.js`, `pages/api/endpoint/gethash_api.js`, `tests/phase7/service-behaviour.test.cjs`, `tests/phase14/service-behaviour.test.cjs`, `pages/api/file/getrepsblob.js`, `components/elements/index.js`, `memory-bank/*`
type: change
rationale: Extend browser-safe hash signing to portal-login call path, keep signer allow-list explicit, and align an over-tightened file guard with actual endpoint contract discovered during runtime verification.
impact: Removes remaining browser-side hash risk in portal login path; preserves API response contracts while restoring route-accurate parameter validation and resolving Quill runtime import issue.
status: completed
Summary:
- Updated `accountDirectService.getPortalLogin` to use `/api/endpoint/gethash_api` for signed hash retrieval in browser contexts.
- Added `/api/endpoint/getportallogin_api` to signer endpoint allow-list in `gethash_api.js`.
- Updated tests to reflect signer-driven behavior and allow-list coverage:
- `tests/phase7/service-behaviour.test.cjs`
- `tests/phase14/service-behaviour.test.cjs`
- Applied follow-up runtime/contract fixes confirmed during manual verification:
- `pages/api/file/getrepsblob.js`: removed `casefolderID` hard requirement and pass-through argument to align with route usage (`container + hash`).
- `components/elements/index.js`: restored missing `updateLinks` import required by Quill-related flow.
- Branch pushed with follow-up commits:
- `6094874`
- `b8fa514`
Validation:
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
Follow-ups:
- Keep signer allow-list expansion minimal and task-driven.
- Continue replacing remaining browser-side direct hash assumptions only where flows require it.
- Confirm work-item/branch naming alignment for any subsequent phase slices if strict tracker continuity is required.
+632
View File
@@ -0,0 +1,632 @@
# Progress — PEDW FrontEnd
## Completed / evidenced milestones
- Bilingual routing structure is established with extensive Welsh rewrites and locale namespace mapping.
- Next-auth + Prisma SQL Server persistence is integrated for user/session lifecycle.
- Broad API surface exists for search, case, portal, admin, and file/document operations.
- CRM/relay hash pattern is implemented across many endpoint handlers.
- Recent shipped updates focused on:
- search/case back-navigation and breadcrumb behavior,
- DNS/view-all navigation logic,
- preferred-language handling for notify template selection,
- PDF formatting/hyperlink fixes,
- upload validation character updates.
## Work in progress signals
- Ongoing iterative fixes in breadcrumb/case-summary flows suggest navigation consistency is still being tuned.
- Mixed proxy/non-proxy endpoint implementations continue to evolve in parallel.
- Priority 1 refactor plan execution started for `actions/index.js` split (Phase 1 foundation complete).
## Planned / partially implemented (inferred from code state)
- Partial migration or coexistence of deployment/runtime patterns (Next runtime plus custom server artifacts).
- Continued refinement of document/email workflow behavior (template/language and formatting quality).
- Prioritized structural refactor backlog documented in `memory-bank/refactor-backlog.md`.
## Known technical debt
- `actions/services/legacyActionsService.js` still holds broad domain wrappers after Phase 1 extraction (core helpers now separated).
- Significant duplication of token/hash/relay logic across endpoint files.
- Inconsistent security/hash guard enforcement in some file routes.
- Sparse automated test coverage (`tests/` currently empty).
- Verbose logging in sensitive flows (auth/email/actions/file) increases privacy risk.
## Latest update (2026-03-12)
- Branch `0000-update-actions-monolith` created from development branch for Priority 1 refactor implementation.
- `actions/index.js` reduced to a compatibility barrel.
- New core helper modules added under `actions/core/`:
- `env.js`
- `logger.js`
- `hash.js`
- `token.js`
- `headers.js`
- Existing action wrappers moved to `actions/services/legacyActionsService.js` and continue to be exported via the barrel for compatibility.
- `actions/clients/` scaffolded for upcoming client extraction phase.
- Phase 2 service grouping completed with dedicated service files and `actions/services/index.js` barrel.
- Export parity check confirms grouped service modules cover all legacy exported action names.
- Phase 3 migration started by moving selected high-churn consumers and representative endpoint handlers to direct `actions/services/*` and `actions/core/*` imports.
- Phase 4 hardening baseline added with new guard helpers and redacted logging, adopted in selected notify/login/contact API handlers.
- Additional Priority 1 consumer migration pass completed for high-use portal/form/representation helpers:
- Updated direct service/core imports in:
- `components/admin/tabs/documents.js`
- `components/admin/tabs/storage.js`
- `components/admin/utils/serverside.js`
- `components/case/documents.js`
- `components/case/representation/representationComplete.js`
- `components/case/representation/representationElements.js`
- `components/elements/index.js`
- `components/myportal/awaitingsubmissionfromblob.js`
- `components/myportal/topthree.js`
- `components/myportal/topthree_reps.js`
- `components/utils/index.js`
- `lib/myportal/loadMyPortalAppealPage.js`
- `lib/newappeal/loadNewAppealPage.js`
- Additional Priority 1 consumer migration pass completed for view-all/search/unsubscribe flows:
- Updated direct service/core imports in:
- `components/myportal/viewall.js`
- `components/search/addresssearchresults.js`
- `pages/unsubscribe/[watchlistid].js`
- `pages/unsubscribeall/[watchlistid].js`
- Broad `actions` barrel import count reduced again; remaining non-comment usages are now concentrated in selected `pages/api/endpoint/*` and `pages/api/file/*` handlers.
- Additional Priority 1 endpoint/file proxy migration chunk completed:
- Updated imports in:
- `pages/api/endpoint/getmylpacases_api.js`
- `pages/api/endpoint/getbasicdnssearchpaged_api.js`
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
- Migrated from broad `../../../actions` imports to focused `actions/core/*` + service imports.
- Removed duplicated local hash helper in `getmylpacases_api.js` and reused `actions/core/hash`.
- Broad `actions` import scan now reports only comment-only references (no active broad imports in `.js` files).
- Lint check re-run outcome unchanged: `next lint` fails due legacy/unsupported ESLint options in repo tooling config (not introduced by this refactor pass).
## Handover update (2026-03-13)
- Priority 1 import-migration wave has been checked in and completed via pull request.
- Recent migration commits captured in this wave:
- `f0202bb`
- `fa98535`
- `bef8f25`
- Active broad `.../actions` imports are now eliminated from live `.js` code paths.
- Next refactor phase will begin on a **new branch** and focus on splitting `actions/services/legacyActionsService.js` into smaller direct implementation modules.
- Recommended decomposition order for the new branch:
1. reference/search extraction
2. portal/document extraction
3. compatibility layer slimming + dead wrapper removal
## Latest update (2026-03-13 — Phase 5 kickoff)
- New branch created from `SIPS-Development`: `TASK21997-phase5-legacyactions-split`.
- Completed first Phase 5 decomposition slice from `legacyActionsService`:
- Added `actions/services/searchDirectService.js`
- Added `actions/services/referenceDataDirectService.js`
- Updated `actions/services/searchService.js` to source from `searchDirectService`
- Updated `actions/services/referenceDataService.js` to source from `referenceDataDirectService`
- Compatibility approach preserved: public grouped service exports and function signatures remain unchanged for callers.
- Validation status unchanged:
- `npm run lint` still fails due pre-existing Next/ESLint option incompatibility in repo tooling configuration.
## Latest update (2026-03-13 — Phase 5 document slice)
- Continued on branch `TASK21997-phase5-legacyactions-split`.
- Completed second decomposition slice from `legacyActionsService`:
- Added `actions/services/documentDirectService.js`
- Updated `actions/services/documentService.js` to source from `documentDirectService`
- Compatibility approach preserved:
- grouped service export names/signatures unchanged for consumers.
- Verification:
- service import scan now shows remaining legacy-backed grouped modules are:
- `portalService`, `accountService`, `caseService`, `adminService`, `notifyService`, `integrationService`
- `npm run lint` passes with warnings only (no blocking errors).
## Latest update (2026-03-13 — Phase 5 completion)
- Continued decomposition and completed all remaining slices on `TASK21997-phase5-legacyactions-split`:
- portal direct extraction
- account + case direct extraction
- admin + integration + notify direct extraction
- legacy cleanup/removal
- New direct modules now in place:
- `portalDirectService.js`
- `accountDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- `integrationDirectService.js`
- `notifyDirectService.js`
- `actions/services/legacyActionsService.js` removed after import parity checks.
- Validation evidence:
- `actions/services` import scan: zero `./legacyActionsService` references
- `npm run lint`: warnings only, no blocking errors
- `npm run build`: successful production build
## Latest update (2026-03-13 — Phase 6 post-Phase-5 hardening, in progress)
- Branch created from `SIPS-Development`: `TASK21998-phase6-postphase5-hardening`.
- Added focused parity checks for grouped/direct service stability and service index barrel stability:
- `tests/phase6/service-parity.test.cjs`
- validates grouped service import/export parity across:
- search, referenceData, document, portal, account, case, admin, integration, notify
- validates `actions/services/index.js` export list stability/order.
- Added shared service error helper:
- `actions/services/httpServiceUtils.js`
- centralizes common `consoleLogger + return error.response` and `ErrResponse` shape patterns.
- Applied low-risk duplication reduction in direct services (no signature/shape changes intended):
- `searchDirectService.js`
- `referenceDataDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- Logging cleanup/redaction-oriented hardening (sensitive-flow noisy logs removed):
- removed debug `console.log` statements from:
- `searchDirectService.js`
- `documentDirectService.js`
- `portalDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
### Validation snapshot (Phase 6)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `npm run lint` -> **warnings only** (same pre-existing warnings)
### Decision captured (Phase 6)
- Agreed approach: keep parity guard tests **and** add focused behavioural unit tests in the current refactor stream (instead of deferring all behavioural checks to long-term roadmap).
- Implemented now:
- `tests/phase6/service-behaviour.test.cjs`
- mocked-axios behavioural checks for critical success/error contracts across search/reference/case/admin direct services.
- Wider behavioural expansion remains on roadmap for additional domains/functions.
### Targeted smoke snapshot (local dev server)
- Search flow:
- `GET /searchresults` (EN/CY) -> **500** in local env due existing serialization issue (`initialState.search.searchString` undefined in SSR payload), observed in `/tmp/phase6-dev.log`.
- `GET /advancedsearch` and `GET /cy/advancedsearch` -> **200**
- Case flow:
- `GET /case` and `GET /cy/case` -> **200**
- My Portal flow (negative-path):
- `GET /myportal` and `GET /cy/myportal` -> **307** redirect to `/auth/signin` (expected unauthenticated behavior)
- Document flow (negative-path):
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> **400**
- Notify flow (negative-path):
- `POST /api/email/notify` with `{}` -> **400**
## Latest update (2026-03-13 — Phase 8 hardening slice)
- New branch created from `origin/SIPS-Development` with work item prefix:
- `TASK22017-phase8-hardening-slice`
- Hardened four sensitive API handlers with minimal reversible guards:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
- Scope delivered:
- re-enabled/enforced hash checks where bypassed/commented
- standardized early 400 negative paths for missing/invalid required query inputs
- reduced noisy sensitive-path logging and routed errors through `consoleLogger`
- preserved function signatures and existing response-shape contracts
- Consumer parity updates (required to preserve behaviour after hash enforcement):
- `actions/services/documentDirectService.js`
- append hash for delete-blob-case/delete-blob-rep calls
- `actions/services/portalDirectService.js`
- append hash for create-appeal-complete-message call (hash path preserved to existing API contract)
- Added focused tests:
- `tests/phase8/service-behaviour.test.cjs`
- covers negative paths for all 4 handlers (missing/invalid hash and missing key params as applicable)
- includes one happy-path check for `getportallogin_api` with valid hash via mocked dependencies
### Validation snapshot (Phase 8)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing react-hooks dependency warnings; no new lint errors)
### Manual HTTP checks (Phase 8)
- Local dev server on `http://localhost:3001`.
- Negative-path checks:
- `GET /api/file/deleteblobcase?...` missing hash -> **400**
- `GET /api/file/deleteblobcase?...&hash=bad` -> **400**
- `GET /api/file/deleteblobrep?...` missing hash -> **400**
- `GET /api/file/deleteblobrep?...&hash=bad` -> **400**
- `GET /api/file/deleteblobrep?...` missing `repfile` -> **400**
- `GET /api/file/createappealcompletemessage_api?...` missing hash -> **400**
- `GET /api/file/createappealcompletemessage_api?...&hash=bad` -> **400**
- `GET /api/file/createappealcompletemessage_api?...` missing `tempcaseref` -> **400**
- `GET /api/endpoint/getportallogin_api?...` missing hash -> **400**
- `GET /api/endpoint/getportallogin_api?...&hash=bad` -> **400**
- `GET /api/endpoint/getportallogin_api?...` missing `emailAddress` -> **400**
- Valid-hash spot-check:
- `GET /api/endpoint/getportallogin_api?...&hash=<valid>` reached handler with valid hash but returned **400** from upstream relay/CRM call in local environment (expected environmental dependency risk, not hash-guard bypass).
## Latest update (2026-03-13 — Phase 9 hardening slice)
- New branch created from `origin/SIPS-Development` with work item prefix:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive file handlers with minimal reversible changes:
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
- Scope delivered:
- re-enabled/enforced hash validation where bypassed/commented
- added/standardized early 400 negative paths for missing/invalid hash and missing key parameters
- reduced noisy logging in sensitive file-upload/message paths
- preserved handler signatures and external response-shape contracts
- Consumer parity updates:
- `actions/services/portalDirectService.js`
- append hash for `createrepcompletemessage_api` call
- `actions/services/documentDirectService.js`
- append hash for `upload` and `uploadsinglefile` calls
- Added focused tests:
- `tests/phase9/service-behaviour.test.cjs`
- covers negative paths across all 4 phase-9 handlers
### Validation snapshot (Phase 9)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 9)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/createrepcompletemessage_api?...` missing hash -> **400**
- `GET /api/file/createrepcompletemessage_api?...&hash=bad` -> **400**
- `POST /api/file/upload` missing hash -> **400**
- `POST /api/file/upload?hash=bad` -> **400**
- `POST /api/file/uploadsinglefile` missing hash -> **400**
- `POST /api/file/uploadsinglefile?hash=bad` -> **400**
- `GET /api/file/setupcontainer?ident=...` missing hash -> **400**
- `GET /api/file/setupcontainer?ident=...&hash=bad` -> **400**
- `GET /api/file/setupcontainer?hash=bad` (missing ident) -> **400**
- Valid-hash happy-path spot-check:
- Completed using `.env.local` runtime key: `POST /api/file/uploadsinglefile?hash=<valid>` -> **200**
### Rollback plan (Phase 9)
1. Revert the four hardened API handlers.
2. Revert direct-service hash append changes in `documentDirectService` and `portalDirectService`.
3. Remove `tests/phase9/service-behaviour.test.cjs` if full slice rollback required.
4. Re-run phase6/7/8/9 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 10 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive file handlers with minimal reversible changes:
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required inputs (`container`, `casefolderID` where applicable, `hash`)
- standardized hash-mismatch negative-path checks to early return 400
- removed legacy/noisy commented debug blocks from touched handlers
- preserved existing handler signatures and response-shape contracts
- Caller parity impact:
- no new caller changes required in this slice because direct-service consumers already append hash for these APIs.
- Added focused tests:
- `tests/phase10/service-behaviour.test.cjs`
- covers negative paths for all 4 selected phase-10 handlers
- includes one valid-hash contract-preserving happy-path check (`getbloblist` via mocked dependencies)
### Validation snapshot (Phase 10)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 10)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/getawaitingsubmissionfromblob?container=test` (missing hash) -> **400**
- `GET /api/file/getprogressobjblob?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/getbloblist?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/getrepsblob?container=test&casefolderID=case-1` (missing hash) -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/getbloblist?container=test&casefolderID=case-1&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 10)
1. Revert the four hardened API handlers.
2. Remove `tests/phase10/service-behaviour.test.cjs` if full phase-10 rollback required.
3. Re-run phase6/7/8/9/10 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 11 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened three additional sensitive file handlers with minimal reversible changes:
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required query inputs (`container`, `casefolderID`, `blobname`, `hash` as applicable)
- standardized hash-mismatch negative-path checks to early return 400
- removed legacy/noisy commented debug traces in touched handlers
- preserved existing handler signatures and response-shape contracts
- Caller parity impact:
- no new caller changes required in this slice; direct-service consumers already pass expected query/hash data.
- Added focused tests:
- `tests/phase11/service-behaviour.test.cjs`
- covers negative paths for selected phase-11 handlers
- includes one valid-hash contract-preserving happy-path check (`getbloblist` via mocked dependencies)
### Validation snapshot (Phase 11)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 11)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/getbloblist?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/deleteblob?container=test&casefolderID=case-1&blobname=file.pdf` (missing hash) -> **400**
- `GET /api/file/deleteawaitingsubmissionfromblob?container=test&casefolderID=case-1&blobname=file.pdf` (missing hash) -> **400**
- `GET /api/file/deleteblob?container=test&casefolderID=case-1&hash=bad` (missing blobname/invalid hash) -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/getbloblist?container=test&casefolderID=case-1&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 11)
1. Revert the three hardened API handlers.
2. Remove `tests/phase11/service-behaviour.test.cjs` if full phase-11 rollback required.
3. Re-run phase6/7/8/9/10/11 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 12 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive handlers with minimal reversible changes:
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required query inputs (`container`, `casefolderID`, `blobname`, `hash` as applicable)
- standardized hash-mismatch negative-path checks for `downloadblob`
- preserved existing function signatures and response-shape contracts
- Added focused tests:
- `tests/phase12/service-behaviour.test.cjs`
- covers negative paths for selected phase-12 handlers
- includes one valid-hash contract-preserving happy-path check (`downloadblob` via mocked dependencies)
### Validation snapshot (Phase 12)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 12)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/downloadblob?container=test&casefolderID=case-1&blobname=file.pdf` (missing hash) -> **400**
- `GET /api/file/downloadblob?container=test&casefolderID=case-1&blobname=file.pdf&hash=bad` -> **400**
- `GET /api/file/getbloblistproxy?casefolderID=case-1` (missing container) -> **400**
- `GET /api/file/getrepsblobproxy` (missing container) -> **400**
- `GET /api/file/getawaitingsubmissionfromblobproxy` (missing container) -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/downloadblob?container=test&casefolderID=case-1&blobname=file.pdf&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 12)
1. Revert the four hardened API handlers.
2. Remove `tests/phase12/service-behaviour.test.cjs` if full phase-12 rollback required.
3. Re-run phase6/7/8/9/10/11/12 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 13 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive handlers with minimal reversible changes:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required query/body inputs (`hash`, `contactid`, `incidentid` as applicable)
- standardized hash-mismatch negative-path checks for delete-blob handlers
- removed noisy body/query logging in involvement handlers
- preserved existing function signatures and response-shape contracts
- Added focused tests:
- `tests/phase13/service-behaviour.test.cjs`
- covers negative paths for selected phase-13 handlers
- includes valid-hash/valid-body contract-preserving happy-path checks via mocked dependencies
### Validation snapshot (Phase 13)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 13)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/deleteblobcase?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/deleteblobcase?container=test&casefolderID=case-1&hash=bad` -> **400**
- `GET /api/file/deleteblobrep?container=test&casefolderID=case-1&repfile=r.pdf` (missing hash) -> **400**
- `POST /api/file/createcaseinvolvement_api` with `{}` -> **400**
- `POST /api/file/createrepinvolvement_api` with `{}` -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/deleteblobcase?container=test&casefolderID=case-1&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 13)
1. Revert the four hardened API handlers.
2. Remove `tests/phase13/service-behaviour.test.cjs` if full phase-13 rollback required.
3. Re-run phase6/7/8/9/10/11/12/13 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 14 client-hash signing fix)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Fixed authenticated user-journey runtime error during new appeal flow where client-side hashing tried to use server-only `HASHKEY`.
- Added authenticated server-side hash signer endpoint:
- `pages/api/endpoint/gethash_api.js`
- requires session (`getSession`), allow-lists supported API paths, returns `{ hash }`.
- Updated direct services to request hash from server signer for browser calls, with server-side fallback only when `HASHKEY` exists:
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- Preserved existing API contracts and response shapes in file handlers.
- Added focused tests:
- `tests/phase14/service-behaviour.test.cjs`
- covers signer endpoint negative paths + authenticated happy path.
### Validation snapshot (Phase 14)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing hook dependency warnings)
### Rollback plan (Phase 14)
1. Revert `pages/api/endpoint/gethash_api.js`.
2. Revert direct-service hash signer usage in `documentDirectService.js` and `portalDirectService.js`.
3. Remove `tests/phase14/service-behaviour.test.cjs` if rolling back full phase-14 slice.
4. Re-run phase6phase14 tests + lint.
## Latest update (2026-03-13 — Phase 14 closeout follow-up)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Completed follow-up hardening and contract-alignment after runtime verification:
- `actions/services/accountDirectService.js`
- `getPortalLogin` now uses authenticated signer endpoint (`/api/endpoint/gethash_api`) for browser-safe hash generation
- retains server-only fallback to local `hashAPIPath` when `HASHKEY` is available
- `pages/api/endpoint/gethash_api.js`
- allow-list expanded for `/api/endpoint/getportallogin_api`
- `pages/api/file/getrepsblob.js`
- removed enforced `casefolderID` guard and argument pass-through to align with route contract (`/api/file/getrepsblob?container=...`)
- `components/elements/index.js`
- added missing `updateLinks` import from `components/utils` to resolve Quill runtime error
- Tests updated:
- `tests/phase7/service-behaviour.test.cjs`
- updated `getPortalLogin` expectations for signer endpoint behavior
- `tests/phase14/service-behaviour.test.cjs`
- added allow-list coverage for signer path `/api/endpoint/getportallogin_api`
### Validation snapshot (Phase 14 follow-up)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing hook dependency warnings)
### Risks + mitigations (follow-up)
- Risk: signer endpoint over-expansion could broaden hash issuance.
- Mitigation: strict allow-list retained; only required `getportallogin_api` path added.
- Risk: tightened/changed guards could break existing caller contracts.
- Mitigation: `getrepsblob` guard aligned back to actual route contract; response shape unchanged.
- Risk: UI runtime dependency regressions during hardening verification.
- Mitigation: missing `updateLinks` import restored with minimal diff.
### Rollback plan (follow-up)
1. Revert signer-path migration commit `6094874` (or files: `accountDirectService.js`, `gethash_api.js`, `tests/phase7`, `tests/phase14`).
2. Revert contract/import alignment commit `b8fa514` (or files: `getrepsblob.js`, `components/elements/index.js`).
3. Re-run phase6phase14 tests and lint to confirm rollback parity.
### Rollback plan (Phase 8)
1. Revert the four hardened API handlers.
2. Revert direct-service hash append changes in `documentDirectService` and `portalDirectService`.
3. Remove `tests/phase8/service-behaviour.test.cjs` if full slice rollback required.
4. Re-run phase6/7 baseline tests + lint after rollback.
## Outstanding risks / gaps
- Navigation regressions across EN/CY + route query combinations.
- Drift between rewrite config and component-level locale path logic.
- Deployment ambiguity due to multiple CI/CD artifacts (Azure pipeline, Jenkins, Docker) with unclear active source of truth.
- Security posture variability where hash checks are bypassed/commented in selected handlers.
## Latest update (2026-03-13 — Phase 7 post-Phase-6 hardening)
- New branch created from `origin/SIPS-Development` with required work item prefix:
- `TASK21988a-phase7-postphase6-hardening`
- Fixed known pre-existing SSR serialization issue on search results route:
- `pages/searchresults.js`
- updated SSR dispatch fallback from `setSearch(query.q)` to `setSearch(query?.q || "")`
- result: `/searchresults` and `/cy/searchresults` now return 200 in local smoke checks (no 500 observed in this run).
- Added expanded behavioural coverage for remaining direct-service domains:
- new test file: `tests/phase7/service-behaviour.test.cjs`
- includes document, portal, account, notify, integration behavioural checks
- includes negative-path assertions for relay/hash/token-sensitive behaviors where feasible:
- hashed URL append checks (`hashAPIPath`)
- error handling contracts (undefined/error string/rethrow depending on existing function contract)
### Validation snapshot (Phase 7)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `npm run lint` -> **warnings only** (pre-existing hook dependency warnings; no new lint errors)
### Targeted smoke snapshot (local dev server)
- Note: dev server auto-started on `http://localhost:3001` because port `3000` was in use.
- Search flow:
- `GET /searchresults` and `GET /cy/searchresults` -> **200**
- Case flow:
- `GET /case` and `GET /cy/case` -> **200**
- My Portal flow (negative-path):
- `GET /myportal` and `GET /cy/myportal` -> **307** redirect to `/auth/signin`
- Document flow (negative-path):
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> **400**
- Notify flow (negative-path):
- `POST /api/email/notify` with `{}` -> **400**
+747
View File
@@ -0,0 +1,747 @@
# Refactor Plan — Priority 1 (`actions/index.js` split)
Last updated: 2026-03-13
## Handover note (2026-03-13)
- Import-migration wave from `actions` barrel to focused modules has been checked in and completed via PR.
- Next chunk will be delivered from a **new branch** and should start at legacy decomposition.
### Next branch kickoff scope (Phase 5)
Primary objective: split `actions/services/legacyActionsService.js` into smaller direct implementations while preserving function signatures.
Recommended first slice:
1. Extract reference/search internals from `legacyActionsService.js` into dedicated modules.
2. Keep `actions/services/*` exports stable and route through new direct implementations.
3. Add focused parity checks per extracted function group (inputs, headers, hash behavior, return shapes).
Then continue with:
4. Portal/document extraction in small batches.
5. Remove dead wrappers from `legacyActionsService.js` once call paths are fully migrated.
### Phase 5 slice roadmap (completed)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Search + reference direct extraction
- `searchDirectService.js`, `referenceDataDirectService.js`
- `searchService.js` / `referenceDataService.js` re-pointed
2. `[x]` Document direct extraction
- create `documentDirectService.js`
- re-point `documentService.js`
- parity checks: blob paths/hash behavior/return shapes
3. `[x]` Portal direct extraction
- create `portalDirectService.js`
- re-point `portalService.js`
- parity checks: watched-case and completion-message flows
4. `[x]` Account + case direct extraction
- create `accountDirectService.js`, `caseDirectService.js`
- re-point grouped services
5. `[x]` Notify + integration + admin direct extraction
- create `notifyDirectService.js`, `integrationDirectService.js`, `adminDirectService.js`
6. `[x]` Legacy slim-down pass
- remove dead wrappers from `legacyActionsService.js`
- keep only temporary compatibility exports still required
7. `[x]` Final cleanup + validation pass
- grep checks for remaining `./legacyActionsService` imports
- lint/manual smoke checks
### Phase 5 completion snapshot (2026-03-13)
- All grouped service modules now route through focused direct modules under `actions/services/*DirectService.js`.
- `actions/services/legacyActionsService.js` has been removed.
- Verification completed:
- `actions/services` scan returns zero `./legacyActionsService` imports.
- `npm run lint` completes with warnings only.
- `npm run build` completes successfully.
### Next phase kickoff recommendation
Focus on post-split hardening and cleanup:
1. Add parity-focused tests for extracted direct services and `actions/services/index.js` export stability.
2. Consolidate repeated axios/error-handling patterns into shared clients/utilities where safe.
3. Reduce verbose debug logging in direct services (especially account/case/document paths) with redaction discipline.
4. Run targeted manual smoke matrix for search/case/myportal/document/notify with EN/CY parity checks.
### Phase 6 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Add parity-focused tests for extracted services and barrel stability
- `tests/phase6/service-parity.test.cjs`
- validates grouped/direct service export parity
- validates `actions/services/index.js` export stability
2. `[x]` Reduce repeated axios/error-handling patterns safely
- added shared helper: `actions/services/httpServiceUtils.js`
- adopted in selected direct modules without signature/return-shape changes
3. `[x]` Logging cleanup in sensitive direct-service flows
- removed noisy debug logs from search/document/portal/case/admin direct services
4. `[x]` Add focused behavioural contract tests in current phase
- `tests/phase6/service-behaviour.test.cjs`
- success/error contract checks with mocked axios/logger (8/8 passing)
5. `[x]` Run targeted validation/smoke checks
- parity test: pass
- behavioural test: pass
- manual smoke matrix executed (search/case/myportal/document/notify, EN/CY + negative paths)
Notes:
- Known pre-existing caveat during smoke checks:
- `/searchresults` and `/cy/searchresults` return 500 in local dev due SSR serialization issue (`initialState.search.searchString` undefined).
- treated as existing issue, not introduced by this refactor phase.
### Future requirements / next-phase backlog (post-Phase-6)
1. Investigate and fix `/searchresults` SSR serialization issue (EN/CY parity).
2. Expand behavioural service tests to remaining domains:
- document
- portal
- account
- notify
- integration
3. Add focused negative-path tests for token/hash/relay-sensitive handlers.
4. Continue small-batch hardening with reversible commits and parity checks per batch.
5. Prepare PR evidence bundle for each increment:
- lint + test commands
- behavioural/parity test outputs
- manual EN/CY + negative-path smoke matrix
### Phase 7 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK21988a-phase7-postphase6-hardening`
2. `[x]` Fix pre-existing `/searchresults` SSR serialization issue with EN/CY parity
- updated `pages/searchresults.js`:
- `setSearch(query.q)` -> `setSearch(query?.q || "")`
3. `[x]` Expand behavioural tests to remaining service domains
- added `tests/phase7/service-behaviour.test.cjs`
- coverage includes: document, portal, account, notify, integration
4. `[x]` Add feasible negative-path tests for token/hash/relay-sensitive flows
- validated hashed path composition (e.g., `hashAPIPath` usage)
- validated existing function error contracts (undefined/error string/rethrow)
5. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `npm run lint` -> warnings only (no new errors)
6. `[x]` Execute targeted manual smoke matrix (EN/CY + negative paths)
- local server on `localhost:3001` (3000 occupied)
- `/searchresults`, `/cy/searchresults` -> 200
- `/case`, `/cy/case` -> 200
- `/myportal`, `/cy/myportal` -> 307 -> `/auth/signin`
- `/api/file/getbloblistproxy?container=test&casefolderID=test` -> 400
- `POST /api/email/notify` with `{}` -> 400
### Phase 7 rollback notes
- Revert `pages/searchresults.js` fallback change if search behaviour regresses unexpectedly.
- Remove `tests/phase7/service-behaviour.test.cjs` if test scope needs to be rolled back.
- Reset branch to pre-Phase-7 commit if full rollback required.
### Phase 8 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK22017-phase8-hardening-slice`
2. `[x]` Re-enable/enforce hash validation in 4 sensitive target handlers
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
3. `[x]` Standardize negative-path behavior for invalid/missing hash and required keys
- added early 400 guards for missing required query params where applicable
- preserved response-shape contracts/signatures
4. `[x]` Improve logging discipline in sensitive flow
- removed noisy direct console output in `createappealcompletemessage_api`
- routed updated error path logging via `consoleLogger`
5. `[x]` Keep caller behavior compatible after hash re-enforcement
- `actions/services/documentDirectService.js` now appends hash for:
- `deleteblobcase`
- `deleteblobrep`
- `actions/services/portalDirectService.js` now appends hash for:
- `createappealcompletemessage_api` (existing hash-path contract retained)
6. `[x]` Add focused Phase 8 tests
- added `tests/phase8/service-behaviour.test.cjs`
- includes negative-path coverage for all 4 target handlers
- includes one valid-hash happy-path contract check (`getportallogin_api`) via mocks
7. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing react-hooks dependency warnings)
8. `[x]` Execute manual HTTP negative-path matrix + feasible happy-path spot-check
- invalid/missing hash across 4 target handlers -> 400
- missing required params where tested -> 400
- valid-hash `getportallogin_api` spot-check returned 400 in local env due upstream relay/CRM dependency
### Phase 8 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- `tests/phase8/service-behaviour.test.cjs`
- Re-run phase6/phase7 baseline tests and lint after rollback to confirm parity.
### Phase 9 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/enforce hash validation in 4 additional sensitive file handlers
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
3. `[x]` Standardize negative-path behavior for invalid/missing hash and key params
- added early 400 handling for missing required query/body values where applicable
4. `[x]` Improve logging discipline in sensitive paths
- removed noisy direct console logging in touched handlers
5. `[x]` Keep caller behavior compatible after hash re-enforcement
- `actions/services/portalDirectService.js`
- `sendRepCompleteMessage` now appends hash
- `actions/services/documentDirectService.js`
- upload-related calls now append hash
6. `[x]` Add focused Phase 9 tests
- added `tests/phase9/service-behaviour.test.cjs`
- negative-path tests for all 4 selected handlers
7. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (no new lint errors)
8. `[x]` Execute manual HTTP negative-path matrix
- missing/invalid hash checks across selected handlers -> 400
- missing key params where tested -> 400
9. `[x]` Valid-hash HTTP happy-path spot-check
- `POST /api/file/uploadsinglefile?hash=<valid>` -> 200 (hash generated from `.env.local` key)
### Phase 9 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
- `actions/services/portalDirectService.js`
- `actions/services/documentDirectService.js`
- `tests/phase9/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9 baseline tests and lint after rollback to confirm parity.
### Phase 10 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in 4 additional file retrieval handlers
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash values
- standardized early 400 for hash mismatch
4. `[x]` Improve logging discipline in touched handlers
- removed old commented debug traces
5. `[x]` Add focused Phase 10 tests
- added `tests/phase10/service-behaviour.test.cjs`
- includes negative-path tests for all 4 handlers
6. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (no new lint errors)
7. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing hash across selected handlers -> 400
- valid hash `getbloblist` spot-check -> 500 (downstream/local dependency)
### Phase 10 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
- `tests/phase10/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10 baseline tests and lint after rollback to confirm parity.
### Phase 11 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in additional sensitive handlers
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash values
- standardized early 400 for hash mismatch
4. `[x]` Improve logging discipline in touched handlers
- removed old commented debug traces
5. `[x]` Add focused Phase 11 tests
- added `tests/phase11/service-behaviour.test.cjs`
- includes negative-path tests for selected handlers
6. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (no new lint errors)
7. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing/invalid hash across selected handlers -> 400
- valid hash `getbloblist` spot-check -> 500 (downstream/local dependency)
### Phase 11 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- `tests/phase11/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10/phase11 baseline tests and lint after rollback to confirm parity.
### Phase 12 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in additional sensitive handlers
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash values
- standardized early 400 for hash mismatch in `downloadblob`
4. `[x]` Add focused Phase 12 tests
- added `tests/phase12/service-behaviour.test.cjs`
- includes negative-path tests for selected handlers
5. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (no new lint errors)
6. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing/invalid inputs across selected handlers -> 400
- valid hash `downloadblob` spot-check -> 500 (downstream/local dependency)
### Phase 12 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `tests/phase12/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10/phase11/phase12 baseline tests and lint after rollback to confirm parity.
### Phase 13 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in additional sensitive handlers
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash/body values
- standardized early 400 for hash mismatch in delete handlers
4. `[x]` Improve logging discipline in touched handlers
- removed noisy body/query logging in involvement handlers
5. `[x]` Add focused Phase 13 tests
- added `tests/phase13/service-behaviour.test.cjs`
- includes negative-path tests and valid-input mocked happy paths
6. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `npm run lint` -> warnings only (no new lint errors)
7. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing/invalid inputs across selected handlers -> 400
- valid hash `deleteblobcase` spot-check -> 500 (downstream/local dependency)
### Phase 13 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
- `tests/phase13/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10/phase11/phase12/phase13 baseline tests and lint after rollback to confirm parity.
### Phase 14 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Diagnose authenticated browser journey runtime error
- traced to client-side `hashAPIPath` usage reading server-only `HASHKEY`
2. `[x]` Implement minimal reversible signing bridge
- added `pages/api/endpoint/gethash_api.js`
- session-gated with allow-listed API path prefixes
3. `[x]` Keep client/service contracts stable
- updated `documentDirectService` and `portalDirectService` to request hash from signer endpoint
- kept server-side fallback hashing only when env key exists
4. `[x]` Add focused phase-14 tests
- added `tests/phase14/service-behaviour.test.cjs`
- covers 400/401 negative paths and authenticated happy path
5. `[x]` Execute validation bundle
- phase6phase14 test set all pass
- lint warnings only (pre-existing)
### Phase 14 rollback notes
- Revert these files to rollback the fix slice:
- `pages/api/endpoint/gethash_api.js`
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- `tests/phase14/service-behaviour.test.cjs`
- Re-run phase6phase14 tests + lint after rollback.
### Phase 14 closeout follow-up snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Expand signer usage to remaining browser-sensitive hashed call in account domain
- `actions/services/accountDirectService.js`
- `getPortalLogin` now requests hash from authenticated signer endpoint
2. `[x]` Extend signer allow-list minimally for required route
- `pages/api/endpoint/gethash_api.js`
- added `/api/endpoint/getportallogin_api` only
3. `[x]` Preserve and verify behavioural contract coverage
- `tests/phase7/service-behaviour.test.cjs` updated for signer flow
- `tests/phase14/service-behaviour.test.cjs` extended with allow-list path test
4. `[x]` Apply runtime-discovered contract alignment fixes (minimal/reversible)
- `pages/api/file/getrepsblob.js`
- removed unnecessary `casefolderID` requirement to match endpoint contract
- `components/elements/index.js`
- restored missing `updateLinks` import for Quill path
5. `[x]` Re-run required validation
- phase6 parity + behaviour -> pass
- phase7/8/9/10/11/12/13/14 behaviour -> pass
- lint -> warnings only (pre-existing)
6. `[x]` Push branch for review continuity
- `TASK22019-phase-9-hardening` pushed to origin
### Phase 14 closeout follow-up rollback notes
- Revert `6094874` to remove portal-login signer migration and tests.
- Revert `b8fa514` to remove getrepsblob contract-alignment + Quill import fix.
- Re-run phase6phase14 tests and lint after rollback.
## Safe execution mode for migration chunks (required)
To reduce terminal hangs during bulk migration work, run refactor chunks in **safe stepwise mode** instead of long chained commands.
Required command pattern:
1. Read/inspect target files.
2. Apply edits only.
3. Verify with targeted grep for remaining broad imports.
4. Check `git status`.
5. Stage files.
6. Commit.
7. Re-run verification and then continue to the next chunk.
Rules:
- Do **not** combine edit + verify + add + commit + log in one long chained command.
- Keep each terminal call short and single-purpose.
- If a command is interrupted, re-check `git status` and resume from the next incomplete step.
- Preserve smallest viable diff and behavior parity.
## Implementation status snapshot (2026-03-12)
- Phase 1 completed:
- Extracted core helpers into `actions/core/{env,logger,hash,token,headers}.js`.
- Moved wrapper functions into `actions/services/legacyActionsService.js`.
- Reduced `actions/index.js` to compatibility barrel re-exports.
- Added `actions/clients/README.md` scaffold for upcoming client extraction.
- Validation status:
- `npm run lint` currently fails at framework/tooling option level (legacy ESLint options), not due to this refactor logic.
- Manual test matrix is required next (search/case/myportal/auth/notify/file).
## Implementation status snapshot (Phase 2 update — 2026-03-12)
- Phase 2 completed:
- Added grouped service modules under `actions/services/`:
- `searchService.js`
- `caseService.js`
- `accountService.js`
- `portalService.js`
- `documentService.js`
- `referenceDataService.js`
- `notifyService.js`
- `adminService.js`
- `integrationService.js`
- Added `actions/services/index.js` barrel.
- Updated `actions/index.js` to export from `./services` and `./core/*`.
- Verified export parity: all `legacyActionsService` exports are represented by grouped service modules.
- Current compatibility model:
- Consumers can continue importing from `../actions` with unchanged function names/signatures.
- `legacyActionsService` remains an internal compatibility implementation module until Phase 3 consumer migration.
## Implementation status snapshot (Phase 3 update — 2026-03-12)
- Phase 3 started with targeted consumer migration to focused imports.
- Updated high-churn consumers:
- `components/case/summary.js`
- `createWatchedCases`, `deleteWatchedCases`, `getWatchedCasesProxy`, `setCaseInvolvment` -> `actions/services/portalService`
- `getLinkedCases` -> `actions/services/searchService`
- `getPortalModuleDetailsProxy` -> `actions/services/caseService`
- `pages/api/email/notify.js`
- `consoleLogger` -> `actions/core/logger`
- `getPreferredLanguage` -> `actions/services/accountService`
- Updated selected endpoint handlers to core imports:
- `pages/api/endpoint/getwatchedcases_api.js`
- `pages/api/endpoint/getadvancedsearchpaged_api.js`
- `pages/api/endpoint/getpreferredlanguage_api.js`
- `pages/api/endpoint/createcrmtask_api.js`
- Compatibility remains preserved via `actions/index.js` barrel while migration proceeds incrementally.
## Implementation status snapshot (Phase 4 update — 2026-03-12)
- Phase 4 started with targeted hardening helpers and sensitive-path adoption.
- Added `actions/core/guards.js` with:
- `isNonEmptyString`
- `sanitizeString`
- `escapeODataString`
- Expanded `actions/core/logger.js` with redaction support:
- `redactSensitive`
- masking for likely email/token/secret patterns in logged content
- Updated `actions/index.js` compatibility barrel to export `./core/guards`.
- Applied hardening in selected sensitive/high-churn handlers:
- `pages/api/email/notify.js`
- email input sanitization + required-field guard
- redacted logging for outbound payload diagnostics
- `pages/api/endpoint/getpreferredlanguage_api.js`
- sanitized/validated `emailAddress`
- OData string escaping for query construction
- redacted query logging
- `pages/api/endpoint/createcrmtask_api.js`
- sanitized/validated request inputs for subject/email/body
- centralized structured error logging via `consoleLogger`
## Implementation status snapshot (Priority 1 execution pass — 2026-03-12)
- Completed another targeted consumer migration pass from broad `actions` barrel imports to focused service/core imports in these files:
- `components/admin/tabs/documents.js`
- `components/admin/tabs/storage.js`
- `components/admin/utils/serverside.js`
- `components/case/documents.js`
- `components/case/representation/representationComplete.js`
- `components/case/representation/representationElements.js`
- `components/elements/index.js`
- `components/myportal/awaitingsubmissionfromblob.js`
- `components/myportal/topthree.js`
- `components/myportal/topthree_reps.js`
- `components/utils/index.js`
- `lib/myportal/loadMyPortalAppealPage.js`
- `lib/newappeal/loadNewAppealPage.js`
- Kept compatibility behavior via existing `actions/index.js` barrel.
- Validation note: `npm run lint` remains blocked by repository ESLint/Next option incompatibility (pre-existing tooling configuration).
## Goal
Reduce coupling and regression risk by splitting `actions/index.js` into focused modules while preserving existing behavior and call signatures during migration.
## Why this is first
`actions/index.js` is currently a high-risk hotspot: API wrappers, relay/hash helpers, token acquisition, logging, file helpers, and notification helpers are mixed in one module. This increases blast radius for every change.
## Scope (planned)
- In scope:
- Module extraction and internal architecture cleanup.
- Backward-compatible export strategy.
- Migration plan for consumers under `components/**`, `pages/**`, and `pages/api/**`.
- Out of scope (for this refactor phase):
- Functional changes to business logic.
- Contract changes to API handlers.
- Dependency swaps.
## Proposed target structure
```text
actions/
index.js # compatibility barrel (temporary)
core/
env.js # BASE_URL/API_ROOT/relay roots
logger.js # consoleLogger/conLog with redaction helpers
hash.js # hashAPIPath/hashString/dehashString
token.js # getToken + token request config
headers.js # azureHeaders* helpers
clients/
relayClient.js # signed relay calls + shared axios behavior
endpointClient.js # endpoint route wrappers
fileClient.js # file route wrappers
notifyClient.js # email/notify wrappers
services/
caseService.js
searchService.js
portalService.js
accountService.js
documentService.js
```
Notes:
- Keep `actions/index.js` as a façade initially to avoid breaking imports.
- Move internals first, then update call sites incrementally.
## Migration strategy (phased)
### Phase 1 — Safe extraction with no consumer changes
1. Create new modules under `actions/core/**` and `actions/clients/**`.
2. Move utility functions (`hash`, `headers`, `token`, `logger`, env helpers).
3. Re-export all existing functions from `actions/index.js` unchanged.
4. Verify parity with lint + smoke checks.
### Phase 2 — Service grouping
1. Group route wrapper functions into service modules by domain (case/search/portal/account/document).
2. Keep identical function names/signatures.
3. Add thin unit tests for pure helpers first (`hash`, `env`, route builder helpers).
### Phase 3 — Consumer migration
1. Replace broad imports from `actions/index.js` with targeted imports from new modules.
2. Migrate high-churn areas first:
- `components/breadcrumbs.js`
- `components/case/summary.js`
- `pages/api/email/notify.js`
- selected `pages/api/endpoint/**` handlers
3. Keep index barrel until migration completion.
### Phase 4 — Harden + slim
1. Introduce typed/validated request helper boundaries (JS doc + runtime guard checks).
2. Remove dead exports and duplicate wrappers.
3. Finalize redacted logging policy in sensitive flows.
## Backward compatibility rules
- Do not change public function names/signatures during Phases 12.
- Keep return shapes identical unless explicitly planned and validated.
- Preserve existing hash and relay behavior contract.
- Preserve EN/CY behavior where helper functions influence locale-sensitive flows.
## Validation plan
- `npm run lint`
- Manual smoke paths:
- search -> results -> case summary -> back nav
- myportal viewall -> case summary -> breadcrumb return path
- auth email sign-in flow
- notify new case reference language selection path
- file/document fetch paths touched by moved wrappers
- Negative-path checks:
- invalid hash or malformed query for sensitive handlers
- token acquisition failures and relay timeout handling
## Regression tests and coverage plan (to add during implementation)
### Test tooling recommendation
Current repo has no active automated test runner configured in `package.json`. For this refactor, introduce a minimal unit test setup (recommended: Jest) focused on pure logic first.
Suggested scripts:
- `test`: run all unit tests
- `test:watch`: local watch mode
- `test:coverage`: coverage output for CI and PR evidence
### Minimum tests for Priority 1
1. `actions/core/hash.js`
- deterministic hash for known input
- query string handling (`?hash=` vs `&hash=`)
- malformed/edge inputs
2. `actions/core/headers.js`
- expected OData/auth headers produced for each helper
3. `actions/core/token.js`
- token request config generation
- success and error mapping behavior (mock axios)
4. `actions/core/env.js`
- server vs browser base URL resolution
5. compatibility barrel (`actions/index.js`)
- exports parity test to ensure existing function names remain available during migration
### Integration-style safeguards (mocked external calls)
- Relay client request assembly test:
- signed URL includes hash
- token is attached
- timeout/error behavior is consistent
- Notify client wrapper test:
- language/template routing remains unchanged for PEDW-NEW-CASEREF path
### Coverage targets for this refactor
- `actions/core/**`: >= 90% lines/functions
- `actions/clients/**`: >= 80% lines/functions
- Global coverage gate for this phase: >= 70% (new tests only; avoid blocking unrelated legacy code)
### Regression gate in PR
Required evidence for each phase:
1. `npm run lint`
2. `npm run test`
3. `npm run test:coverage` (attach summary)
4. Manual smoke matrix from this plan (search/case/myportal/auth/notify/file)
## Risks and mitigations
- Risk: import breakage due to broad existing usage.
- Mitigation: keep compatibility barrel and migrate in small batches.
- Risk: hidden behavior differences from refactor-only moves.
- Mitigation: freeze signatures + add helper tests + run route smoke matrix each phase.
- Risk: sensitive logging leakage while touching shared helpers.
- Mitigation: centralize logger early and enforce redaction helper.
## Definition of done (for this priority)
1. `actions/index.js` reduced to compatibility exports only (minimal logic).
2. Core helper modules and service modules exist and are used by migrated consumers.
3. Lint and manual validation matrix pass.
4. Memory docs updated (`activeContext`, `progress`, `change-log`).
+36
View File
@@ -0,0 +1,36 @@
import { hashAPIPath } from "../../../actions/core/hash";
import nextConnect from "next-connect";
import { getSession } from "next-auth/react";
const ApiProxy = nextConnect();
ApiProxy.get(async (req, res) => {
const session = await getSession({ req });
if (!session) {
return res.status(401).json();
}
const queryPath = req.query.path;
const allowedPrefix = [
"/api/endpoint/getportallogin_api",
"/api/file/upload",
"/api/file/uploadsinglefile",
"/api/file/createrepcompletemessage_api",
"/api/file/createappealcompletemessage_api"
];
if (
typeof queryPath !== "string" ||
queryPath.length === 0 ||
!queryPath.startsWith("/api/") ||
!allowedPrefix.some((prefix) => queryPath.startsWith(prefix)) ||
queryPath.includes("/api/endpoint/gethash_api")
) {
return res.status(400).json();
}
return res.status(200).json({ hash: hashAPIPath(queryPath) });
});
export default ApiProxy;
+10 -2
View File
@@ -34,6 +34,16 @@ const hashAPIPath = (queryPath) => {
};
export default async function ApiProxy(req, res) {
if (
!req.body ||
typeof req.body.contactid === "undefined" ||
req.body.contactid.length === 0 ||
typeof req.body.incidentid === "undefined" ||
req.body.incidentid.length === 0
) {
return res.status(400).json();
}
var token = await getToken();
var contactid = req.body.contactid;
var queryUrl =
@@ -41,8 +51,6 @@ export default async function ApiProxy(req, res) {
req.body.incidentid +
")/pinswg_incident_contact_case_involvement/$ref";
console.log(req.body, queryUrl);
var crmUrl = "https://" + process.env.CRMURL;
var data = {
+34 -17
View File
@@ -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 = {
+10 -2
View File
@@ -34,6 +34,16 @@ const hashAPIPath = (queryPath) => {
};
export default async function ApiProxy(req, res) {
if (
!req.body ||
typeof req.body.contactid === "undefined" ||
req.body.contactid.length === 0 ||
typeof req.body.incidentid === "undefined" ||
req.body.incidentid.length === 0
) {
return res.status(400).json();
}
var token = await getToken();
var contactid = req.body.contactid;
var queryUrl =
@@ -41,8 +51,6 @@ export default async function ApiProxy(req, res) {
req.body.incidentid +
")/pinswg_incident_contact_case_involvement/$ref";
console.log(req.body, queryUrl);
var crmUrl = "https://" + process.env.CRMURL;
var crmVersion = process.env.CRMURL_VERSION;
@@ -13,6 +13,19 @@ ApiProxy.get(async (req, res) => {
var blobName = req.query.blobname;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0 ||
typeof blobName === "undefined" ||
blobName.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath =
"/api/file/deleteblob?container=" +
containerName +
@@ -21,8 +34,9 @@ ApiProxy.get(async (req, res) => {
"&blobname=" +
blobName;
//console.log(hashAPIPath(checkquerypath), checkHash);
//console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
await deleteBlob(
@@ -31,8 +45,6 @@ ApiProxy.get(async (req, res) => {
).then((data) => {
return res.status(200).json({ data: data });
});
} else {
return res.status(400).json();
}
});
+17 -2
View File
@@ -13,6 +13,19 @@ ApiProxy.get(async (req, res) => {
var blobName = req.query.blobname;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0 ||
typeof blobName === "undefined" ||
blobName.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath =
"/api/file/deleteblob?container=" +
containerName +
@@ -21,6 +34,10 @@ ApiProxy.get(async (req, res) => {
"&blobname=" +
encodeURIComponent(blobName);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
await deleteBlob(
containerName,
@@ -28,8 +45,6 @@ ApiProxy.get(async (req, res) => {
).then((data) => {
return res.status(200).json({ data: data });
});
} else {
return res.status(400).json();
}
});
+7 -3
View File
@@ -16,7 +16,9 @@ ApiProxy.get(async (req, res) => {
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0
casefolderID.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
@@ -27,12 +29,14 @@ ApiProxy.get(async (req, res) => {
"&casefolderID=" +
casefolderID;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
await deleteBlobCase(containerName, casefolderID).then((data) => {
return res.status(200).json({ data: data });
});
} else {
return res.status(400).json();
}
});
+7 -3
View File
@@ -19,7 +19,9 @@ ApiProxy.get(async (req, res) => {
typeof casefolderID === "undefined" ||
casefolderID.length === 0 ||
typeof repfile === "undefined" ||
repfile.length === 0
repfile.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
@@ -32,14 +34,16 @@ ApiProxy.get(async (req, res) => {
"&repfile=" +
repfile;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
casefolderID = casefolderID + "/" + repfile;
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
await deleteBlobRep(containerName, casefolderID).then((data) => {
return res.status(200).json({ data: data });
});
} else {
return res.status(400).json();
}
});
+17 -2
View File
@@ -16,6 +16,19 @@ ApiProxy.get(async (req, res) => {
var blobName = req.query.blobname;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0 ||
typeof blobName === "undefined" ||
blobName.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath =
"/api/file/downloadblob?container=" +
containerName +
@@ -24,6 +37,10 @@ ApiProxy.get(async (req, res) => {
"&blobname=" +
blobName.trim();
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
const bloblocation =
casefolderID + (blobName.indexOf(".json") > 0 ? "/" : "/files/");
@@ -38,8 +55,6 @@ ApiProxy.get(async (req, res) => {
"attachment; filename=" + decodeURI(blobName)
);
return res.status(200).send(downloaded);
} else {
return res.status(400).json();
}
});
@@ -14,16 +14,21 @@ ApiProxy.get(async (req, res) => {
var containerName = req.query.container;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath =
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
// console.log(
// "///////////////////////\ngetawaitingsubmissionblob url :",
// req.url,
// "\n///////////////////////\n"
// );
//console.log(hashAPIPath(checkquerypath), checkHash);
//console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
const blobObj = await getAllProgressBlobs(containerName)
@@ -33,8 +38,6 @@ ApiProxy.get(async (req, res) => {
.then((data) => {
return res.status(200).json(data);
});
} else {
return res.status(400).json();
}
});
@@ -23,6 +23,11 @@ const BASE_URL = process.env.API_ROOT || `http://localhost:${port}`;
export default async function ApiProxy(req, res) {
var containerName = req.query.container;
var checkHash = req.query.hash;
if (typeof containerName === "undefined" || containerName.length === 0) {
return res.status(400).json();
}
var token = await getToken();
var queryUrl =
+15 -8
View File
@@ -43,18 +43,27 @@ ApiProxy.get(async (req, res) => {
var casefolderID = req.query.casefolderID;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath =
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID;
// console.log("-----", casefolderID);
// console.log("-----", req.query);
// console.log("-----", checkquerypath);
// console.log("-----", hashAPIPath(checkquerypath));
// console.log("-----", checkHash);
// console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
casefolderID.split("/").length > 1
@@ -68,8 +77,6 @@ ApiProxy.get(async (req, res) => {
: await getBlobs(containerName, casefolderID).then((data) => {
return res.status(200).json({ "value": [data] });
});
} else {
return res.status(400).json();
}
});
+10
View File
@@ -24,6 +24,16 @@ export default async function ApiProxy(req, res) {
var containerName = req.query.container;
var casefolderID = req.query.casefolderID;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0
) {
return res.status(400).json();
}
var token = await getToken();
var queryUrl =
+15 -9
View File
@@ -15,23 +15,31 @@ ApiProxy.get(async (req, res) => {
var casefolderID = req.query.casefolderID;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath =
"/api/file/getprogressobjblob?container=" +
containerName +
"&casefolderID=" +
casefolderID;
// console.log("-----", casefolderID);
// console.log("-----", req.query);
// console.log("-----", checkquerypath);
// console.log("-----", hashAPIPath(checkquerypath));
// console.log("-----", checkHash);
// console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
const blobObj = await getProgressBlobs(containerName, casefolderID)
.then((data) => {
//console.log("Progress blob path:", data.path);
return downloadProgressFile(
containerName,
data.path,
@@ -41,8 +49,6 @@ ApiProxy.get(async (req, res) => {
.then((data) => {
return res.status(200).json(data);
});
} else {
return res.status(400).json();
}
});
+14 -9
View File
@@ -47,17 +47,24 @@ ApiProxy.get(async (req, res) => {
var casefolderID = req.query.casefolderID;
var checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
containerName.length === 0 ||
typeof checkHash === "undefined" ||
checkHash.length === 0
) {
return res.status(400).json();
}
var checkquerypath = "/api/file/getrepsblob?container=" + containerName;
//console.log("-----", casefolderID);
//console.log("-----", req.query);
//console.log("-----", checkquerypath);
//console.log("-----", hashAPIPath(checkquerypath));
//console.log("-----", checkHash);
//console.log(hashAPIPath(checkquerypath) == "&hash=" + checkHash);
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
}
if (hashAPIPath(checkquerypath) == "&hash=" + checkHash) {
const blobObj = await getRepsBlobs(containerName, casefolderID)
const blobObj = await getRepsBlobs(containerName)
.then(async (data) => {
return data;
})
@@ -72,8 +79,6 @@ ApiProxy.get(async (req, res) => {
.catch((error) => {
consoleLogger(error);
});
} else {
return res.status(400).json();
}
});
+5
View File
@@ -23,6 +23,11 @@ const BASE_URL = process.env.API_ROOT || `http://localhost:${port}`;
export default async function ApiProxy(req, res) {
var containerName = req.query.container;
var checkHash = req.query.hash;
if (typeof containerName === "undefined" || containerName.length === 0) {
return res.status(400).json();
}
var token = await getToken();
var queryUrl = "/api/file/getrepsblob?container=" + containerName;
+10 -8
View File
@@ -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();
}
});
+20 -28
View File
@@ -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;
+20 -18
View File
@@ -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
}
}
+236
View File
@@ -0,0 +1,236 @@
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;
}
};
return () => router;
};
const createRes = () => {
const state = {
statusCode: null,
jsonBody: undefined
};
return {
state,
setHeader: () => {},
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("getawaitingsubmissionfromblob rejects missing hash with 400", async () => {
const calls = [];
const nextConnect = createNextConnectMock();
const mod = loadModule("pages/api/file/getawaitingsubmissionfromblob.js", {
hashAPIPath: () => "&hash=expected",
getAllProgressBlobs: async (...args) => {
calls.push(args);
return [];
},
downloadAllProgressFiles: async () => [],
_: { isEmpty: (value) => !value },
nextConnect,
middleware: () => {}
});
const req = { query: { container: "c1" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("getprogressobjblob rejects missing casefolderID with 400", async () => {
const calls = [];
const nextConnect = createNextConnectMock();
const mod = loadModule("pages/api/file/getprogressobjblob.js", {
hashAPIPath: () => "&hash=expected",
getProgressBlobs: async (...args) => {
calls.push(args);
return { path: "x" };
},
downloadProgressFile: async () => ({}),
_: { isEmpty: (value) => !value },
nextConnect,
middleware: () => {}
});
const req = {
query: { container: "c1", hash: "expected" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("getbloblist rejects invalid hash with 400", async () => {
const calls = [];
const nextConnect = createNextConnectMock();
const mod = loadModule("pages/api/file/getbloblist.js", {
hashAPIPath: () => "&hash=expected",
getBlobs: async (...args) => {
calls.push(args);
return [];
},
getRepsFilesBlobs: async () => [],
nextConnect,
middleware: () => {}
});
const req = {
query: {
container: "c1",
casefolderID: "case-1",
hash: "wrong"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("getrepsblob rejects missing hash with 400", async () => {
const calls = [];
const nextConnect = createNextConnectMock();
const mod = loadModule("pages/api/file/getrepsblob.js", {
hashAPIPath: () => "&hash=expected",
getRepsBlobs: async (...args) => {
calls.push(args);
return [];
},
downloadAllRepsFiles: async () => [],
consoleLogger: () => {},
_: { isEmpty: (value) => !value },
nextConnect,
middleware: () => {}
});
const req = {
query: {
container: "c1",
casefolderID: "case-1"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("getbloblist happy path with valid hash returns 200", async () => {
const nextConnect = createNextConnectMock();
const mod = loadModule("pages/api/file/getbloblist.js", {
hashAPIPath: () => "&hash=expected",
getBlobs: async () => ["file-1"],
getRepsFilesBlobs: async () => ["file-rep"],
nextConnect,
middleware: () => {}
});
const req = {
query: {
container: "c1",
casefolderID: "case-1",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [["file-1"]]
});
});
const run = async () => {
let passed = 0;
for (const currentTest of tests) {
await currentTest.fn();
passed += 1;
}
console.log(
`Phase 10 behavioural tests passed (${passed}/${tests.length}).`
);
};
run().catch((error) => {
console.error(error);
process.exit(1);
});
+171
View File
@@ -0,0 +1,171 @@
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: () => {}, error: () => {} },
...injected
};
vm.runInNewContext(source, context, { filename: filePath });
return context.module.exports;
};
const createNextConnectMock = () => {
const router = {
handler: null,
use: () => {},
get(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("getbloblist rejects missing hash with 400", async () => {
const calls = [];
const mod = loadModule("pages/api/file/getbloblist.js", {
hashAPIPath: () => "&hash=expected",
getBlobs: async (...args) => {
calls.push(args);
return [];
},
getRepsFilesBlobs: async () => [],
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = { query: { container: "c1", casefolderID: "case-1" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("deleteblob rejects missing blobname with 400", async () => {
const calls = [];
const mod = loadModule("pages/api/file/deleteblob.js", {
hashAPIPath: () => "&hash=expected",
deleteBlob: async (...args) => {
calls.push(args);
return true;
},
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: { container: "c1", casefolderID: "case-1", hash: "expected" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("deleteawaitingsubmissionfromblob rejects invalid hash with 400", async () => {
const calls = [];
const mod = loadModule(
"pages/api/file/deleteawaitingsubmissionfromblob.js",
{
hashAPIPath: () => "&hash=expected",
deleteBlob: async (...args) => {
calls.push(args);
return true;
},
nextConnect: createNextConnectMock(),
middleware: () => {}
}
);
const req = {
query: {
container: "c1",
casefolderID: "case-1",
blobname: "f.pdf",
hash: "wrong"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("getbloblist valid hash returns 200", async () => {
const mod = loadModule("pages/api/file/getbloblist.js", {
hashAPIPath: () => "&hash=expected",
getBlobs: async () => ["f1"],
getRepsFilesBlobs: async () => ["rf1"],
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: { container: "c1", casefolderID: "case-1", hash: "expected" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [["f1"]]
});
});
const run = async () => {
let passed = 0;
for (const t of tests) {
await t.fn();
passed += 1;
}
console.log(
`Phase 11 behavioural tests passed (${passed}/${tests.length}).`
);
};
run().catch((error) => {
console.error(error);
process.exit(1);
});
+169
View File
@@ -0,0 +1,169 @@
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: () => {}, error: () => {} },
port: 3000,
...injected
};
vm.runInNewContext(source, context, { filename: filePath });
return context.module.exports;
};
const createNextConnectMock = () => {
const router = {
handler: null,
use: () => {},
get(fn) {
this.handler = fn;
}
};
return () => router;
};
const createRes = () => {
const state = {
statusCode: null,
jsonBody: undefined,
sentBody: undefined,
headers: {}
};
return {
state,
status(code) {
state.statusCode = code;
return this;
},
json(payload) {
state.jsonBody = payload;
return payload;
},
send(payload) {
state.sentBody = payload;
return payload;
},
setHeader(name, value) {
state.headers[name] = value;
}
};
};
const tests = [];
const test = (name, fn) => tests.push({ name, fn });
test("downloadblob rejects missing hash with 400", async () => {
const calls = [];
const mod = loadModule("pages/api/file/downloadblob.js", {
hashAPIPath: () => "&hash=expected",
downloadFile: async (...args) => {
calls.push(args);
return Buffer.from("x");
},
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: { container: "c1", casefolderID: "case-1", blobname: "f.pdf" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("downloadblob valid hash returns 200", async () => {
const mod = loadModule("pages/api/file/downloadblob.js", {
hashAPIPath: () => "&hash=expected",
downloadFile: async () => Buffer.from("file"),
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: {
container: "c1",
casefolderID: "case-1",
blobname: "f.pdf",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
});
test("getbloblistproxy rejects missing container with 400", async () => {
const mod = loadModule("pages/api/file/getbloblistproxy.js", {
getToken: async () => ({ access_token: "t" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { ok: true } }) },
consoleLogger: () => {}
});
const req = { query: { casefolderID: "case-1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
});
test("getawaitingsubmissionfromblobproxy rejects missing container with 400", async () => {
const mod = loadModule(
"pages/api/file/getawaitingsubmissionfromblobproxy.js",
{
getToken: async () => ({ access_token: "t" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { ok: true } }) },
consoleLogger: () => {}
}
);
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
});
const run = async () => {
let passed = 0;
for (const t of tests) {
await t.fn();
passed += 1;
}
console.log(
`Phase 12 behavioural tests passed (${passed}/${tests.length}).`
);
};
run().catch((error) => {
console.error(error);
process.exit(1);
});
+197
View File
@@ -0,0 +1,197 @@
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: () => {}, error: () => {} },
...injected
};
vm.runInNewContext(source, context, { filename: filePath });
return context.module.exports;
};
const createNextConnectMock = () => {
const router = {
handler: null,
use: () => {},
get(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("deleteblobcase rejects missing hash with 400", async () => {
const calls = [];
const mod = loadModule("pages/api/file/deleteblobcase.js", {
hashAPIPath: () => "&hash=expected",
deleteBlobCase: async (...args) => {
calls.push(args);
return true;
},
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = { query: { container: "c1", casefolderID: "case-1" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("deleteblobrep rejects missing hash with 400", async () => {
const calls = [];
const mod = loadModule("pages/api/file/deleteblobrep.js", {
hashAPIPath: () => "&hash=expected",
deleteBlobRep: async (...args) => {
calls.push(args);
return true;
},
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: { container: "c1", casefolderID: "case-1", repfile: "r.pdf" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(calls.length, 0);
});
test("createcaseinvolvement_api rejects missing required body values with 400", async () => {
const mod = loadModule("pages/api/file/createcaseinvolvement_api.js", {
getToken: async () => ({ access_token: "token" }),
axios: async () => ({ data: { ok: true } }),
consoleLogger: () => {}
});
const req = { body: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
});
test("createrepinvolvement_api rejects missing required body values with 400", async () => {
const mod = loadModule("pages/api/file/createrepinvolvement_api.js", {
getToken: async () => ({ access_token: "token" }),
axios: async () => ({ data: { ok: true } }),
consoleLogger: () => {}
});
const req = { body: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
});
test("deleteblobcase valid hash returns 200", async () => {
const mod = loadModule("pages/api/file/deleteblobcase.js", {
hashAPIPath: () => "&hash=expected",
deleteBlobCase: async () => ({ ok: true }),
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: { container: "c1", casefolderID: "case-1", hash: "expected" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
});
test("createcaseinvolvement_api with valid body returns 200", async () => {
const mod = loadModule("pages/api/file/createcaseinvolvement_api.js", {
getToken: async () => ({ access_token: "token" }),
CryptoJS: {
HmacSHA256: () => ({ toString: () => "signed" }),
enc: { Hex: { parse: () => "" } }
},
axios: async () => ({ data: { ok: true } }),
consoleLogger: () => {}
});
const req = { body: { contactid: "c1", incidentid: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
});
test("createrepinvolvement_api with valid body returns 200", async () => {
const mod = loadModule("pages/api/file/createrepinvolvement_api.js", {
getToken: async () => ({ access_token: "token" }),
CryptoJS: {
HmacSHA256: () => ({ toString: () => "signed" }),
enc: { Hex: { parse: () => "" } }
},
axios: async () => ({ data: { ok: true } }),
consoleLogger: () => {}
});
const req = { body: { contactid: "c1", incidentid: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
});
const run = async () => {
let passed = 0;
for (const t of tests) {
await t.fn();
passed += 1;
}
console.log(
`Phase 13 behavioural tests passed (${passed}/${tests.length}).`
);
};
run().catch((error) => {
console.error(error);
process.exit(1);
});
+164
View File
@@ -0,0 +1,164 @@
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: () => {}, error: () => {} },
...injected
};
vm.runInNewContext(source, context, { filename: filePath });
return context.module.exports;
};
const createNextConnectMock = () => {
const router = {
handler: null,
use: () => {},
get(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("gethash_api rejects missing path with 400", async () => {
const mod = loadModule("pages/api/endpoint/gethash_api.js", {
hashAPIPath: () => "?hash=expected",
getSession: async () => ({ user: { id: "u1" } }),
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
});
test("gethash_api rejects non-api path with 400", async () => {
const mod = loadModule("pages/api/endpoint/gethash_api.js", {
hashAPIPath: () => "?hash=expected",
getSession: async () => ({ user: { id: "u1" } }),
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = { query: { path: "/not-api/path" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
});
test("gethash_api returns hash for valid api path", async () => {
const mod = loadModule("pages/api/endpoint/gethash_api.js", {
hashAPIPath: () => "&hash=expected",
getSession: async () => ({ user: { id: "u1" } }),
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = { query: { path: "/api/file/upload" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
hash: "&hash=expected"
});
});
test("gethash_api returns hash for allow-listed getportallogin path", async () => {
const mod = loadModule("pages/api/endpoint/gethash_api.js", {
hashAPIPath: () => "&hash=login",
getSession: async () => ({ user: { id: "u1" } }),
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = {
query: { path: "/api/endpoint/getportallogin_api?emailAddress=a@b.com" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
hash: "&hash=login"
});
});
test("gethash_api rejects unauthenticated requests with 401", async () => {
const mod = loadModule("pages/api/endpoint/gethash_api.js", {
hashAPIPath: () => "&hash=expected",
getSession: async () => null,
nextConnect: createNextConnectMock(),
middleware: () => {}
});
const req = { query: { path: "/api/file/upload" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 401);
});
const run = async () => {
let passed = 0;
for (const t of tests) {
await t.fn();
passed += 1;
}
console.log(
`Phase 14 behavioural tests passed (${passed}/${tests.length}).`
);
};
run().catch((error) => {
console.error(error);
process.exit(1);
});
+21 -12
View File
@@ -208,31 +208,34 @@ test("portal/deleteMyRepresentations logs and returns undefined on failure", asy
test("account/getPortalLogin appends hash and returns res.data", async () => {
const axios = createAxiosMock();
const logger = createLoggerMock();
const hashCalls = [];
const hashAPIPath = (queryPath) => {
hashCalls.push(queryPath);
return "&hash=login123";
};
const signCalls = [];
axios.getHandler = async () => ({ data: { value: [{ id: "user-1" }] } });
axios.getHandler = async (url) => {
if (url.startsWith("/api/endpoint/gethash_api?path=")) {
signCalls.push(url);
return { data: { hash: "&hash=login123" } };
}
return { data: { value: [{ id: "user-1" }] } };
};
const account = loadServiceModule("accountDirectService.js", {
axios,
BASE_URL: "http://example.local",
consoleLogger: logger.consoleLogger,
hashAPIPath
hashAPIPath: () => "&hash=fallback"
});
const result = await account.getPortalLogin("person@example.com");
assert.deepStrictEqual(normalize(result), { value: [{ id: "user-1" }] });
assert.strictEqual(hashCalls.length, 1);
assert.strictEqual(signCalls.length, 1);
assert.strictEqual(
hashCalls[0],
"/api/endpoint/getportallogin_api?emailAddress=person@example.com"
signCalls[0],
"/api/endpoint/gethash_api?path=%2Fapi%2Fendpoint%2Fgetportallogin_api%3FemailAddress%3Dperson%40example.com"
);
assert.strictEqual(
axios.calls[0].url,
axios.calls[1].url,
"http://example.local/api/endpoint/getportallogin_api?emailAddress=person@example.com&hash=login123"
);
});
@@ -242,7 +245,13 @@ test("account/getPortalLogin returns JSON stringified error on failure", async (
const logger = createLoggerMock();
const error = createAxiosError(500, "Broken");
axios.getHandler = async () => Promise.reject(error);
axios.getHandler = async (url) => {
if (url.startsWith("/api/endpoint/gethash_api?path=")) {
return { data: { hash: "&hash=err" } };
}
return Promise.reject(error);
};
const account = loadServiceModule("accountDirectService.js", {
axios,
+250
View File
@@ -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);
});