devendra-skill-package 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.github/skills/boiler-project-generator/SKILL.md +103 -0
  2. package/.github/skills/boiler-project-generator/assets/templates/android/README.md +3 -0
  3. package/.github/skills/boiler-project-generator/assets/templates/angular/README.md +3 -0
  4. package/.github/skills/boiler-project-generator/assets/templates/flutter/README.md +3 -0
  5. package/.github/skills/boiler-project-generator/assets/templates/ios/README.md +3 -0
  6. package/.github/skills/boiler-project-generator/assets/templates/react/README.md +3 -0
  7. package/.github/skills/boiler-project-generator/assets/templates/react-native/README.md +3 -0
  8. package/.github/skills/boiler-project-generator/assets/templates/react-native/api-setup.ts +76 -0
  9. package/.github/skills/boiler-project-generator/assets/templates/react-native/component-primary-button.tsx +29 -0
  10. package/.github/skills/boiler-project-generator/assets/templates/react-native/env-config.env +4 -0
  11. package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-auth-screen.tsx +45 -0
  12. package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-dashboard-screen.tsx +45 -0
  13. package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-profile-screen.tsx +45 -0
  14. package/.github/skills/boiler-project-generator/assets/templates/react-native/hook-use-app-boot.ts +11 -0
  15. package/.github/skills/boiler-project-generator/assets/templates/react-native/navigation-setup.tsx +25 -0
  16. package/.github/skills/boiler-project-generator/assets/templates/react-native/project-structure.md +24 -0
  17. package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-hooks.ts +5 -0
  18. package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-session-slice.ts +29 -0
  19. package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-store.ts +15 -0
  20. package/.github/skills/boiler-project-generator/assets/templates/react-native/theme-index.ts +23 -0
  21. package/.github/skills/boiler-project-generator/assets/templates/react-native/utils-helpers.ts +7 -0
  22. package/.github/skills/boiler-project-generator/metadata.yaml +29 -0
  23. package/.github/skills/boiler-project-generator/references/android.md +19 -0
  24. package/.github/skills/boiler-project-generator/references/angular.md +14 -0
  25. package/.github/skills/boiler-project-generator/references/flutter.md +17 -0
  26. package/.github/skills/boiler-project-generator/references/ios.md +17 -0
  27. package/.github/skills/boiler-project-generator/references/react-native.md +103 -0
  28. package/.github/skills/boiler-project-generator/references/react.md +17 -0
  29. package/.github/skills/boiler-project-generator/scripts/angular.js +43 -0
  30. package/.github/skills/boiler-project-generator/scripts/generate-project.mjs +107 -0
  31. package/.github/skills/boiler-project-generator/scripts/init.js +105 -0
  32. package/.github/skills/boiler-project-generator/scripts/react-native.js +1041 -0
  33. package/.github/skills/boiler-project-generator/scripts/react.js +32 -0
  34. package/.github/skills/clean-architecture-generator/SKILL.md +235 -0
  35. package/.github/skills/clean-architecture-generator/references/architecture-core.md +126 -0
  36. package/.github/skills/clean-architecture-generator/references/platform-angular.md +374 -0
  37. package/.github/skills/clean-architecture-generator/references/platform-flutter.md +366 -0
  38. package/.github/skills/clean-architecture-generator/references/platform-react-native.md +590 -0
  39. package/.github/skills/clean-architecture-generator/references/platform-react.md +261 -0
  40. package/.github/skills/code-test-coverage-analyzer/SKILL.md +1358 -0
  41. package/.github/skills/code-test-coverage-analyzer/assets/config-questionnaire.md +118 -0
  42. package/.github/skills/code-test-coverage-analyzer/assets/conflict-resolution-protocol.md +99 -0
  43. package/.github/skills/code-test-coverage-analyzer/assets/coverage-calculation-rules.md +294 -0
  44. package/.github/skills/code-test-coverage-analyzer/assets/multi-strategy-search.md +145 -0
  45. package/.github/skills/code-test-coverage-analyzer/assets/output-file-contracts.md +651 -0
  46. package/.github/skills/code-test-coverage-analyzer/assets/status-definitions.md +273 -0
  47. package/.github/skills/code-test-coverage-analyzer/examples/example-invocation.md +208 -0
  48. package/.github/skills/code-test-coverage-analyzer/references/android-patterns.md +229 -0
  49. package/.github/skills/code-test-coverage-analyzer/references/angular-patterns.md +177 -0
  50. package/.github/skills/code-test-coverage-analyzer/references/backend-patterns.md +383 -0
  51. package/.github/skills/code-test-coverage-analyzer/references/flutter-patterns.md +254 -0
  52. package/.github/skills/code-test-coverage-analyzer/references/ios-patterns.md +228 -0
  53. package/.github/skills/code-test-coverage-analyzer/references/react-native-patterns.md +431 -0
  54. package/.github/skills/code-test-coverage-analyzer/references/react-patterns.md +206 -0
  55. package/.github/skills/code-test-coverage-analyzer/scripts/detect-platform.ps1 +261 -0
  56. package/.github/skills/code-test-coverage-analyzer/scripts/scan-repository.ps1 +242 -0
  57. package/.github/skills/code-test-coverage-analyzer/templates/brd-compliance-row.md +107 -0
  58. package/.github/skills/code-test-coverage-analyzer/templates/test-case-row.md +130 -0
  59. package/.github/skills/code-test-coverage-analyzer/templates/user-story-compliance-row.md +170 -0
  60. package/.github/skills/custom-font-integrator/SKILL.md +373 -0
  61. package/.github/skills/custom-font-integrator/assets/FontTestScreen.js +150 -0
  62. package/.github/skills/custom-font-integrator/references/example-info-plist.xml +21 -0
  63. package/.github/skills/custom-font-integrator/scripts/setup-fonts.sh +99 -0
  64. package/.github/skills/encryption-decryption-utility/Instructions.md +282 -0
  65. package/.github/skills/encryption-decryption-utility/README.md +222 -0
  66. package/.github/skills/encryption-decryption-utility/SKILL.md +645 -0
  67. package/.github/skills/encryption-decryption-utility/assets/encryption-config-template.json +13 -0
  68. package/.github/skills/encryption-decryption-utility/assets/platform-matrix.json +180 -0
  69. package/.github/skills/encryption-decryption-utility/metadata.yaml +51 -0
  70. package/.github/skills/encryption-decryption-utility/references/android-crypto.md +157 -0
  71. package/.github/skills/encryption-decryption-utility/references/angular-crypto.md +130 -0
  72. package/.github/skills/encryption-decryption-utility/references/approach-1-webcrypto.md +95 -0
  73. package/.github/skills/encryption-decryption-utility/references/approach-2-rsa-aes-cbc.md +111 -0
  74. package/.github/skills/encryption-decryption-utility/references/approach-3-aes-sharedkey.md +114 -0
  75. package/.github/skills/encryption-decryption-utility/references/architecture.md +233 -0
  76. package/.github/skills/encryption-decryption-utility/references/backend-alignment.md +506 -0
  77. package/.github/skills/encryption-decryption-utility/references/dependency-guidance.md +234 -0
  78. package/.github/skills/encryption-decryption-utility/references/ec-implementation.md +832 -0
  79. package/.github/skills/encryption-decryption-utility/references/error-handling-playbook.md +234 -0
  80. package/.github/skills/encryption-decryption-utility/references/flutter-crypto.md +105 -0
  81. package/.github/skills/encryption-decryption-utility/references/ios-crypto.md +338 -0
  82. package/.github/skills/encryption-decryption-utility/references/native-rsa-implementation.md +373 -0
  83. package/.github/skills/encryption-decryption-utility/references/node-crypto.md +86 -0
  84. package/.github/skills/encryption-decryption-utility/references/react-crypto.md +105 -0
  85. package/.github/skills/encryption-decryption-utility/references/react-native-crypto.md +1001 -0
  86. package/.github/skills/encryption-decryption-utility/references/react-web-crypto.md +668 -0
  87. package/.github/skills/encryption-decryption-utility/references/security-checklist.md +176 -0
  88. package/.github/skills/encryption-decryption-utility/references/web-crypto.md +74 -0
  89. package/.github/skills/encryption-decryption-utility/scripts/detect.js +128 -0
  90. package/.github/skills/encryption-decryption-utility/scripts/install.js +1713 -0
  91. package/.github/skills/encryption-decryption-utility/scripts/setup.js +800 -0
  92. package/.github/skills/encryption-decryption-utility/template.md +241 -0
  93. package/.github/skills/encryption-decryption-utility/templates/config-questionnaire.md +467 -0
  94. package/.github/skills/encryption-decryption-utility/templates/delivery-checklist.md +206 -0
  95. package/.github/skills/encryption-decryption-utility/templates/implementation-plan.md +304 -0
  96. package/.github/skills/feature-generator/SKILL.MD +2741 -0
  97. package/.github/skills/feature-generator/assets/fingerprint-schema.json +385 -0
  98. package/.github/skills/feature-generator/assets/spec-template.md +172 -0
  99. package/.github/skills/feature-generator/assets/transform-rules.json +82 -0
  100. package/.github/skills/feature-generator/references/api-envelope-patterns.md +327 -0
  101. package/.github/skills/feature-generator/references/screen-logic-patterns.md +399 -0
  102. package/.github/skills/feature-generator/references/state-library-patterns.md +464 -0
  103. package/.github/skills/figma-ui-mapper/README.md +34 -0
  104. package/.github/skills/figma-ui-mapper/SKILL.md +101 -0
  105. package/.github/skills/figma-ui-mapper/assets/templates/component-map-template.md +30 -0
  106. package/.github/skills/figma-ui-mapper/assets/templates/generation-report-template.md +49 -0
  107. package/.github/skills/figma-ui-mapper/metadata.yaml +22 -0
  108. package/.github/skills/figma-ui-mapper/references/android.md +11 -0
  109. package/.github/skills/figma-ui-mapper/references/angular.md +18 -0
  110. package/.github/skills/figma-ui-mapper/references/capacitor.md +18 -0
  111. package/.github/skills/figma-ui-mapper/references/common.md +23 -0
  112. package/.github/skills/figma-ui-mapper/references/expo.md +18 -0
  113. package/.github/skills/figma-ui-mapper/references/flutter.md +18 -0
  114. package/.github/skills/figma-ui-mapper/references/frameworks/angular/rules.md +23 -0
  115. package/.github/skills/figma-ui-mapper/references/frameworks/capacitor/rules.md +23 -0
  116. package/.github/skills/figma-ui-mapper/references/frameworks/expo/rules.md +23 -0
  117. package/.github/skills/figma-ui-mapper/references/frameworks/flutter/rules.md +23 -0
  118. package/.github/skills/figma-ui-mapper/references/frameworks/ionic/rules.md +23 -0
  119. package/.github/skills/figma-ui-mapper/references/frameworks/kotlin-multiplatform/rules.md +23 -0
  120. package/.github/skills/figma-ui-mapper/references/frameworks/native-android/rules.md +23 -0
  121. package/.github/skills/figma-ui-mapper/references/frameworks/native-ios/rules.md +23 -0
  122. package/.github/skills/figma-ui-mapper/references/frameworks/next/rules.md +23 -0
  123. package/.github/skills/figma-ui-mapper/references/frameworks/nuxt/rules.md +23 -0
  124. package/.github/skills/figma-ui-mapper/references/frameworks/react/rules.md +19 -0
  125. package/.github/skills/figma-ui-mapper/references/frameworks/react-native/rules.md +23 -0
  126. package/.github/skills/figma-ui-mapper/references/frameworks/svelte/rules.md +23 -0
  127. package/.github/skills/figma-ui-mapper/references/frameworks/vue/rules.md +23 -0
  128. package/.github/skills/figma-ui-mapper/references/ionic.md +18 -0
  129. package/.github/skills/figma-ui-mapper/references/ios.md +11 -0
  130. package/.github/skills/figma-ui-mapper/references/kotlin-multiplatform.md +18 -0
  131. package/.github/skills/figma-ui-mapper/references/native-android.md +18 -0
  132. package/.github/skills/figma-ui-mapper/references/native-ios.md +18 -0
  133. package/.github/skills/figma-ui-mapper/references/next.md +18 -0
  134. package/.github/skills/figma-ui-mapper/references/nuxt.md +18 -0
  135. package/.github/skills/figma-ui-mapper/references/react-figma-guideline.md +21 -0
  136. package/.github/skills/figma-ui-mapper/references/react-native.md +18 -0
  137. package/.github/skills/figma-ui-mapper/references/react.md +18 -0
  138. package/.github/skills/figma-ui-mapper/references/script-usage.md +46 -0
  139. package/.github/skills/figma-ui-mapper/references/svelte.md +18 -0
  140. package/.github/skills/figma-ui-mapper/references/vue.md +18 -0
  141. package/.github/skills/figma-ui-mapper/references/workflow.md +74 -0
  142. package/.github/skills/figma-ui-mapper/scripts/generate-page-through-figma.js +635 -0
  143. package/.github/skills/figma-ui-mapper/scripts/generate-react-page-through-figma.js +4 -0
  144. package/.github/skills/frontend-task-breakdown/SKILL.md +734 -0
  145. package/.github/skills/md-file-converter/SKILL.md +291 -0
  146. package/.github/skills/md-file-converter/config/formats.json +114 -0
  147. package/.github/skills/md-file-converter/extractors/extract-docx.sh +120 -0
  148. package/.github/skills/md-file-converter/extractors/extract-eml.py +108 -0
  149. package/.github/skills/md-file-converter/extractors/extract-pdf.sh +97 -0
  150. package/.github/skills/md-file-converter/extractors/extract-pptx.py +143 -0
  151. package/.github/skills/md-file-converter/extractors/extract-xlsx.py +175 -0
  152. package/.github/skills/md-file-converter/scripts/check-tools.sh +47 -0
  153. package/.github/skills/md-file-converter/scripts/extract-archive.sh +83 -0
  154. package/.github/skills/md-file-converter/scripts/extract-ocr.sh +77 -0
  155. package/.github/skills/md-file-converter/scripts/get-metadata.sh +57 -0
  156. package/.github/skills/md-file-converter/templates/output.md +73 -0
  157. package/.github/skills/md-file-converter/templates/unsupported-object.md +6 -0
  158. package/.github/skills/mockAPI-contract-generator/SKILL.md +607 -0
  159. package/.github/skills/test-case-validator/CHANGELOG.md +155 -0
  160. package/.github/skills/test-case-validator/CONFIGURATION.md +130 -0
  161. package/.github/skills/test-case-validator/EXAMPLES.md +50 -0
  162. package/.github/skills/test-case-validator/QUALITY_SCORING.md +98 -0
  163. package/.github/skills/test-case-validator/README.md +111 -0
  164. package/.github/skills/test-case-validator/REPORT_TEMPLATE.md +116 -0
  165. package/.github/skills/test-case-validator/SKILL.md +209 -0
  166. package/.github/skills/test-case-validator/SYSTEM_PROMPT.md +127 -0
  167. package/.github/skills/test-case-validator/TRACEABILITY_ENGINE.md +152 -0
  168. package/.github/skills/test-case-validator/VALIDATION_RULES.md +411 -0
  169. package/.github/skills/test-case-validator/config/default.config.yaml +62 -0
  170. package/.github/skills/test-case-validator/docs/ARCHITECTURE.md +66 -0
  171. package/.github/skills/test-case-validator/docs/EXTENDING.md +85 -0
  172. package/.github/skills/test-case-validator/docs/FAQ.md +88 -0
  173. package/.github/skills/test-case-validator/docs/VERSIONING.md +55 -0
  174. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-brd.md +43 -0
  175. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-test-cases.csv +9 -0
  176. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-user-story.md +29 -0
  177. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/output-report.md +303 -0
  178. package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/input-spec.md +43 -0
  179. package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/input-test-files-index.md +56 -0
  180. package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/output-report.md +274 -0
  181. package/.github/skills/test-case-validator/prompts/00-applicability-resolution.md +61 -0
  182. package/.github/skills/test-case-validator/prompts/01-extraction-user-story.md +58 -0
  183. package/.github/skills/test-case-validator/prompts/02-extraction-brd.md +64 -0
  184. package/.github/skills/test-case-validator/prompts/03-extraction-test-cases.md +92 -0
  185. package/.github/skills/test-case-validator/prompts/04-traceability-matrix.md +41 -0
  186. package/.github/skills/test-case-validator/prompts/05-coverage-analysis.md +46 -0
  187. package/.github/skills/test-case-validator/prompts/06-missing-scenario-detection.md +32 -0
  188. package/.github/skills/test-case-validator/prompts/07-duplicate-detection.md +36 -0
  189. package/.github/skills/test-case-validator/prompts/08-risk-analysis.md +39 -0
  190. package/.github/skills/test-case-validator/prompts/09-gated-wiring-validation.md +53 -0
  191. package/.github/skills/test-case-validator/prompts/10-observation-report.md +56 -0
  192. package/.github/skills/test-case-validator/prompts/11-final-report-assembly.md +57 -0
  193. package/.github/skills/test-case-validator/templates/coverage-table.md +25 -0
  194. package/.github/skills/test-case-validator/templates/executive-summary.md +38 -0
  195. package/.github/skills/test-case-validator/templates/final-summary-block.md +87 -0
  196. package/.github/skills/test-case-validator/templates/observation-entry.md +41 -0
  197. package/.github/skills/test-case-validator/templates/rtm-row.md +25 -0
  198. package/.github/skills/validation-engine-builder/README.md +50 -0
  199. package/.github/skills/validation-engine-builder/SKILL.md +241 -0
  200. package/.github/skills/validation-engine-builder/references/core.md +158 -0
  201. package/.github/skills/validation-engine-builder/references/platform-angular.md +166 -0
  202. package/.github/skills/validation-engine-builder/references/platform-flutter.md +118 -0
  203. package/.github/skills/validation-engine-builder/references/platform-react-native.md +170 -0
  204. package/.github/skills/validation-engine-builder/references/platform-react.md +94 -0
  205. package/.github/skills/validation-engine-builder/scripts/apply-to-project.js +80 -0
  206. package/.github/skills/validation-engine-builder/scripts/generate-angular.js +271 -0
  207. package/.github/skills/validation-engine-builder/scripts/generate-flutter.js +298 -0
  208. package/.github/skills/validation-engine-builder/scripts/generate-react-native.js +217 -0
  209. package/.github/skills/validation-engine-builder/scripts/generate-react.js +163 -0
  210. package/.github/skills/validation-engine-builder/scripts/install-deps.js +53 -0
  211. package/.github/skills/validation-engine-builder/scripts/shared/detect-platform.js +68 -0
  212. package/.github/skills/validation-engine-builder/scripts/shared/npm-deps.js +103 -0
  213. package/.github/skills/validation-engine-builder/scripts/shared/rhf-hook.js +43 -0
  214. package/.github/skills/validation-engine-builder/scripts/shared/ts-messages-regex.js +62 -0
  215. package/.github/skills/validation-engine-builder/scripts/shared/util.js +44 -0
  216. package/.github/skills/validation-engine-builder/scripts/shared/yup-schemas.js +80 -0
  217. package/.github/skills/validation-engine-builder/template.md +73 -0
  218. package/.github/skills/validation-engine-builder/templates/messages.json +22 -0
  219. package/.github/skills/validation-engine-builder/templates/regex.json +16 -0
  220. package/.github/skills/validation-engine-builder/templates/schemas.json +48 -0
  221. package/bin/index.js +145 -0
  222. package/package.json +22 -0
  223. package/readme.md +0 -0
@@ -0,0 +1,668 @@
1
+ # Platform: React JS (Web) — Production Crypto (Approach 7)
2
+
3
+ ## Overview
4
+
5
+ React JS web projects on approach 7 use the **built-in Web Crypto API** (`window.crypto.subtle`) for all cryptographic operations.
6
+ No external crypto packages are required. All crypto is native to the browser.
7
+
8
+ Supports two modes:
9
+ - **RSA-OAEP** (default) — per-request AES-256-GCM key wrapped with the server's RSA public key. Full request/response encryption. Per-request key isolation.
10
+ - **ECDH-Session** — ECDH P-256 + HKDF-SHA256 session key derivation + AES-256-GCM with AAD. No RSA. Session key derived once per session. Use when integrating with ECDH-Session / HDFC-LI partner APIs.
11
+
12
+ > **Approach 7 vs Approach 1 for React:**
13
+ > Approach 1 is a basic `crypto.subtle` encrypt-only interceptor with no response decryption, no RSA key management, and no session handling.
14
+ > Approach 7 is the full production-grade React JS equivalent of React Native approach 4 — it adds per-request key isolation, response decryption, RSA key service (fetch/cache/retry), in-memory request mapping with guaranteed cleanup, browser-safe storage, and ECDH-Session support.
15
+
16
+ ---
17
+
18
+ ## Prerequisite: Web Crypto API Availability
19
+
20
+ `crypto.subtle` is only available on **secure origins** (HTTPS or `localhost`).
21
+ It is `undefined` on plain HTTP. Verify at startup:
22
+
23
+ ```typescript
24
+ if (typeof window.crypto?.subtle === 'undefined') {
25
+ throw new Error('crypto.subtle unavailable — app must be served over HTTPS or localhost');
26
+ }
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Framework Detection (Env Var Prefix)
32
+
33
+ Detect from `package.json` before implementation:
34
+
35
+ | Framework | Env prefix | Detection |
36
+ |---|---|---|
37
+ | Vite (React) | `VITE_` | `vite` in devDependencies |
38
+ | Create React App | `REACT_APP_` | `react-scripts` in dependencies |
39
+ | Next.js | `NEXT_PUBLIC_` | `next` in dependencies |
40
+
41
+ ---
42
+
43
+ ## File Naming Convention (Approach 7)
44
+
45
+ | Role | Path |
46
+ |---|---|
47
+ | Crypto utilities | `src/services/security/crypto-utils.ts` |
48
+ | AES service | `src/services/security/aes.service.ts` |
49
+ | RSA service | `src/services/security/rsa.service.ts` |
50
+ | RSA key management | `src/services/security/rsa-key.service.ts` |
51
+ | HMAC service | `src/services/security/hmac.service.ts` |
52
+ | Browser storage | `src/services/security/storage.service.ts` |
53
+ | Interceptor | `src/services/security/encryption.interceptor.ts` |
54
+ | Config / feature flags | `src/services/security/encryption-config.ts` |
55
+ | TypeScript types | `src/services/security/types.ts` |
56
+ | ECDH Base64URL util | `src/core/security/ecdh-session/base64url.ts` |
57
+ | ECDH AAD builder | `src/core/security/ecdh-session/aad.ts` |
58
+ | ECDH key pair + derivation | `src/core/security/ecdh-session/ecdhSession.ts` |
59
+ | AES-GCM with AAD | `src/core/security/ecdh-session/aesGcm.ts` |
60
+ | ECDH session service | `src/core/security/ecdh-session/ecdhSessionService.ts` |
61
+
62
+ ---
63
+
64
+ ## Mode A: RSA-OAEP
65
+
66
+ ### Crypto Utilities (`crypto-utils.ts`)
67
+
68
+ ```typescript
69
+ // No Buffer dependency — pure browser APIs
70
+ export const bufferToBase64 = (buffer: ArrayBuffer): string => {
71
+ const bytes = new Uint8Array(buffer);
72
+ let binary = '';
73
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
74
+ return btoa(binary);
75
+ };
76
+
77
+ export const base64ToBuffer = (base64: string): ArrayBuffer => {
78
+ const binary = atob(base64);
79
+ const bytes = new Uint8Array(binary.length);
80
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
81
+ return bytes.buffer;
82
+ };
83
+
84
+ export const generateNonce = (appId: string): string => {
85
+ const randomBytes = crypto.getRandomValues(new Uint8Array(8));
86
+ const hex = Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join('');
87
+ return `${appId}^${Date.now()}.${hex}`;
88
+ };
89
+ ```
90
+
91
+ ### AES Service (`aes.service.ts`)
92
+
93
+ ```typescript
94
+ import { bufferToBase64, base64ToBuffer } from './crypto-utils';
95
+
96
+ export interface AESKeyMaterial {
97
+ cryptoKey: CryptoKey; // for encrypt/decrypt calls
98
+ rawBytes: Uint8Array; // for RSA-OAEP wrapping
99
+ }
100
+
101
+ export const generateAESKey = async (): Promise<AESKeyMaterial> => {
102
+ const cryptoKey = await crypto.subtle.generateKey(
103
+ { name: 'AES-GCM', length: 256 },
104
+ true,
105
+ ['encrypt', 'decrypt'],
106
+ );
107
+ const rawBytes = new Uint8Array(await crypto.subtle.exportKey('raw', cryptoKey));
108
+ return { cryptoKey, rawBytes };
109
+ };
110
+
111
+ // Web Crypto AES-GCM appends the 16-byte auth tag automatically
112
+ export const encryptAESGCM = async (
113
+ plaintext: string, key: CryptoKey, iv: Uint8Array,
114
+ ): Promise<ArrayBuffer> => {
115
+ const data = new TextEncoder().encode(plaintext);
116
+ return crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, data);
117
+ };
118
+
119
+ export const decryptAESGCM = async (
120
+ cipherWithTag: ArrayBuffer, key: CryptoKey, iv: Uint8Array,
121
+ ): Promise<string> => {
122
+ // Throws DOMException on auth tag mismatch — treat as tamper detection
123
+ const plainBuffer = await crypto.subtle.decrypt(
124
+ { name: 'AES-GCM', iv, tagLength: 128 }, key, cipherWithTag,
125
+ );
126
+ return new TextDecoder().decode(plainBuffer);
127
+ };
128
+ ```
129
+
130
+ ### RSA Service (`rsa.service.ts`)
131
+
132
+ ```typescript
133
+ import { base64ToBuffer } from './crypto-utils';
134
+
135
+ // Input: PEM (strip headers) or raw Base64 DER → SPKI format required by crypto.subtle
136
+ export const importRSAPublicKey = async (pemOrBase64: string): Promise<CryptoKey> => {
137
+ const base64 = pemOrBase64
138
+ .replace(/-----BEGIN PUBLIC KEY-----/g, '')
139
+ .replace(/-----END PUBLIC KEY-----/g, '')
140
+ .replace(/\s/g, '');
141
+ return crypto.subtle.importKey(
142
+ 'spki',
143
+ base64ToBuffer(base64),
144
+ { name: 'RSA-OAEP', hash: 'SHA-256' },
145
+ false,
146
+ ['encrypt'],
147
+ );
148
+ };
149
+
150
+ export const encryptRSAOAEP = async (rsaKey: CryptoKey, aesKeyBytes: Uint8Array): Promise<ArrayBuffer> =>
151
+ crypto.subtle.encrypt({ name: 'RSA-OAEP' }, rsaKey, aesKeyBytes);
152
+ ```
153
+
154
+ ### TypeScript Types (`types.ts`)
155
+
156
+ ```typescript
157
+ export interface EncryptedEnvelope {
158
+ data: string; // base64(ciphertext + 16-byte GCM auth_tag)
159
+ key: string; // base64(RSA-OAEP encrypted AES key bytes)
160
+ iv: string; // base64(12-byte IV)
161
+ nonce: string; // appId^unix_timestamp_ms.random_hex
162
+ key_id: string; // RSA key version identifier
163
+ }
164
+
165
+ export interface EncryptedResponse {
166
+ data: string; // base64(ciphertext + 16-byte GCM auth_tag)
167
+ iv: string; // base64(new server-generated 12-byte IV — never the request IV)
168
+ nonce?: string;
169
+ }
170
+ ```
171
+
172
+ ### Request Interceptor Pattern
173
+
174
+ ```typescript
175
+ // src/services/security/encryption.interceptor.ts
176
+ import { generateAESKey, encryptAESGCM, decryptAESGCM } from './aes.service';
177
+ import { encryptRSAOAEP } from './rsa.service';
178
+ import { bufferToBase64, base64ToBuffer, generateNonce } from './crypto-utils';
179
+ import { getRSAPublicKey, getCurrentKeyId, refreshRSAPublicKey } from './rsa-key.service';
180
+ import { ENCRYPTION_ENABLED, EXCLUDED_PATHS, APP_ID, HMAC_ENABLED, HMAC_SECRET } from './encryption-config';
181
+
182
+ const ENCRYPT_METHODS = new Set(['post', 'put', 'patch']);
183
+ const requestMap = new Map<string, { cryptoKey: CryptoKey; iv: Uint8Array }>();
184
+
185
+ export const applyProductionEncryptionInterceptor = (axiosInstance: AxiosInstance): void => {
186
+
187
+ axiosInstance.interceptors.request.use(async (config) => {
188
+ if (!ENCRYPTION_ENABLED) return config;
189
+ if (!config.method || !ENCRYPT_METHODS.has(config.method.toLowerCase())) return config;
190
+ if (!config.data) return config;
191
+ if (config.data instanceof FormData) return config;
192
+ if (EXCLUDED_PATHS.some(path => config.url?.includes(path))) return config;
193
+
194
+ const rsaPublicKey = await getRSAPublicKey();
195
+ const { cryptoKey, rawBytes } = await generateAESKey();
196
+ const iv = crypto.getRandomValues(new Uint8Array(12));
197
+ const nonce = generateNonce(APP_ID);
198
+ const requestId = crypto.randomUUID();
199
+
200
+ const plaintext = typeof config.data === 'string' ? config.data : JSON.stringify(config.data);
201
+ const cipherWithTag = await encryptAESGCM(plaintext, cryptoKey, iv);
202
+ const encryptedKey = await encryptRSAOAEP(rsaPublicKey, rawBytes);
203
+
204
+ requestMap.set(requestId, { cryptoKey, iv });
205
+
206
+ config.data = {
207
+ data: bufferToBase64(cipherWithTag),
208
+ key: bufferToBase64(encryptedKey),
209
+ iv: bufferToBase64(iv),
210
+ nonce,
211
+ key_id: getCurrentKeyId(),
212
+ };
213
+ config.headers['X-Request-ID'] = requestId;
214
+
215
+ if (HMAC_ENABLED && HMAC_SECRET) {
216
+ const canonical = `${(config.method ?? 'POST').toUpperCase()}|${config.url}|${nonce}|${config.data.data}`;
217
+ const keyMaterial = await crypto.subtle.importKey(
218
+ 'raw', new TextEncoder().encode(HMAC_SECRET),
219
+ { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
220
+ );
221
+ const sig = await crypto.subtle.sign('HMAC', keyMaterial, new TextEncoder().encode(canonical));
222
+ config.headers['X-Request-Signature'] = bufferToBase64(sig);
223
+ config.headers['X-Nonce'] = nonce;
224
+ }
225
+ return config;
226
+ });
227
+
228
+ axiosInstance.interceptors.response.use(
229
+ async (response) => {
230
+ if (!ENCRYPTION_ENABLED) return response;
231
+ const requestId = response.config.headers['X-Request-ID'] as string;
232
+ const mapping = requestMap.get(requestId);
233
+ if (requestId) requestMap.delete(requestId); // always clean up
234
+
235
+ if (!mapping || !response.data?.data || !response.data?.iv) return response;
236
+
237
+ try {
238
+ const responseIv = new Uint8Array(base64ToBuffer(response.data.iv));
239
+ const cipherWithTag = base64ToBuffer(response.data.data);
240
+ const plaintext = await decryptAESGCM(cipherWithTag, mapping.cryptoKey, responseIv);
241
+ response.data = JSON.parse(plaintext);
242
+ } catch (err: unknown) {
243
+ if (err instanceof DOMException) {
244
+ throw new Error('ERR_CRYPTO_TAG_MISMATCH: payload tampered or wrong key');
245
+ }
246
+ throw err;
247
+ }
248
+ return response;
249
+ },
250
+ async (error) => {
251
+ const requestId = error.config?.headers?.['X-Request-ID'];
252
+ if (requestId) requestMap.delete(requestId); // cleanup on error
253
+
254
+ if (error.response?.status === 401 &&
255
+ error.response?.data?.error?.code === 'KEY_ID_NOT_FOUND') {
256
+ await refreshRSAPublicKey();
257
+ return axiosInstance.request(error.config);
258
+ }
259
+ return Promise.reject(error);
260
+ },
261
+ );
262
+ };
263
+ ```
264
+
265
+ ### RSA Key Service (`rsa-key.service.ts`)
266
+
267
+ ```typescript
268
+ import { importRSAPublicKey } from './rsa.service';
269
+ import { RSA_KEY_ENDPOINT, RSA_KEY_RETRIES } from './encryption-config';
270
+
271
+ let cachedKey: CryptoKey | null = null;
272
+ let currentKeyId = '';
273
+
274
+ export const getRSAPublicKey = async (): Promise<CryptoKey> => {
275
+ if (cachedKey) return cachedKey;
276
+ return fetchAndCacheRSAPublicKey();
277
+ };
278
+
279
+ const fetchAndCacheRSAPublicKey = async (retries = RSA_KEY_RETRIES): Promise<CryptoKey> => {
280
+ for (let i = 1; i <= retries; i++) {
281
+ try {
282
+ const res = await fetch(RSA_KEY_ENDPOINT, { credentials: 'include' });
283
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
284
+ const { public_key, key_id } = await res.json();
285
+ cachedKey = await importRSAPublicKey(public_key);
286
+ currentKeyId = key_id;
287
+ return cachedKey;
288
+ } catch (err) {
289
+ if (i === retries) throw new Error(`ERR_RSA_KEY_UNAVAILABLE: ${err}`);
290
+ }
291
+ }
292
+ throw new Error('ERR_RSA_KEY_UNAVAILABLE');
293
+ };
294
+
295
+ export const refreshRSAPublicKey = async (): Promise<void> => {
296
+ cachedKey = null;
297
+ currentKeyId = '';
298
+ await fetchAndCacheRSAPublicKey();
299
+ };
300
+
301
+ export const getCurrentKeyId = (): string => currentKeyId;
302
+ ```
303
+
304
+ ### Config + Feature Flags (`encryption-config.ts`)
305
+
306
+ Auto-generated with the correct env prefix for the detected framework:
307
+
308
+ ```typescript
309
+ // Vite:
310
+ export const ENCRYPTION_ENABLED = import.meta.env.VITE_ENCRYPTION_ENABLED === 'true';
311
+ export const RSA_KEY_ENDPOINT = import.meta.env.VITE_RSA_KEY_ENDPOINT ?? '/api/security/public-key';
312
+ export const APP_ID = import.meta.env.VITE_APP_ID ?? 'web-app';
313
+ export const HMAC_ENABLED = import.meta.env.VITE_HMAC_ENABLED === 'true';
314
+ export const HMAC_SECRET = import.meta.env.VITE_HMAC_SECRET ?? '';
315
+ export const RSA_KEY_RETRIES = 3;
316
+ export const EXCLUDED_PATHS: string[] = [];
317
+ ```
318
+
319
+ ```typescript
320
+ // CRA: replace VITE_ with REACT_APP_ and import.meta.env with process.env
321
+ // Next.js: replace VITE_ with NEXT_PUBLIC_ and import.meta.env with process.env
322
+ ```
323
+
324
+ ### Browser Storage (`storage.service.ts`)
325
+
326
+ ```typescript
327
+ // sessionStorage — cleared on tab close; acceptable for short-lived tokens
328
+ export const setToken = (key: string, value: string): void => sessionStorage.setItem(key, value);
329
+ export const getToken = (key: string): string | null => sessionStorage.getItem(key);
330
+ export const removeToken = (key: string): void => sessionStorage.removeItem(key);
331
+ export const clearAllTokens = (): void => sessionStorage.clear();
332
+ ```
333
+
334
+ > HTTP-only cookie alternative (recommended for auth tokens): backend sets `Set-Cookie: HttpOnly; Secure; SameSite=Strict`.
335
+ > Configure Axios with `{ withCredentials: true }`. No client-side token storage code needed.
336
+
337
+ ### Module Wiring (`axiosInstance.ts`)
338
+
339
+ ```typescript
340
+ import axios from 'axios';
341
+ import { applyProductionEncryptionInterceptor } from '../services/security/encryption.interceptor';
342
+ import { getRSAPublicKey } from '../services/security/rsa-key.service';
343
+ import { ENCRYPTION_ENABLED } from '../services/security/encryption-config';
344
+
345
+ const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL });
346
+ applyProductionEncryptionInterceptor(axiosInstance);
347
+ // ENCRYPTION_INTERCEPTOR_PLACEHOLDER
348
+
349
+ export default axiosInstance;
350
+
351
+ // Bootstrap: call in App.tsx or auth provider before first API call
352
+ export const bootstrapEncryption = async (): Promise<void> => {
353
+ if (ENCRYPTION_ENABLED) await getRSAPublicKey();
354
+ };
355
+ ```
356
+
357
+ ### .env Files
358
+
359
+ ```
360
+ VITE_ENCRYPTION_ENABLED=true
361
+ VITE_RSA_KEY_ENDPOINT=https://<your-api-host>/api/security/public-key
362
+ VITE_APP_ID=com.yourapp.web
363
+ VITE_HMAC_ENABLED=false
364
+ VITE_HMAC_SECRET=
365
+ ```
366
+
367
+ Set `VITE_ENCRYPTION_ENABLED=false` in `.env.development` and `true` in `.env.production`.
368
+
369
+ ---
370
+
371
+ ## Mode B: ECDH-Session
372
+
373
+ Use when integrating with ECDH-Session / HDFC-LI partner APIs.
374
+ Web Crypto API natively supports ECDH + HKDF — no manual RFC 5869 HMAC chain required.
375
+ Session key derived once per session from ECDH + HKDF-SHA256; IV is random per request.
376
+
377
+ ### ECDH-Session Questions (Additional, all blocking)
378
+
379
+ Ask before implementation — no safe defaults:
380
+ 1. **Session init endpoint URL** — e.g. `POST <API_BASE_URL>/api/security/session/init`
381
+ 2. **HKDF info prefix** — default `HDFC-LI-Session:v1`
382
+ 3. **AAD field order** — default `{"api_ver":"v1","device_id":"<deviceId>","key_id":"<keyId>"}` (alphabetical, no spaces)
383
+ 4. **API version string** for AAD `api_ver` — default `v1`
384
+
385
+ ### Base64URL Utility (`base64url.ts`)
386
+
387
+ ```typescript
388
+ export const toBase64URL = (buffer: ArrayBuffer): string => {
389
+ const bytes = new Uint8Array(buffer);
390
+ let binary = '';
391
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
392
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
393
+ };
394
+
395
+ export const fromBase64URL = (str: string): ArrayBuffer => {
396
+ const b64 = str.replace(/-/g, '+').replace(/_/g, '/');
397
+ const padded = b64.padEnd(b64.length + (4 - (b64.length % 4)) % 4, '=');
398
+ const binary = atob(padded);
399
+ const bytes = new Uint8Array(binary.length);
400
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
401
+ return bytes.buffer;
402
+ };
403
+ ```
404
+
405
+ ### AAD Builder (`aad.ts`)
406
+
407
+ Fixed alphabetical key order — never use `JSON.stringify`:
408
+
409
+ ```typescript
410
+ export const buildAAD = (apiVersion: string, deviceId: string, keyId: string): ArrayBuffer =>
411
+ new TextEncoder().encode(
412
+ `{"api_ver":"${apiVersion}","device_id":"${deviceId}","key_id":"${keyId}"}`
413
+ );
414
+ ```
415
+
416
+ ### ECDH Key Pair + HKDF Session Key (`ecdhSession.ts`)
417
+
418
+ Web Crypto API handles HKDF natively in one `deriveKey` call:
419
+
420
+ ```typescript
421
+ import { toBase64URL, fromBase64URL } from './base64url';
422
+
423
+ export const generateECDHKeyPair = async (): Promise<{ privateKey: CryptoKey; publicKeyBase64URL: string }> => {
424
+ const keyPair = await crypto.subtle.generateKey(
425
+ { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits'],
426
+ );
427
+ const publicKeyBytes = await crypto.subtle.exportKey('raw', keyPair.publicKey);
428
+ return { privateKey: keyPair.privateKey, publicKeyBase64URL: toBase64URL(publicKeyBytes) };
429
+ };
430
+
431
+ export const computeSessionAESKey = async (
432
+ privateKey: CryptoKey,
433
+ serverPublicKeyBase64URL: string,
434
+ saltBase64URL: string,
435
+ keyId: string,
436
+ deviceId: string,
437
+ infoPrefix = 'HDFC-LI-Session:v1',
438
+ ): Promise<CryptoKey> => {
439
+ const serverPublicKey = await crypto.subtle.importKey(
440
+ 'raw', fromBase64URL(serverPublicKeyBase64URL),
441
+ { name: 'ECDH', namedCurve: 'P-256' }, false, [],
442
+ );
443
+ const sharedBits = await crypto.subtle.deriveBits(
444
+ { name: 'ECDH', public: serverPublicKey }, privateKey, 256,
445
+ );
446
+ const hkdfKey = await crypto.subtle.importKey('raw', sharedBits, { name: 'HKDF' }, false, ['deriveKey']);
447
+ return crypto.subtle.deriveKey(
448
+ {
449
+ name: 'HKDF', hash: 'SHA-256',
450
+ salt: fromBase64URL(saltBase64URL),
451
+ info: new TextEncoder().encode(`${infoPrefix}|${keyId}|${deviceId}`),
452
+ },
453
+ hkdfKey,
454
+ { name: 'AES-GCM', length: 256 },
455
+ false, // derived key is non-extractable — never stored
456
+ ['encrypt', 'decrypt'],
457
+ );
458
+ };
459
+ ```
460
+
461
+ ### AES-GCM with AAD (`aesGcm.ts`)
462
+
463
+ Format: `Base64URL(IV || ciphertext || authTag)` — same as the React Native ECDH-Session skill:
464
+
465
+ ```typescript
466
+ import { toBase64URL, fromBase64URL } from './base64url';
467
+
468
+ const IV_BYTES = 12;
469
+ const TAG_BYTES = 16;
470
+
471
+ export const encryptAESGCM = async (plaintext: string, aesKey: CryptoKey, aad: ArrayBuffer): Promise<string> => {
472
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
473
+ const data = new TextEncoder().encode(plaintext);
474
+ const cipherWithTag = await crypto.subtle.encrypt(
475
+ { name: 'AES-GCM', iv, additionalData: aad, tagLength: 128 }, aesKey, data,
476
+ );
477
+ const combined = new Uint8Array(IV_BYTES + cipherWithTag.byteLength);
478
+ combined.set(iv, 0);
479
+ combined.set(new Uint8Array(cipherWithTag), IV_BYTES);
480
+ return toBase64URL(combined.buffer);
481
+ };
482
+
483
+ export const decryptAESGCM = async (payloadBase64URL: string, aesKey: CryptoKey, aad: ArrayBuffer): Promise<string> => {
484
+ const combined = new Uint8Array(fromBase64URL(payloadBase64URL));
485
+ if (combined.length < IV_BYTES + TAG_BYTES + 1) throw new Error('ERR_CRYPTO_PAYLOAD_TOO_SHORT');
486
+ const iv = combined.slice(0, IV_BYTES);
487
+ const cipherWithTag = combined.slice(IV_BYTES);
488
+ // DOMException thrown on auth tag failure — caller must handle as tamper detection
489
+ const plainBuffer = await crypto.subtle.decrypt(
490
+ { name: 'AES-GCM', iv, additionalData: aad, tagLength: 128 }, aesKey, cipherWithTag,
491
+ );
492
+ return new TextDecoder().decode(plainBuffer);
493
+ };
494
+ ```
495
+
496
+ ### ECDH Session Service (`ecdhSessionService.ts`)
497
+
498
+ ```typescript
499
+ import { generateECDHKeyPair, computeSessionAESKey } from './ecdhSession';
500
+
501
+ const STORAGE_KEY = 'ecdh_session';
502
+
503
+ export interface SessionMaterial {
504
+ privateKeyJwk: JsonWebKey;
505
+ serverPublicKeyBase64URL: string;
506
+ saltBase64URL: string;
507
+ keyId: string;
508
+ deviceId: string;
509
+ apiVersion: string;
510
+ }
511
+
512
+ let _sessionAESKey: CryptoKey | null = null;
513
+ let _sessionMaterial: SessionMaterial | null = null;
514
+
515
+ export const getSessionAESKey = (): CryptoKey => {
516
+ if (!_sessionAESKey) throw new Error('ERR_SESSION_NOT_INITIALIZED');
517
+ return _sessionAESKey;
518
+ };
519
+
520
+ export const getSessionMaterial = (): SessionMaterial => {
521
+ if (!_sessionMaterial) throw new Error('ERR_SESSION_NOT_INITIALIZED');
522
+ return _sessionMaterial;
523
+ };
524
+
525
+ const getOrCreateDeviceId = (): string => {
526
+ let id = sessionStorage.getItem('device_id');
527
+ if (!id) { id = crypto.randomUUID(); sessionStorage.setItem('device_id', id); }
528
+ return id;
529
+ };
530
+
531
+ export const restoreSession = async (): Promise<boolean> => {
532
+ const raw = sessionStorage.getItem(STORAGE_KEY);
533
+ if (!raw) return false;
534
+ const mat: SessionMaterial = JSON.parse(raw);
535
+ const privateKey = await crypto.subtle.importKey(
536
+ 'jwk', mat.privateKeyJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits'],
537
+ );
538
+ _sessionMaterial = mat;
539
+ _sessionAESKey = await computeSessionAESKey(
540
+ privateKey, mat.serverPublicKeyBase64URL, mat.saltBase64URL, mat.keyId, mat.deviceId,
541
+ );
542
+ return true;
543
+ };
544
+
545
+ export const initSession = async (sessionInitUrl: string, apiVersion = 'v1'): Promise<void> => {
546
+ const deviceId = getOrCreateDeviceId();
547
+ const { privateKey, publicKeyBase64URL } = await generateECDHKeyPair();
548
+
549
+ const res = await fetch(sessionInitUrl, {
550
+ method: 'POST',
551
+ headers: { 'Content-Type': 'application/json' },
552
+ credentials: 'include',
553
+ body: JSON.stringify({ clientPublicKey: publicKeyBase64URL }),
554
+ });
555
+ if (!res.ok) throw new Error(`ERR_SESSION_INIT_FAILED: HTTP ${res.status}`);
556
+ const { serverPublicKey, salt, keyId } = await res.json();
557
+
558
+ _sessionAESKey = await computeSessionAESKey(privateKey, serverPublicKey, salt, keyId, deviceId);
559
+
560
+ const mat: SessionMaterial = {
561
+ privateKeyJwk: await crypto.subtle.exportKey('jwk', privateKey),
562
+ serverPublicKeyBase64URL: serverPublicKey,
563
+ saltBase64URL: salt, keyId, deviceId, apiVersion,
564
+ };
565
+ _sessionMaterial = mat;
566
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(mat));
567
+ };
568
+
569
+ export const clearSession = (): void => {
570
+ _sessionAESKey = null;
571
+ _sessionMaterial = null;
572
+ sessionStorage.removeItem(STORAGE_KEY);
573
+ sessionStorage.removeItem('device_id');
574
+ };
575
+ ```
576
+
577
+ ### ECDH-Session Interceptor Pattern
578
+
579
+ ```typescript
580
+ // Add to axiosClient.ts
581
+ import { getSessionAESKey, getSessionMaterial } from '../security/ecdh-session/ecdhSessionService';
582
+ import { buildAAD } from '../security/ecdh-session/aad';
583
+ import { encryptAESGCM, decryptAESGCM } from '../security/ecdh-session/aesGcm';
584
+
585
+ instance.interceptors.request.use(async (config) => {
586
+ if (!ENCRYPTION_ENABLED) return config;
587
+ if (!config.method || !ENCRYPT_METHODS.has(config.method.toLowerCase())) return config;
588
+ if (!config.data) return config;
589
+ const aesKey = getSessionAESKey();
590
+ const mat = getSessionMaterial();
591
+ const aad = buildAAD(mat.apiVersion, mat.deviceId, mat.keyId);
592
+ const plaintext = typeof config.data === 'string' ? config.data : JSON.stringify(config.data);
593
+ config.data = { encryptedRequest: await encryptAESGCM(plaintext, aesKey, aad), keyId: mat.keyId };
594
+ config.headers['X-Device-ID'] = mat.deviceId;
595
+ config.headers['X-Timestamp'] = new Date().toISOString();
596
+ return config;
597
+ });
598
+
599
+ instance.interceptors.response.use(
600
+ async (response) => {
601
+ if (!ENCRYPTION_ENABLED || !response.data?.encryptedResponse) return response;
602
+ const aesKey = getSessionAESKey();
603
+ const mat = getSessionMaterial();
604
+ const aad = buildAAD(mat.apiVersion, mat.deviceId, mat.keyId);
605
+ try {
606
+ response.data = JSON.parse(await decryptAESGCM(response.data.encryptedResponse, aesKey, aad));
607
+ } catch (err: unknown) {
608
+ if (err instanceof DOMException) {
609
+ clearSession();
610
+ throw new Error('ERR_CRYPTO_TAG_MISMATCH: session invalid — re-init required');
611
+ }
612
+ throw err;
613
+ }
614
+ return response;
615
+ },
616
+ (error) => Promise.reject(error),
617
+ );
618
+ ```
619
+
620
+ ### App Bootstrap (ECDH-Session)
621
+
622
+ ```typescript
623
+ // In App.tsx or auth provider:
624
+ import { restoreSession, initSession } from './core/security/ecdh-session/ecdhSessionService';
625
+
626
+ async function bootstrapSecurity(): Promise<void> {
627
+ const restored = await restoreSession();
628
+ if (!restored) await initSession(import.meta.env.VITE_SESSION_INIT_URL);
629
+ }
630
+ ```
631
+
632
+ ---
633
+
634
+ ## Key Differences from Approach 1 (React Basic Web Crypto)
635
+
636
+ | Feature | Approach 1 | Approach 7 |
637
+ |---|---|---|
638
+ | Response decryption | No | Yes — per-request key map |
639
+ | RSA key management | Manual/static | Auto fetch/cache/retry service |
640
+ | Per-request key isolation | No | Yes — `CryptoKey` map per requestId |
641
+ | Browser-safe storage | Not included | `sessionStorage` + HTTP-only cookie guidance |
642
+ | ECDH-Session mode | No | Yes — ECDH P-256 + HKDF-SHA256 + AES-GCM with AAD |
643
+ | HMAC signing | No | Yes — Web Crypto HMAC-SHA256 |
644
+ | Framework detection | No | Yes — Vite/CRA/Next.js prefix |
645
+ | `crypto.subtle` availability check | No | Yes — HTTPS gate at startup |
646
+
647
+ ---
648
+
649
+ ## Security Rules (Approach 7)
650
+
651
+ - Use `crypto.getRandomValues` for all random material — never `Math.random()`.
652
+ - Never reuse IV values.
653
+ - `DOMException` from `crypto.subtle.decrypt` = auth tag failure — trigger secure logout, do not retry.
654
+ - Never store AES keys, IVs, or derived ECDH session keys in any storage.
655
+ - ECDH-Session: private key exported as JWK to `sessionStorage` only; derived AES key never stored.
656
+ - Never fall back to `localStorage` for tokens or session material.
657
+ - Enforce HTTPS — `crypto.subtle` is unavailable on plain HTTP origins.
658
+ - `tsconfig.json` must include `"lib": ["DOM"]` for `crypto.subtle` typings.
659
+ - No `crypto-js`, `node-forge`, or Buffer polyfill — use `Uint8Array` + `TextEncoder`/`TextDecoder` + `btoa`/`atob`.
660
+ - CORS: ensure backend `Access-Control-Allow-Headers` includes `X-Request-ID`, `X-Nonce`, `X-Request-Signature`, `X-Device-ID`, `X-Timestamp`.
661
+
662
+ ---
663
+
664
+ ## Build Command
665
+
666
+ ```bash
667
+ npm run build
668
+ ```