feat(portal-shell): enable Apollo Client APQ + PQ manifest generator

Task 12-13 of portal-shell data abstraction plan (M3 security).

APQ (Automatic Persisted Queries):
- Add createPersistedQueryLink with sha256 to apollo-client.ts
- Production: client sends only query hash, not plaintext query
- Dev: NEXT_PUBLIC_APOLLO_APQ=false to disable for debugging
- Prevents attackers from crafting arbitrary queries via DevTools

PQ Manifest generator:
- New scripts/generate-pq-manifest.ts iterates operations barrel
- Outputs public/pq-manifest.json (sha256 -> query text whitelist)
- prebuild hook: codegen + generate-pq-manifest before next build
- 51 queries currently registered

- crypto-hash dependency added
- typecheck + lint (0 errors) + test (85/85) verified
This commit is contained in:
SpecialX
2026-07-17 13:31:11 +08:00
parent 1b5781bf42
commit b30d43f983
6 changed files with 178 additions and 5 deletions

13
pnpm-lock.yaml generated
View File

@@ -317,6 +317,9 @@ importers:
'@edu/ui-tokens':
specifier: workspace:*
version: link:../../packages/ui-tokens
crypto-hash:
specifier: ^4.0.1
version: 4.0.1
graphql:
specifier: ^16.8.0
version: 16.14.2
@@ -371,10 +374,10 @@ importers:
version: 10.5.2(postcss@8.5.18)
eslint:
specifier: ^9.0.0
version: 9.39.5(jiti@2.7.0)
version: 9.39.5(jiti@1.21.7)
eslint-config-prettier:
specifier: ^9.1.0
version: 9.1.2(eslint@9.39.5(jiti@2.7.0))
version: 9.1.2(eslint@9.39.5(jiti@1.21.7))
jsdom:
specifier: ^25.0.0
version: 25.0.0
@@ -6770,6 +6773,10 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
crypto-hash@4.0.1:
resolution: {integrity: sha512-4u4yl+8C+75Xedf1uMA0J/TTYAqJ54XdyBc1RV3UjCkU2wQjFMsB5ScjRDh3oo21zAwSprcVEDolaGDT8z5G6g==}
engines: {node: '>=20'}
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
@@ -17218,6 +17225,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.1
crypto-hash@4.0.1: {}
css.escape@1.5.1: {}
cssesc@3.0.0: {}