From 27e5282614b22de4fce1b65ac932befcf11145cf Mon Sep 17 00:00:00 2001 From: Jason Chuang Date: Sat, 19 Sep 2026 13:18:06 +0800 Subject: [PATCH 1/5] permission policy --- docs/csp-apache.conf.md | 1 + docs/permissions-policy-apache.conf.md | 152 +++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 docs/permissions-policy-apache.conf.md diff --git a/docs/csp-apache.conf.md b/docs/csp-apache.conf.md index c7d578e7..cdce4c4a 100644 --- a/docs/csp-apache.conf.md +++ b/docs/csp-apache.conf.md @@ -6,6 +6,7 @@ Adjust `report-uri` if your API base path or host differs (UAT example below; PR See also: +- [permissions-policy-apache.conf.md](./permissions-policy-apache.conf.md) — Permissions-Policy reporting via the same `/api/csp-report` URL - [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — latest PROD + UAT report review and add/remove summary - [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — earlier PROD report analysis that drove `frame-src` / `media-src` updates diff --git a/docs/permissions-policy-apache.conf.md b/docs/permissions-policy-apache.conf.md new file mode 100644 index 00000000..9fdb4a65 --- /dev/null +++ b/docs/permissions-policy-apache.conf.md @@ -0,0 +1,152 @@ +# Permissions-Policy reporting (Apache) + +The live `Permissions-Policy` header denies features with an empty allowlist +(`feature=()` — grant to nobody). That header does **not** send reports by +itself. + +Unlike CSP, Permissions-Policy has **no `report-uri`**. Reporting uses the +Reporting API: a named endpoint in `Reporting-Endpoints`, plus a per-feature +`report-to` parameter. + +Reuse the existing CSP collector: + +- UAT: `https://pnspsuat.gld.gov.hk/api/csp-report` +- PROD: `https://pnsps.gld.gov.hk/api/csp-report` + +Backend: `CspReportController` (`POST /csp-report`) already logs the raw JSON +and `Content-Type` and returns `204`. No backend change is required. + +See also: [csp-apache.conf.md](./csp-apache.conf.md) + +## Why not `report-uri` + +This CSP-style trailer does **not** produce Permissions-Policy reports: + +```apache +# Wrong — browsers ignore report-uri on Permissions-Policy +Header always set Permissions-Policy "camera=(), geolocation=(); report-uri https://pnspsuat.gld.gov.hk/api/csp-report" +``` + +`report-to` is a **parameter of each feature**, not a global trailing directive. + +## Does every feature need `report-to`? + +Yes, **if you want a report for that feature**. + +- `feature=()` — still blocked, but silent (no report) +- `feature=();report-to=csp-endpoint` — blocked **and** reported + +There is no `all=()` reporter and no header-level `report-to` that applies to +every feature. Add `;report-to=csp-endpoint` only on features you want in the +log. Leave the rest as `()` if you do not need those reports. + +```apache +# camera is reported; geolocation is still blocked, but not reported +Header always set Permissions-Policy "camera=();report-to=csp-endpoint, geolocation=()" +``` + +## Enforcing + reporting + +Keep the current deny-all policy. Add `Reporting-Endpoints`, then append +`;report-to=csp-endpoint` on each feature you want to monitor. + +Copy the feature list from the live header; only the `report-to` parameter is +new. Example (UAT): + +```apache +Header always set Reporting-Endpoints "csp-endpoint=\"https://pnspsuat.gld.gov.hk/api/csp-report\"" + +Header always set Permissions-Policy "\ + accelerometer=();report-to=csp-endpoint, \ + autoplay=();report-to=csp-endpoint, \ + camera=();report-to=csp-endpoint, \ + display-capture=();report-to=csp-endpoint, \ + encrypted-media=();report-to=csp-endpoint, \ + fullscreen=();report-to=csp-endpoint, \ + geolocation=();report-to=csp-endpoint, \ + gyroscope=();report-to=csp-endpoint, \ + magnetometer=();report-to=csp-endpoint, \ + microphone=();report-to=csp-endpoint, \ + midi=();report-to=csp-endpoint, \ + payment=();report-to=csp-endpoint, \ + picture-in-picture=();report-to=csp-endpoint, \ + publickey-credentials-get=();report-to=csp-endpoint, \ + screen-wake-lock=();report-to=csp-endpoint, \ + usb=();report-to=csp-endpoint, \ + web-share=();report-to=csp-endpoint, \ + xr-spatial-tracking=();report-to=csp-endpoint" +``` + +PROD: same headers, with + +`https://pnsps.gld.gov.hk/api/csp-report` + +Because deny-all is already enforced, put `report-to` on **`Permissions-Policy`** +(not only Report-Only). Those reports have `disposition: "enforce"`. + +## Report-Only (observe without blocking) + +Use `Permissions-Policy-Report-Only` only when testing a restriction that is +**not** already denied by the enforcing header. Reports have +`disposition: "report"`. + +```apache +Header always set Reporting-Endpoints "csp-endpoint=\"https://pnspsuat.gld.gov.hk/api/csp-report\"" +Header always set Permissions-Policy-Report-Only "geolocation=();report-to=csp-endpoint" +``` + +Report-Only cannot re-enable a feature already denied by `Permissions-Policy`. + +## Same URL, different payload + +Keep CSP on `report-uri` as it is. The collector URL is shared; the body is not. + +| Source | `Content-Type` | Body | +| --- | --- | --- | +| CSP `report-uri` | `application/csp-report` | `{ "csp-report": { … } }` | +| Permissions-Policy | `application/reports+json` | JSON **array**, `type` = `permissions-policy-violation` | + +Example Permissions-Policy report: + +```json +[{ + "type": "permissions-policy-violation", + "url": "https://pnspsuat.gld.gov.hk/", + "body": { + "disposition": "enforce", + "featureId": "geolocation", + "message": "Permissions policy violation: geolocation access has been blocked because of a permissions policy applied to the current document." + } +}] +``` + +Chrome often serializes the feature as `policyId` instead of `featureId`. Filter +logs on `permissions-policy-violation` vs `csp-report` so the two streams stay +distinct. + +Optional: also point CSP at the same named endpoint (CSP `report-uri` remains +for older browsers): + +```apache +Header always set Content-Security-Policy-Report-Only "…; report-uri https://pnspsuat.gld.gov.hk/api/csp-report; report-to csp-endpoint" +``` + +## Follow-up checklist + +- [ ] Confirm the live Apache `Permissions-Policy` feature list (deny-all `()`). +- [ ] Add `Reporting-Endpoints` → existing `/api/csp-report` (UAT vs PROD host). +- [ ] Add `;report-to=csp-endpoint` only on features you want in the log. +- [ ] Deploy to UAT first; trigger a blocked API (e.g. `navigator.geolocation`) in Chrome/Edge. +- [ ] Confirm a `permissions-policy-violation` line in the backend log (may be batched, a few seconds later). +- [ ] Repeat on PROD with the PROD report URL. + +## Notes + +- Reporting is Chromium-only (Chrome / Edge). Safari and Firefox still enforce + `()` and usually send nothing. +- Reports are batched and may arrive a few seconds after the violation, not on + the same page request. +- Same-origin `/api/csp-report` needs no extra CORS setup. The endpoint is + already unauthenticated and CSRF is disabled. +- Do not allowlist a feature in `Permissions-Policy` just to silence a report. + Only grant a feature if the application itself needs it. From e10a788123fd274c125311646ada6a8aea84c835 Mon Sep 17 00:00:00 2001 From: Jason Chuang Date: Tue, 22 Sep 2026 15:03:29 +0800 Subject: [PATCH 2/5] csp update --- docs/csp-apache.conf.md | 3 +- docs/csp-report-review-2026-08-03.md | 1 + docs/csp-report-review-2026-08-14.md | 1 + docs/csp-report-review-2026-09-22.md | 197 +++++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 docs/csp-report-review-2026-09-22.md diff --git a/docs/csp-apache.conf.md b/docs/csp-apache.conf.md index cdce4c4a..b92563d0 100644 --- a/docs/csp-apache.conf.md +++ b/docs/csp-apache.conf.md @@ -7,7 +7,8 @@ Adjust `report-uri` if your API base path or host differs (UAT example below; PR See also: - [permissions-policy-apache.conf.md](./permissions-policy-apache.conf.md) — Permissions-Policy reporting via the same `/api/csp-report` URL -- [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — latest PROD + UAT report review and add/remove summary +- [csp-report-review-2026-09-22.md](./csp-report-review-2026-09-22.md) — latest PROD report review (`All_CSP.txt`) +- [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — earlier PROD + UAT report review and add/remove summary - [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — earlier PROD report analysis that drove `frame-src` / `media-src` updates ## Enforcing diff --git a/docs/csp-report-review-2026-08-03.md b/docs/csp-report-review-2026-08-03.md index 5200455c..db91127c 100644 --- a/docs/csp-report-review-2026-08-03.md +++ b/docs/csp-report-review-2026-08-03.md @@ -13,6 +13,7 @@ Related app docs: - [csp-apache.conf.md](./csp-apache.conf.md) — Apache header snippets to deploy - [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — later PROD + UAT review (add/remove summary) +- [csp-report-review-2026-09-22.md](./csp-report-review-2026-09-22.md) — later PROD review (`All_CSP.txt`) --- diff --git a/docs/csp-report-review-2026-08-14.md b/docs/csp-report-review-2026-08-14.md index 34cad47f..6c0e17d7 100644 --- a/docs/csp-report-review-2026-08-14.md +++ b/docs/csp-report-review-2026-08-14.md @@ -14,6 +14,7 @@ Related app docs: - [csp-apache.conf.md](./csp-apache.conf.md) — Apache header snippets to deploy - [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — earlier PROD review +- [csp-report-review-2026-09-22.md](./csp-report-review-2026-09-22.md) — later PROD review (`All_CSP.txt`) --- diff --git a/docs/csp-report-review-2026-09-22.md b/docs/csp-report-review-2026-09-22.md new file mode 100644 index 00000000..837d9c54 --- /dev/null +++ b/docs/csp-report-review-2026-09-22.md @@ -0,0 +1,197 @@ +# CSP Report Review (PROD, 2026-09-22) + +Review of Content-Security-Policy-Report-Only violations from PNSPS PROD +(`https://pnsps.gld.gov.hk`). + +Source logs (external): + +- `All_CSP.txt` — 196 reports, 2026-08-27 to 2026-09-17 + +Related app docs: + +- [csp-apache.conf.md](./csp-apache.conf.md) — Apache header snippets to deploy +- [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — earlier PROD + UAT review +- [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — earlier PROD review + +--- + +## Verdict + +**No app-side CSP concern in this dump.** Almost every line is a browser +extension, not PNSPS code. `/proof/reply/{id}` is just a busy authenticated +page, so the same extension noise shows up there a lot. + +CSP is still **Report-Only** (`disposition: report`) — the browser logged these +hits and **did not block the page**. + +The 2026-08-14 app-owned issue (proof PDF preview `frame-src`) is **gone** from +this dump. Live PROD now includes `frame-src 'self' data: blob:;`. + +--- + +## Add / remove summary + +**PROD — add nothing required. Remove nothing.** + +Keep `script-src 'self'`. Do not add `'unsafe-inline'`, `'unsafe-eval'`, or +`'wasm-unsafe-eval'`. + +**Optional (low priority, already in [csp-apache.conf.md](./csp-apache.conf.md)):** + +```apache +media-src 'self' blob: data:; +``` + +Live PROD is still `media-src 'self' blob:` (no `data:`). This dump has only +2 matching hits in three weeks. + +**Do not add:** `'wasm-unsafe-eval'`, Perplexity CDN, Youdao, NetEase CDN, +Google Fonts. + +--- + +## Confirmed current PROD policy (Report-Only) + +This is the live header that generated every report in `All_CSP.txt`. It matches +`original-policy` in all 196 violations. + +```apache +Header always set Content-Security-Policy-Report-Only "default-src 'self'; \ + base-uri 'self'; \ + object-src 'none'; \ + frame-ancestors 'none'; \ + frame-src 'self' data: blob:; \ + form-action 'self'; \ + script-src 'self'; \ + style-src 'self' 'unsafe-inline'; \ + style-src-elem 'self' 'unsafe-inline'; \ + img-src 'self' data:; \ + media-src 'self' blob:; \ + font-src 'self' data:; \ + connect-src 'self'; \ + upgrade-insecure-requests; \ + report-uri https://pnsps.gld.gov.hk/api/csp-report" +``` + +Changes vs the 2026-08-14 live header: + +- `frame-src 'self' data: blob:;` is now present. +- `img-src` no longer allowlists `https://www.w3.org` / `https://w3.org` + (badge is hosted locally). + +`style-src 'unsafe-inline'` is expected for MUI and is the only real policy +weakness. These reports do not show a new XSS issue. + +--- + +## Summary of findings + +| Count | Directive | Blocked | Verdict | +|---:|---|---|---| +| **192** | `script-src` | `wasm-eval` | Noise — Chrome extension | +| 2 | `media-src` | `data` | Optional leftover — `data:` not in live `media-src` | +| 1 | `font-src` | `frontend-cdn.perplexity.ai` … `FKGroteskNeue.woff2` | Noise — Perplexity sidebar | +| 1 | `img-src` | `ydlunacommon-cdn.nosdn.127.net` … `.svg` | Noise — Youdao / NetEase plugin | + +No `frame-src` reports. No app `eval`. No Google Fonts. + +--- + +## Why `wasm-eval` volume is high + +192 of 196 reports (98%) are `script-src` / `wasm-eval`. + +| Count | `source-file` | Notes | +|---:|---|---| +| 103 | `chrome-extension` | Every one with a location is **line 23, column 39316** — the same injected script | +| 89 | (none) | Chrome often sends a second report for the same violation without `source-file` | + +About 46 timestamps have 2+ reports in the same second (paired duplicates). +That is why the log looks twice as large as the number of real events. + +Password managers, translators, Grammarly-style tools, and similar extensions +compile WebAssembly into the page. `script-src 'self'` (without +`'wasm-unsafe-eval'`) reports that. Do **not** loosen `script-src` to silence +it. + +If quieter logs are needed, filter `blocked-uri=wasm-eval` plus +`source-file=chrome-extension` (and the paired empty-source twin). + +--- + +## Why many `/proof/reply` reports + +| Count | Page group | +|---:|---| +| 90 | `/login` | +| **38** | `/proof/reply/{id}` | +| 23 | `/publicNotice/{id}` | +| 15 | `/publicNotice/apply` | +| 10 | `/publicNotice` | +| 10 | `/proof/search` | +| 4 | `/paymentPage/*` | +| 3 | `/user/changePassword` | +| 2 | `/registerFromOrganization` | +| 1 | `/verify/*` | + +The 38 Reply Proof hits span **12 proof IDs** (`29518`, `29948`, `30112`, +`30114`, `30134`, `30166`, `30190`, `30266`, `30306`, `30326`, `30484`, +`30958`). They are the same `wasm-eval` + Chrome extension pattern as login, +apply, and notice detail. + +Reply Proof is a long, authenticated screen. Anyone with a WASM extension +triggers a report on every visit. **No Reply Proof code change is required.** + +--- + +## Not a PNSPS SPA problem — do not allowlist + +| Count | Page | Directive | Blocked | Why ignore | +|---:|---|---|---|---| +| 192 | many (login 90, reply 38, …) | `script-src` | `wasm-eval` | `chrome-extension` (line 23, col 39316) or paired empty source | +| 1 | `/login` | `font-src` | Perplexity `FKGroteskNeue.woff2` | Sidebar / extension | +| 1 | `/login` | `img-src` | NetEase `ydlunacommon-cdn.nosdn.127.net` | Youdao-style translator | +| 2 | `/publicNotice/apply`, `/proof/reply/30166` | `media-src` | `data` | Optional `data:` gap only; 2 hits in three weeks | + +`status-code: 404` on 166 reports is typical SPA client-route noise, not a +broken document. + +--- + +## Optional: `media-src` and `data:` + +Live policy: `media-src 'self' blob:;` + +Captcha audio already uses `blob:` (`CustomFormWizard.js` and similar). Adding +`data:` is low risk and already documented: + +```apache +media-src 'self' blob: data:; +``` + +Not required for this dump. + +--- + +## Recommended actions + +1. **No Apache change required** for these reports. `frame-src` deploy from + 2026-08-14 looks effective (zero `frame-src` hits here). +2. Keep PROD at `script-src 'self'`. Do not add `'unsafe-eval'` or + `'wasm-unsafe-eval'`. +3. Optional: add `data:` to `media-src` to match + [csp-apache.conf.md](./csp-apache.conf.md). +4. Keep Report-Only until remaining volume is understood as extension noise; + then promote the same policy to enforcing. +5. Optional log hygiene: drop or sample `wasm-eval` + `chrome-extension` so + real app regressions stay visible. + +No application code change is required. + +--- + +## Backend reference + +- Report endpoint: `POST /csp-report` (context path → `/api/csp-report`) +- Controller: `CspReportController` in PNSPS-backend +- Frontend/Apache CSP above is what browsers apply to the SPA document From 064a9981d0857998af5716645b7957a20eca05ba Mon Sep 17 00:00:00 2001 From: Jason Chuang Date: Fri, 18 Sep 2026 21:13:28 +0800 Subject: [PATCH 3/5] npm compile refine --- src/utils/HttpUtils.js | 280 +++++++++++++++++--------------- src/utils/registerValidation.js | 95 +++++------ src/utils/sanitizeHtml.js | 16 +- 3 files changed, 197 insertions(+), 194 deletions(-) diff --git a/src/utils/HttpUtils.js b/src/utils/HttpUtils.js index fe8aea75..e58f219d 100644 --- a/src/utils/HttpUtils.js +++ b/src/utils/HttpUtils.js @@ -1,174 +1,188 @@ -import axios from "axios"; -import { FILE_UP_POST, FILE_DOWN_GET } from "../utils/ApiPathConst"; +import axios from 'axios'; +import { FILE_UP_POST, FILE_DOWN_GET } from '../utils/ApiPathConst'; import qs from 'qs'; export const get = ({ url, params, onSuccess, onFail, onError, onFinally }) => { - axios.get(url, { - params, - paramsSerializer: (p) => qs.stringify(p, { arrayFormat: 'repeat' }) // <-- FIX - }).then( - (response) => { onResponse(response, onSuccess, onFail); } - ).catch((error) => { - return handleError(error, onError); - }).finally(() => { - if (typeof onFinally === 'function') { - onFinally(); - } + axios + .get(url, { + params, + paramsSerializer: (p) => qs.stringify(p, { arrayFormat: 'repeat' }) // <-- FIX + }) + .then((response) => { + onResponse(response, onSuccess, onFail); + }) + .catch((error) => { + return handleError(error, onError); + }) + .finally(() => { + if (typeof onFinally === 'function') { + onFinally(); + } }); }; //TODO export const put = ({ url, params, onSuccess, onFail, onError }) => { - axios.put(url, params).then( - (response) => { onResponse(response, onSuccess, onFail); } - ).catch(error => { - return handleError(error, onError); + axios + .put(url, params) + .then((response) => { + onResponse(response, onSuccess, onFail); + }) + .catch((error) => { + return handleError(error, onError); }); }; export const patch = ({ url, params, onSuccess, onFail, onError }) => { - axios.patch(url, params).then( - (response) => { onResponse(response, onSuccess, onFail); } - ).catch(error => { - return handleError(error, onError); + axios + .patch(url, params) + .then((response) => { + onResponse(response, onSuccess, onFail); + }) + .catch((error) => { + return handleError(error, onError); }); }; export const del = ({ url, params, onSuccess, onFail, onError }) => { - axios.delete(url, { params }).then( - (response) => { onResponse(response, onSuccess, onFail); } - ).catch(error => { - return handleError(error, onError); + axios + .delete(url, { params }) + .then((response) => { + onResponse(response, onSuccess, onFail); + }) + .catch((error) => { + return handleError(error, onError); }); }; export const post = ({ url, params, onSuccess, onFail, onError, headers }) => { - headers = headers ? headers : { - "Content-Type": "application/json" - }; - - axios.post(url, params, - { - headers: headers - }).then( - (response) => { onResponse(response, onSuccess, onFail); } - ).catch(error => { - return handleError(error, onError); - }); + headers = headers + ? headers + : { + 'Content-Type': 'application/json' + }; + + axios + .post(url, params, { + headers: headers + }) + .then((response) => { + onResponse(response, onSuccess, onFail); + }) + .catch((error) => { + return handleError(error, onError); + }); }; export const postWithFiles = ({ url, params, files, onSuccess, onFail, onError }) => { - var formData = new FormData(); - for (let i = 0; i < files.length; i++) { - const file = files[i] - formData.append("multipartFileList", file); + var formData = new FormData(); + for (let i = 0; i < files.length; i++) { + const file = files[i]; + formData.append('multipartFileList', file); + } + if (params) + for (var key in params) { + if (typeof params[key] === 'object') { + formData.append(key, JSON.stringify(params[key])); + } else { + formData.append(key, params[key]); + } } - if (params) - for (var key in params) { - if (typeof (params[key]) === 'object') { - formData.append(key, JSON.stringify(params[key])); - } else { - formData.append(key, params[key]); - } - } - axios.post(url, formData, - { headers: { "Content-Type": "multipart/form-data" } }) - .then( - (response) => { onResponse(response, onSuccess, onFail); } - ).catch(error => { - return handleError(error, onError); - }); + axios + .post(url, formData, { headers: { 'Content-Type': 'multipart/form-data' } }) + .then((response) => { + onResponse(response, onSuccess, onFail); + }) + .catch((error) => { + return handleError(error, onError); + }); }; export const fileDownload = ({ url, fileId, skey, params, method, onResponse, onError }) => { - if (!url) { - url = FILE_DOWN_GET + "/" + fileId + "/" + skey - } - if (method == 'post') { - axios.post(url, params, - { - responseType: 'blob', - headers: { - "Content-Type": "application/json" - } - } - ).then( - (response) => { - fileDownloadResponse(response, onResponse) - } - ).catch(error => { - return handleError(error, onError); - }); - - } else { - axios.get(url, - { - responseType: 'blob', - params: params - } - ).then( - (response) => { - fileDownloadResponse(response, onResponse) - } - ).catch(error => { - return handleError(error, onError); - }); - } + if (!url) { + url = FILE_DOWN_GET + '/' + fileId + '/' + skey; + } + if (method == 'post') { + axios + .post(url, params, { + responseType: 'blob', + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + fileDownloadResponse(response, onResponse); + }) + .catch((error) => { + return handleError(error, onError); + }); + } else { + axios + .get(url, { + responseType: 'blob', + params: params + }) + .then((response) => { + fileDownloadResponse(response, onResponse); + }) + .catch((error) => { + return handleError(error, onError); + }); + } }; const fileDownloadResponse = (response, onResponse) => { - const cd = response.headers?.['content-disposition']; - const fn = cd?.split('filename=')[1]?.replaceAll('"','')?.trim() || 'export.xlsx'; - - const url = URL.createObjectURL(response.data); - const a = document.createElement('a'); - a.href = url; - a.download = fn; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - onResponse?.(); + const cd = response.headers?.['content-disposition']; + const fn = cd?.split('filename=')[1]?.replaceAll('"', '')?.trim() || 'export.xlsx'; + + const url = URL.createObjectURL(response.data); + const a = document.createElement('a'); + a.href = url; + a.download = fn; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + onResponse?.(); }; export const fileUpload = ({ refType, refId, files, refCode, onSuccess, onFail, onError }) => { - postWithFiles({ - url: FILE_UP_POST, - params: { - refType: refType, - refId: refId, - refCode: refCode - }, - files: files, - onSuccess: onSuccess, - onFail: onFail, - onError: onError - }); + postWithFiles({ + url: FILE_UP_POST, + params: { + refType: refType, + refId: refId, + refCode: refCode + }, + files: files, + onSuccess: onSuccess, + onFail: onFail, + onError: onError + }); }; - const onResponse = (response, onSuccess, onFail) => { - if (response.status >= 300 || response.status < 200) { - console.log("onFail"); - if (onFail) { - onFail(response); - } else { - console.log(response); - } - return; + if (response.status >= 300 || response.status < 200) { + console.log('onFail'); + if (onFail) { + onFail(response); + } else { + console.log(response); } + return; + } - if (onSuccess) { - onSuccess(response?.data); - } -} + if (onSuccess) { + onSuccess(response?.data); + } +}; const handleError = (error, onError) => { - if (onError) { - return onError(error); - } else { - // console.log(error); - return false; - } -} \ No newline at end of file + if (onError) { + return onError(error); + } else { + // console.log(error); + return false; + } +}; diff --git a/src/utils/registerValidation.js b/src/utils/registerValidation.js index 403c1f88..54cb0772 100644 --- a/src/utils/registerValidation.js +++ b/src/utils/registerValidation.js @@ -1,18 +1,18 @@ export const ADDRESS_LINE_MAX_LENGTH = 40; export function isAddressLineWithinLimit(line, maxLen = ADDRESS_LINE_MAX_LENGTH) { - if (line == null || line === '') return true; - return line.length <= maxLen; + if (line == null || line === '') return true; + return line.length <= maxLen; } /** Step-0 gate: line1 required; lines 1–3 each <= maxLen */ export function isRegisterAddressValid(data, maxLen = ADDRESS_LINE_MAX_LENGTH) { - return ( - data.address1 !== '' && - isAddressLineWithinLimit(data.address1, maxLen) && - isAddressLineWithinLimit(data.address2, maxLen) && - isAddressLineWithinLimit(data.address3, maxLen) - ); + return ( + data.address1 !== '' && + isAddressLineWithinLimit(data.address1, maxLen) && + isAddressLineWithinLimit(data.address2, maxLen) && + isAddressLineWithinLimit(data.address3, maxLen) + ); } /** @@ -20,48 +20,51 @@ export function isRegisterAddressValid(data, maxLen = ADDRESS_LINE_MAX_LENGTH) { * Packs at comma boundaries where possible. */ export function splitAddressIntoLines(address, maxLen = ADDRESS_LINE_MAX_LENGTH) { - const empty = { address1: '', address2: '', address3: '' }; - if (address == null || address === '') return empty; + const empty = { address1: '', address2: '', address3: '' }; + if (address == null || address === '') return empty; - const trimmed = address.trim(); - if (trimmed.length <= maxLen) { - return { address1: trimmed, address2: '', address3: '' }; - } + const trimmed = address.trim(); + if (trimmed.length <= maxLen) { + return { address1: trimmed, address2: '', address3: '' }; + } - const segments = trimmed.split(',').map((s) => s.trim()).filter(Boolean); - const lines = []; - let current = ''; + const segments = trimmed + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + const lines = []; + let current = ''; - const pushCurrent = () => { - if (current) { - lines.push(current); - current = ''; - } - }; + const pushCurrent = () => { + if (current) { + lines.push(current); + current = ''; + } + }; - for (const segment of segments) { - const candidate = current ? `${current}, ${segment}` : segment; - if (candidate.length <= maxLen) { - current = candidate; - } else if (segment.length <= maxLen) { - pushCurrent(); - current = segment; - } else { - pushCurrent(); - let remaining = segment; - while (remaining.length > maxLen) { - lines.push(remaining.slice(0, maxLen)); - remaining = remaining.slice(maxLen); - } - current = remaining; - } - if (lines.length >= 3) break; + for (const segment of segments) { + const candidate = current ? `${current}, ${segment}` : segment; + if (candidate.length <= maxLen) { + current = candidate; + } else if (segment.length <= maxLen) { + pushCurrent(); + current = segment; + } else { + pushCurrent(); + let remaining = segment; + while (remaining.length > maxLen) { + lines.push(remaining.slice(0, maxLen)); + remaining = remaining.slice(maxLen); + } + current = remaining; } - pushCurrent(); + if (lines.length >= 3) break; + } + pushCurrent(); - return { - address1: lines[0] ?? '', - address2: lines[1] ?? '', - address3: lines[2] ?? '', - }; + return { + address1: lines[0] ?? '', + address2: lines[1] ?? '', + address3: lines[2] ?? '' + }; } diff --git a/src/utils/sanitizeHtml.js b/src/utils/sanitizeHtml.js index 97fc0f01..d812e92e 100644 --- a/src/utils/sanitizeHtml.js +++ b/src/utils/sanitizeHtml.js @@ -38,21 +38,7 @@ const ALLOWED_TAGS = [ 'ul' ]; -const ALLOWED_ATTR = [ - 'href', - 'title', - 'target', - 'rel', - 'src', - 'alt', - 'width', - 'height', - 'colspan', - 'rowspan', - 'scope', - 'class', - 'style' -]; +const ALLOWED_ATTR = ['href', 'title', 'target', 'rel', 'src', 'alt', 'width', 'height', 'colspan', 'rowspan', 'scope', 'class', 'style']; if (typeof window !== 'undefined') { DOMPurify.addHook('afterSanitizeAttributes', (node) => { From f459943a6b67fd242236b46c187ced96b114186f Mon Sep 17 00:00:00 2001 From: jason Date: Thu, 24 Sep 2026 14:56:49 +0800 Subject: [PATCH 4/5] update wcag2.2 --- src/assets/images/wcag2AA.png | Bin 11526 -> 7549 bytes src/components/cards/AuthFooter.js | 2 +- src/translations/en.json | 2 +- src/translations/zh-CN.json | 2 +- src/translations/zh-HK.json | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/images/wcag2AA.png b/src/assets/images/wcag2AA.png index b706d1f57d01a55c6b8becccf4c41bd3dd906e1b..5d1ead7247d383110a9ac35281ae694f65149750 100644 GIT binary patch literal 7549 zcmcgxWmFtZvtAb41r`tPSzz(t5PVrQ$l@WmLy#cBCCK7Q2yVd%5IpE&A-G#ekRZV! zxO?F8zTbEM-(R=SoIZ1&p6cqVsh+NSCK{=s1i_=h0{{RJ6=j4r0D$58a32Z+KJ?@I zSfc;{JwgQ`r|a`;*Tg36Q6H)EJeVRB8$?VRz%1+9xI{XC#w-WK!e_<;l7OK0p*t(Y zbQIqIw6WHc8pewj@6r};+lE>1Ph$q{DcN5GQ}+ZH^omQr=5sx=7(RKj7!h?CO$M^=>XQTf$ju%; zQ3O`_2W=Nphe;Ev!+r_n**~BgsYT$PzC5r(CL1~A0lob$ly@oiFgA(G{|7b8U?XRY zWFvRZKngveC(kBPE34zEe08#zhzB&tAp;pe_n^iBKJkE#@IByh|5xSlGs)|askqNV z2h-oJ{3)rwi{GcciXFadBEQpSbavX#!cUpW%;M3!H?jL!f=6~u{wa0WQHBL+zz)_?z95GsB2Jq{6VG)dab%=5@v%@I7U?drYW<1>HBmgvmz$l;@uPHU z)o)lZgC+T=3;d zI}Gt9O+7evlZbOQODGS_YHl8h4B9&sCo}I#Q!VO9P5EhO*&Rdjh|gm3u^>*0HE`*! z)8zX}#Ym>XRg!pTu>+ZyMCsbVt#l%gz~4*D1Xu7k$l*EEzVuIVKgQsh3gVv^h4ap;qtD%sNHixj(_bgegdtk5A1)yCRR$@s{pE<(pXndElPk z7J`F`My|icu2e`9A2S{K^QV)~3P1(adEM!L@Fr$gV!0?6fn)-cr&p%m)E8hA3J=pv z{neYe=4*KQrB02WlTu~%zY`@Qe@%~a`f+`ond%gFx+yHx=e>|!8pQ4%=J8_RAwl_-E2iiD$|0_B+3mpi z=#@cSsjI*GMjE-3s%D%i*Y6e-Ae6&S+9mroMYb%HP+cLS3u=Y7-9lbScVHl(V=i z11yNKR&Rmyhey8kuC_-S?;j#jnF(b%9jGiyq`N#U;H6o?a83RZ2+w9=z>PitY=Q<> zF0iM!o{R1bhp_T-f3~}b-5g0sItmUOyXrZk!`CJzN-*|n^_X*nPaqXx?VvSh;^;^m z)n%;p2(8t=Xx`@SuIX={@&IH@I>op;9l9sG@lGMBUz4eh$kP?;=r0q(L9ulxVREYX zOLE))PIJj+2Sj~+x2$81wfE9ynZcQfpyzRBAO2?d(C<2c*yHt(NA+xWU;NeuP=s5P z0;!mCgHvF=G^$+M?bx=KhA#^Kt6`~3|9oF>P(a_zeuYru1RebCCkzyX9I--%iU7Vp zVGwnMGzg}%1>}6B(uKA4le_p>#;xhrdSecgeBVV4;PVtA$Va1{0w$KQ)qNUtaJoG# zK%9J`F*S}_X?NBA3Yfs^#a#SuJjMzFm4|@hqa0u8u5Yd~(Z>h*bW&xg-!w-kl^Ksf}s`Lf(2Z}Z)A z+tjx;T1RMWD50}X-hQvk__odSd@%RzwpJ84_}RzLDZp-Yep<5$k!ZdQ^7P@5Lapey zlEkq1W*WEQgfdy`FuMn4pl&7(TLmy;HVNgGBg$|WZ;8`b!~vO;DYq!R+xT320q8u#!mvTBj){OE9YXq=jNKIrtgK9PD~((iqr`uUiQ zVw%s{^~k88s8W^m)T_J2_c{%pIbS>uzlRAnW;J;oe9i-+yPFvF*1tqVnLXbZ9q~3lcfKl>FAwd&r5FX_;rTIxsTQ^`ZW_jHW zO+2VcF3YCOeDpXSAMDBCHt!+2Mr{x0cfYvH{7Gw9gW@T+6s1WCfrLXS>X)&m2^j0r z8Qc$RET}b2TT6wVASNo;Rd%%6yVCw;M_UlLO!Z##N}B`z`zqnS?Dha>vCf{kxSP?r z8Mk^A)RQoo?XW;qF|*9 zlej!4Q&3J7kT&@6y(AQ=pU7&``J^72rb8AJGR^IsXdb)x=D4#u;;LnIk~*Og(h#IU zjdqpsrlx1ZZ|~Vt8gsmp6H_*4Y{>a?wEn8)ofz)A4JcXzN2#72&O z#zVo1FaciR#wQJzFR$JHN^p!qaL3BWEUaK2^t^k2e59)o5x`gk5MVqpcGv@61`6k) zAoJvixz&S(O^9QOloOA4M7>fa%@nfwz;d5}3%ifbSV;CcmH9%x5S0weT9gIk#4Fps z{Y*&GX4+CD`*QBPdz0pJ|k=%Rh&0`^Bp{09WHUkZ*I$-?YeHm14QjK3$LjT=3Vjq|8C@fc7`yE zIq>meX|ShjL(B)67^%4PjPBC#ZrqWlY(ZuO*t2V&>>ew)_vA(0A~=|omwPLauE8o= zY-XH3Z;*oENIsMJiKG;OEyhZ*Z@ukHc#b%+7rHkt9mkAu@`;^AQ zG@uNV(Zbl)%8w-1URD6nuao%6A`U#7FJP`8;&;mmQoGJMm|Qvoy>73`aO5@}=9S2sx%VI?8Gg|fjZv{Fs=%_Q!z zJk#C9inUl-n5vRKzUz&goS+C&CCd_bYtd@RH0dax8a;7#d;PDAm)zp)3a3 zp~+CC%&Aq;F0R+>`(%>u&3;9fUG!B*#FtG2Rl9fIo4)JUFSL&zD3n!JFx`FeePv9Q zl9!w@)cox%Mnz8Osbou#nX%{^zC7KF7yu_(+rgXhMhuj<6lE_@|_dYJP60>h{Hdhi07EYHbejIU&4?!*)MI# zZk3agn{6H=3yj^CH!y3z*In~4Y`uX0dM{`a=07_B=&0@8I%FUD%s=!n zutpc!K-b>2oL0>xgYnpy{ZYl|SS9y)Sw7vjB#%Br7l6AM793Dne|E!G5BQZ;kHx;? z;O&U9d`ylqI<;4S?V^a)pU<5Ub=}4E&2OuJiKiICzXZ;YQ)7>lC0B}=b=COof1E$D zb>^0#@lx!i6q;iNj2-!g4d+GdQ&=gG>1sw_!p+_Yo#8ERx0kL+_htK~SO=e#!Us6S zNv~MVZy*_&kVW7&F3;!$s(OoLQvO66y(j_0`{2q~KR~NVZTULA*Et|-dmLiXc; zE3SAc8_v$yd|Um;2UvycO|2JMer*8SY5RVIlm~=^gOMezouEQSwP3g3kG`~!?#grH zXub4Z!lbQt9km8^C*YMMf-H3oIbVH*9n7mjXg*f$*vPXJa~*j?Mf1wJ-zzK*kdR~y zB}QKyM~=*JCJiqJ91`FXPx^4a2)`XB#*_wC!rt$%sP|QM^~sIV5GMJ*sg$zURUG2-2+Tp}51aYXFC{?Xs|cXlaYLcO;hW zQhg;lbZ*vZ&MD|Dlg`KkFD3jdh84jm#eFbV zIom4)n*4X-j44w}G|6Hbf(6-8aD3p063Bf(h?L4|IoTrTdSlLt^%J5?|Mul*afwlP zZvo4hh(IQ1nmemCse(@Y0G52JCz5Ot?0EOpD2bUi;*bY7gxKPN6tooq;R|X%F-sQd zntfKyWYHnOXGIuS*WCGbba_dm(!G^Zyc3;FG5|`^%3Lf{yZy)e-=F_u#Rxeuo*N8w za*A}A5CY(dc0tgrTD=_<5S^<~^J;f^hzO&_<9KPFp9TRcbCZ-+CRPt+HolOKJnZnb ztG7Xz)(%9J328|KU=x@J;G#YzS1$T@dgeNbd8vc*koen8=0njtI2fWK#Eu@DjQ?s( z(reS4v|zhnJ)jAGZ8V78Pby^dq`-&8(*ljd^rN%FCU*QS9G=HYs77Nb3vE`K%6EMG zzjm(t3T8HvdHs#b$z&z(2}m`Oz;A%{U41@~+Snf@vm}R|Ku4j$5i0FrfCw zc#O1QgG|yM%ZaKk;hN|7pDb+HAJR+;*P^#b*QlrkYtjXCpOaYBb_vjd_~X~RV2#lE z-{Xtq^KjfKBU;rEkkFRA={eX2cMS{gAw#6FFRJliQa=pKOA)>U_n_=K1PkwthRq|o zfHF#02FMr{DRh&8S2Ncz4d#}G}D;e5wLbFMXJl@ z#^NdF`4?ka0$;d7^5IC{EUnA`@8fQ)mlK5y=qD8hP&tCMvhw1@Z-mSmr-T6UXvtNQ zRCHQsLUX@?0D|!$Iq$3+iO3Qa`u(Jw znW0LH!&tHsV?D^QgOwbeBaizZlinhs;GjBl8aOn9JPK>_TSeH*8CV61YJ4+L63a7D zB)cvF*8dj)voW?RYC-Ua%Ew%g;bmy>FrH(V@)qDZjcETF?8jmDMrcj$^*|Jekk*%a-6=&L0iu65w z_0AkFml<0LsV!(KE}>+6aoHX#QP--MvQ-7+9wzz+N6suPILSNdY%5nq zO}AFF%Cjz$v(i0p^@eYT(gIau<*t|H*6_U;SZ}hY!TV=5Q$#^hQMOT3weo9$jQ5YC zgYm)}+=rLpzvA`J`Xf09~JTX=(cbxt3*4-O3_* z4HI$PAKHyMDy7f;)=V=KGKtIH1K@IT5^f!Se!9#fqV&|i^jTGi6AgP^A59a#@P6@Q zLrVP^jTfWv%%3?_vn>(WnuRnzQ$ ze=+z@k9h!8<5!7zzWA7);p6XAM#YnFs|E*;FW(t4cfV3bb^^B&lp6s(s8Ht<->Jf3 zLd%{gRlmSr4Wz!>Pk>kA*dxuIS9x`{jAWRsX9JHLUd7ul4r1AxNtAhO2RD-8ofmR- z#5ry`T094wXIqo*T4hCu#hC1p?J=7_bZXCLAs4yce(=v)D~~t8ulMj1U%ToswH-TA?MW?`8EQ%zr|s#xpz} z-P?}A5j*Q2Ll~rpg2t5AnEz%wXxbcDV>|>dmK(yq=%Gf!Av)eZ|1oaSSB?4O{j&+) znOW(`%Ve+KT=n-8kNT(TgeXH!HS{%MKAHleOiP{4gULMdEE~~|Tv@L~d|+v`<04ec z3{T4+7?_Rd)svU;7K47!A`OaE-~2bUo0YSef?o?yaC05cvxo-=91)E!#Swy#Ywa-h z&W=)q&D0up%3&@uz}fTd3|Y4J5yv(d+hjM5eZ1tO1nD$HPg=o;LQN?yR6M~=X~nWf z@{5D9jo!q3oK!&*+G1I)TQYE69H7p6!=rrP*nuicFmg2)V{HC+ERS0u|&UP8=e69E)eTaiowD9W__nm*D|}r zNCYz|TigWBT4@;2hd8ntFEC5RnZ+CtzNrLZe<5|*p<|`LmBmvOb3Hwl^081@HuXu; z`a{tu^4d`JBhUbfDw_zV9%ivD!wb{TUy(5uG=;T|ojH(hM8M)7$Gd#pu9&ggP#&-N z`7UXgfS)&2?3>##+0PgWw-Fp7Scl}%1AmeCopw7Tu(>D4q*f9RrpmoN%jhz&eI*kM z65^bOS2pkcfTTXx028Myx?Quu|6GEoj-jc$ZBd+FhfO}tykm=he)1S=lh1v|_EgiD zEdN6mZ}Bp}>4EkQWy8M53gJcz+*Zye`05B~?tawfIn4*SMPQ>easL4wFS$%w^o~<~PSn8U9HW z0vq2yt@8-lhz17jW&C_#(vj*gsZ5*+o$EGF^+L_hL>=AHkIod8U61sToTW4l^98WJxEpN$C6W5ECrDO-$_sXR9AMSn|gl!OZ-{5^-sfA@h z#h8SGAr$Z@f?Y|%wW}gq~-!}x8+-n}_G-b|JR_x?@wp9D*!BzG~w$m#Gy~qcbnhXiD$X%l?ip@1otdGSbSPI1^_iACUwE zb!V!*JpUlZ#ZCfBddaoD_C-X_Q}XFGUJ*E~enCd46Fu}4p^Btzl& zncUH>#>$n}SP|;$NESCcIVcH_Yg?=sZ2HvIXw7?U86PL3zmD)}c9w~Pt;gP(O9Sr0 zcD-;Zk%<(U`M(R*>f6gCeA(L`73$p>4IM|Y3}3A&4QJ@4)ZmR+{s`OFj5(l4 zuhw64ZTUaX`{(U7rE$dmRBZ@`nd)uq3*QSfEJQ1bOh~h%CR}~zC)*QB7u2WZloPq~ zy1U8lB5O=XT~)ni{*^QYVpmi~cK{`0CbPt-x3tSDSKyPbgG@%wQ21DE&@P>KLi4XL zJ#}u>N`q>(UIb6wpz)ARAFD~ez;p`z|N7I9wp6WiQbG{f`@~5&QJ_5YP&A(k(SzMh zb$8vhbK@UjJ5gS~(g~$JxfrRIuf>Yl6ad7O@Q=~MYNV>3*q!Kc4BTmLaahJ z$1XbiMks2HjZNG@AmIopEPDtCM&>UeDcIhB%F@^`tDKfl4W1z%WUp>ao-+o z)JYB;gf}>A(OL5TQVDLj{|oDaI&*u}CFZVGeGFzoau${7jMBzd7JI>XOmH)7QD Q@LLt2qM(7Ogqw%{54^Mj>i_@% literal 11526 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 diff --git a/src/components/cards/AuthFooter.js b/src/components/cards/AuthFooter.js index 398e99fa..e4fbd89b 100644 --- a/src/components/cards/AuthFooter.js +++ b/src/components/cards/AuthFooter.js @@ -58,7 +58,7 @@ const AuthFooter = () => { {!isGLDLoggedIn()? ( Date: Thu, 24 Sep 2026 14:57:06 +0800 Subject: [PATCH 5/5] update payment message --- src/pages/Payment/MultiPaymentWindow.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/Payment/MultiPaymentWindow.js b/src/pages/Payment/MultiPaymentWindow.js index 485ea34c..e0c25d91 100644 --- a/src/pages/Payment/MultiPaymentWindow.js +++ b/src/pages/Payment/MultiPaymentWindow.js @@ -408,11 +408,11 @@ const MultiPaymentWindow = (props) => { : null} : - - - - - + + + + + }