create-fluxstack 1.9.1 → 1.12.0

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 (259) hide show
  1. package/.dockerignore +1 -2
  2. package/Dockerfile +8 -8
  3. package/LIVE_COMPONENTS_REVIEW.md +781 -0
  4. package/LLMD/INDEX.md +64 -0
  5. package/LLMD/MAINTENANCE.md +197 -0
  6. package/LLMD/MIGRATION.md +156 -0
  7. package/LLMD/config/.gitkeep +1 -0
  8. package/LLMD/config/declarative-system.md +268 -0
  9. package/LLMD/config/environment-vars.md +327 -0
  10. package/LLMD/config/runtime-reload.md +401 -0
  11. package/LLMD/core/.gitkeep +1 -0
  12. package/LLMD/core/build-system.md +599 -0
  13. package/LLMD/core/framework-lifecycle.md +229 -0
  14. package/LLMD/core/plugin-system.md +451 -0
  15. package/LLMD/patterns/.gitkeep +1 -0
  16. package/LLMD/patterns/anti-patterns.md +297 -0
  17. package/LLMD/patterns/project-structure.md +264 -0
  18. package/LLMD/patterns/type-safety.md +440 -0
  19. package/LLMD/reference/.gitkeep +1 -0
  20. package/LLMD/reference/cli-commands.md +250 -0
  21. package/LLMD/reference/plugin-hooks.md +357 -0
  22. package/LLMD/reference/routing.md +39 -0
  23. package/LLMD/reference/troubleshooting.md +364 -0
  24. package/LLMD/resources/.gitkeep +1 -0
  25. package/LLMD/resources/controllers.md +465 -0
  26. package/LLMD/resources/live-components.md +703 -0
  27. package/LLMD/resources/live-rooms.md +482 -0
  28. package/LLMD/resources/live-upload.md +130 -0
  29. package/LLMD/resources/plugins-external.md +617 -0
  30. package/LLMD/resources/routes-eden.md +254 -0
  31. package/README.md +37 -17
  32. package/app/client/index.html +0 -1
  33. package/app/client/src/App.tsx +109 -156
  34. package/app/client/src/components/AppLayout.tsx +68 -0
  35. package/app/client/src/components/BackButton.tsx +13 -0
  36. package/app/client/src/components/DemoPage.tsx +20 -0
  37. package/app/client/src/components/LiveUploadWidget.tsx +204 -0
  38. package/app/client/src/lib/eden-api.ts +85 -65
  39. package/app/client/src/live/ChatDemo.tsx +107 -0
  40. package/app/client/src/live/CounterDemo.tsx +206 -0
  41. package/app/client/src/live/FormDemo.tsx +119 -0
  42. package/app/client/src/live/RoomChatDemo.tsx +242 -0
  43. package/app/client/src/live/UploadDemo.tsx +21 -0
  44. package/app/client/src/main.tsx +13 -10
  45. package/app/client/src/pages/ApiTestPage.tsx +108 -0
  46. package/app/client/src/pages/HomePage.tsx +76 -0
  47. package/app/client/src/vite-env.d.ts +1 -1
  48. package/app/server/app.ts +1 -4
  49. package/app/server/controllers/users.controller.ts +36 -44
  50. package/app/server/index.ts +24 -107
  51. package/app/server/live/LiveChat.ts +77 -0
  52. package/app/server/live/LiveCounter.ts +67 -0
  53. package/app/server/live/LiveForm.ts +63 -0
  54. package/app/server/live/LiveLocalCounter.ts +32 -0
  55. package/app/server/live/LiveRoomChat.ts +285 -0
  56. package/app/server/live/LiveUpload.ts +81 -0
  57. package/app/server/live/register-components.ts +19 -19
  58. package/app/server/routes/index.ts +3 -1
  59. package/app/server/routes/room.routes.ts +117 -0
  60. package/app/server/routes/users.routes.ts +35 -27
  61. package/app/shared/types/index.ts +14 -2
  62. package/config/app.config.ts +2 -62
  63. package/config/client.config.ts +2 -95
  64. package/config/database.config.ts +2 -99
  65. package/config/fluxstack.config.ts +25 -45
  66. package/config/index.ts +57 -38
  67. package/config/monitoring.config.ts +2 -114
  68. package/config/plugins.config.ts +2 -80
  69. package/config/server.config.ts +2 -68
  70. package/config/services.config.ts +2 -130
  71. package/config/system/app.config.ts +29 -0
  72. package/config/system/build.config.ts +49 -0
  73. package/config/system/client.config.ts +68 -0
  74. package/config/system/database.config.ts +17 -0
  75. package/config/system/fluxstack.config.ts +114 -0
  76. package/config/{logger.config.ts → system/logger.config.ts} +3 -1
  77. package/config/system/monitoring.config.ts +114 -0
  78. package/config/system/plugins.config.ts +84 -0
  79. package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
  80. package/config/system/server.config.ts +68 -0
  81. package/config/system/services.config.ts +46 -0
  82. package/config/{system.config.ts → system/system.config.ts} +1 -1
  83. package/core/build/bundler.ts +4 -1
  84. package/core/build/flux-plugins-generator.ts +325 -325
  85. package/core/build/index.ts +159 -27
  86. package/core/build/live-components-generator.ts +70 -3
  87. package/core/build/optimizer.ts +235 -235
  88. package/core/cli/command-registry.ts +6 -4
  89. package/core/cli/commands/build.ts +79 -0
  90. package/core/cli/commands/create.ts +54 -0
  91. package/core/cli/commands/dev.ts +101 -0
  92. package/core/cli/commands/help.ts +34 -0
  93. package/core/cli/commands/index.ts +34 -0
  94. package/core/cli/commands/make-plugin.ts +90 -0
  95. package/core/cli/commands/plugin-add.ts +197 -0
  96. package/core/cli/commands/plugin-deps.ts +2 -2
  97. package/core/cli/commands/plugin-list.ts +208 -0
  98. package/core/cli/commands/plugin-remove.ts +170 -0
  99. package/core/cli/generators/component.ts +769 -769
  100. package/core/cli/generators/controller.ts +1 -1
  101. package/core/cli/generators/index.ts +146 -146
  102. package/core/cli/generators/interactive.ts +227 -227
  103. package/core/cli/generators/plugin.ts +2 -2
  104. package/core/cli/generators/prompts.ts +82 -82
  105. package/core/cli/generators/route.ts +6 -6
  106. package/core/cli/generators/service.ts +2 -2
  107. package/core/cli/generators/template-engine.ts +4 -3
  108. package/core/cli/generators/types.ts +2 -2
  109. package/core/cli/generators/utils.ts +191 -191
  110. package/core/cli/index.ts +115 -558
  111. package/core/cli/plugin-discovery.ts +2 -2
  112. package/core/client/LiveComponentsProvider.tsx +63 -17
  113. package/core/client/api/eden.ts +183 -0
  114. package/core/client/api/index.ts +11 -0
  115. package/core/client/components/Live.tsx +104 -0
  116. package/core/client/fluxstack.ts +1 -9
  117. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
  118. package/core/client/hooks/state-validator.ts +1 -1
  119. package/core/client/hooks/useAuth.ts +48 -48
  120. package/core/client/hooks/useChunkedUpload.ts +170 -69
  121. package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
  122. package/core/client/hooks/useLiveComponent.ts +800 -0
  123. package/core/client/hooks/useLiveUpload.ts +71 -0
  124. package/core/client/hooks/useRoom.ts +409 -0
  125. package/core/client/hooks/useRoomProxy.ts +382 -0
  126. package/core/client/index.ts +18 -51
  127. package/core/client/standalone-entry.ts +8 -0
  128. package/core/client/standalone.ts +74 -53
  129. package/core/client/state/createStore.ts +192 -192
  130. package/core/client/state/index.ts +14 -14
  131. package/core/config/index.ts +70 -291
  132. package/core/config/schema.ts +42 -723
  133. package/core/framework/client.ts +131 -131
  134. package/core/framework/index.ts +7 -7
  135. package/core/framework/server.ts +227 -47
  136. package/core/framework/types.ts +2 -2
  137. package/core/index.ts +23 -4
  138. package/core/live/ComponentRegistry.ts +7 -3
  139. package/core/live/types.ts +77 -0
  140. package/core/plugins/built-in/index.ts +134 -131
  141. package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1074
  142. package/core/plugins/built-in/live-components/index.ts +1 -1
  143. package/core/plugins/built-in/monitoring/index.ts +111 -47
  144. package/core/plugins/built-in/static/index.ts +1 -1
  145. package/core/plugins/built-in/swagger/index.ts +68 -265
  146. package/core/plugins/built-in/vite/index.ts +94 -306
  147. package/core/plugins/built-in/vite/vite-dev.ts +82 -0
  148. package/core/plugins/config.ts +9 -7
  149. package/core/plugins/dependency-manager.ts +31 -1
  150. package/core/plugins/discovery.ts +19 -7
  151. package/core/plugins/executor.ts +2 -2
  152. package/core/plugins/index.ts +203 -203
  153. package/core/plugins/manager.ts +27 -39
  154. package/core/plugins/module-resolver.ts +19 -8
  155. package/core/plugins/registry.ts +309 -21
  156. package/core/plugins/types.ts +106 -55
  157. package/core/server/framework.ts +66 -43
  158. package/core/server/index.ts +15 -16
  159. package/core/server/live/ComponentRegistry.ts +91 -75
  160. package/core/server/live/FileUploadManager.ts +41 -31
  161. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  162. package/core/server/live/LiveRoomManager.ts +261 -0
  163. package/core/server/live/RoomEventBus.ts +234 -0
  164. package/core/server/live/RoomStateManager.ts +172 -0
  165. package/core/server/live/StateSignature.ts +643 -643
  166. package/core/server/live/WebSocketConnectionManager.ts +30 -19
  167. package/core/server/live/auto-generated-components.ts +41 -26
  168. package/core/server/live/index.ts +14 -0
  169. package/core/server/live/websocket-plugin.ts +233 -72
  170. package/core/server/middleware/elysia-helpers.ts +7 -2
  171. package/core/server/middleware/errorHandling.ts +1 -1
  172. package/core/server/middleware/index.ts +31 -31
  173. package/core/server/plugins/database.ts +180 -180
  174. package/core/server/plugins/static-files-plugin.ts +69 -260
  175. package/core/server/plugins/swagger.ts +33 -33
  176. package/core/server/rooms/RoomBroadcaster.ts +357 -0
  177. package/core/server/rooms/RoomSystem.ts +463 -0
  178. package/core/server/rooms/index.ts +13 -0
  179. package/core/server/services/BaseService.ts +1 -1
  180. package/core/server/services/ServiceContainer.ts +1 -1
  181. package/core/server/services/index.ts +8 -8
  182. package/core/templates/create-project.ts +12 -12
  183. package/core/testing/index.ts +9 -9
  184. package/core/testing/setup.ts +73 -73
  185. package/core/types/api.ts +168 -168
  186. package/core/types/build.ts +219 -218
  187. package/core/types/config.ts +56 -26
  188. package/core/types/index.ts +4 -4
  189. package/core/types/plugin.ts +107 -99
  190. package/core/types/types.ts +490 -14
  191. package/core/utils/build-logger.ts +324 -324
  192. package/core/utils/config-schema.ts +480 -480
  193. package/core/utils/env.ts +2 -8
  194. package/core/utils/errors/codes.ts +114 -114
  195. package/core/utils/errors/handlers.ts +36 -1
  196. package/core/utils/errors/index.ts +49 -5
  197. package/core/utils/errors/middleware.ts +113 -113
  198. package/core/utils/helpers.ts +6 -16
  199. package/core/utils/index.ts +17 -17
  200. package/core/utils/logger/colors.ts +114 -114
  201. package/core/utils/logger/config.ts +13 -9
  202. package/core/utils/logger/formatter.ts +82 -82
  203. package/core/utils/logger/group-logger.ts +101 -101
  204. package/core/utils/logger/index.ts +6 -1
  205. package/core/utils/logger/stack-trace.ts +3 -1
  206. package/core/utils/logger/startup-banner.ts +82 -66
  207. package/core/utils/logger/winston-logger.ts +152 -152
  208. package/core/utils/monitoring/index.ts +211 -211
  209. package/core/utils/sync-version.ts +66 -66
  210. package/core/utils/version.ts +1 -1
  211. package/create-fluxstack.ts +8 -7
  212. package/eslint.config.js +23 -23
  213. package/package.json +14 -15
  214. package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
  215. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  216. package/plugins/crypto-auth/client/components/index.ts +11 -11
  217. package/plugins/crypto-auth/client/index.ts +11 -11
  218. package/plugins/crypto-auth/config/index.ts +1 -1
  219. package/plugins/crypto-auth/index.ts +4 -4
  220. package/plugins/crypto-auth/package.json +65 -65
  221. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  222. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  223. package/plugins/crypto-auth/server/index.ts +21 -21
  224. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
  225. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  226. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
  227. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
  228. package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
  229. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  230. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  231. package/tsconfig.api-strict.json +16 -0
  232. package/tsconfig.json +10 -14
  233. package/{app/client/tsconfig.node.json → tsconfig.node.json} +1 -1
  234. package/types/global.d.ts +29 -29
  235. package/types/vitest.d.ts +8 -8
  236. package/vite.config.ts +38 -62
  237. package/vitest.config.live.ts +10 -9
  238. package/vitest.config.ts +29 -17
  239. package/workspace.json +5 -5
  240. package/app/client/README.md +0 -69
  241. package/app/client/SIMPLIFICATION.md +0 -140
  242. package/app/client/frontend-only.ts +0 -12
  243. package/app/client/tsconfig.app.json +0 -44
  244. package/app/client/tsconfig.json +0 -7
  245. package/app/client/zustand-setup.md +0 -65
  246. package/app/server/backend-only.ts +0 -18
  247. package/app/server/live/LiveClockComponent.ts +0 -215
  248. package/app/server/routes/env-test.ts +0 -110
  249. package/core/client/hooks/index.ts +0 -7
  250. package/core/client/hooks/useHybridLiveComponent.ts +0 -631
  251. package/core/client/hooks/useWebSocket.ts +0 -373
  252. package/core/config/env.ts +0 -546
  253. package/core/config/loader.ts +0 -522
  254. package/core/config/runtime-config.ts +0 -327
  255. package/core/config/validator.ts +0 -540
  256. package/core/server/backend-entry.ts +0 -51
  257. package/core/server/standalone.ts +0 -106
  258. package/core/utils/regenerate-files.ts +0 -69
  259. package/fluxstack.config.ts +0 -354
@@ -1,644 +1,644 @@
1
- // 🔐 FluxStack Enhanced State Signature System - Advanced cryptographic validation with key rotation and compression
2
-
3
- import { createHmac, randomBytes, createCipheriv, createDecipheriv, scrypt } from 'crypto'
4
- import { promisify } from 'util'
5
- import { gzip, gunzip } from 'zlib'
6
-
7
- const scryptAsync = promisify(scrypt)
8
- const gzipAsync = promisify(gzip)
9
- const gunzipAsync = promisify(gunzip)
10
-
11
- export interface SignedState<T = any> {
12
- data: T
13
- signature: string
14
- timestamp: number
15
- componentId: string
16
- version: number
17
- keyId?: string // For key rotation
18
- compressed?: boolean // For state compression
19
- encrypted?: boolean // For sensitive data
20
- }
21
-
22
- export interface StateValidationResult {
23
- valid: boolean
24
- error?: string
25
- tampered?: boolean
26
- expired?: boolean
27
- keyRotated?: boolean
28
- }
29
-
30
- export interface StateBackup<T = any> {
31
- componentId: string
32
- state: T
33
- timestamp: number
34
- version: number
35
- checksum: string
36
- }
37
-
38
- export interface KeyRotationConfig {
39
- rotationInterval: number // milliseconds
40
- maxKeyAge: number // milliseconds
41
- keyRetentionCount: number // number of old keys to keep
42
- }
43
-
44
- export interface CompressionConfig {
45
- enabled: boolean
46
- threshold: number // bytes - compress if state is larger than this
47
- level: number // compression level 1-9
48
- }
49
-
50
- export class StateSignature {
51
- private static instance: StateSignature
52
- private currentKey: string
53
- private keyHistory: Map<string, { key: string; createdAt: number }> = new Map()
54
- private readonly maxAge = 24 * 60 * 60 * 1000 // 24 hours default
55
- private keyRotationConfig: KeyRotationConfig
56
- private compressionConfig: CompressionConfig
57
- private backups = new Map<string, StateBackup[]>() // componentId -> backups
58
- private migrationFunctions = new Map<string, (state: any) => any>() // version -> migration function
59
-
60
- constructor(secretKey?: string, options?: {
61
- keyRotation?: Partial<KeyRotationConfig>
62
- compression?: Partial<CompressionConfig>
63
- }) {
64
- this.currentKey = secretKey || this.generateSecretKey()
65
- this.keyHistory.set(this.getCurrentKeyId(), {
66
- key: this.currentKey,
67
- createdAt: Date.now()
68
- })
69
-
70
- this.keyRotationConfig = {
71
- rotationInterval: 7 * 24 * 60 * 60 * 1000, // 7 days
72
- maxKeyAge: 30 * 24 * 60 * 60 * 1000, // 30 days
73
- keyRetentionCount: 5,
74
- ...options?.keyRotation
75
- }
76
-
77
- this.compressionConfig = {
78
- enabled: true,
79
- threshold: 1024, // 1KB
80
- level: 6,
81
- ...options?.compression
82
- }
83
-
84
- this.setupKeyRotation()
85
- }
86
-
87
- public static getInstance(secretKey?: string, options?: {
88
- keyRotation?: Partial<KeyRotationConfig>
89
- compression?: Partial<CompressionConfig>
90
- }): StateSignature {
91
- if (!StateSignature.instance) {
92
- StateSignature.instance = new StateSignature(secretKey, options)
93
- }
94
- return StateSignature.instance
95
- }
96
-
97
- private generateSecretKey(): string {
98
- return randomBytes(32).toString('hex')
99
- }
100
-
101
- private getCurrentKeyId(): string {
102
- return createHmac('sha256', this.currentKey).update('keyid').digest('hex').substring(0, 8)
103
- }
104
-
105
- private setupKeyRotation(): void {
106
- // Rotate keys periodically
107
- setInterval(() => {
108
- this.rotateKey()
109
- }, this.keyRotationConfig.rotationInterval)
110
-
111
- // Cleanup old keys
112
- setInterval(() => {
113
- this.cleanupOldKeys()
114
- }, 24 * 60 * 60 * 1000) // Daily cleanup
115
- }
116
-
117
- private rotateKey(): void {
118
- const oldKeyId = this.getCurrentKeyId()
119
- this.currentKey = this.generateSecretKey()
120
- const newKeyId = this.getCurrentKeyId()
121
-
122
- this.keyHistory.set(newKeyId, {
123
- key: this.currentKey,
124
- createdAt: Date.now()
125
- })
126
-
127
- console.log(`🔄 Key rotated from ${oldKeyId} to ${newKeyId}`)
128
- }
129
-
130
- private cleanupOldKeys(): void {
131
- const now = Date.now()
132
- const keysToDelete: string[] = []
133
-
134
- for (const [keyId, keyData] of this.keyHistory) {
135
- const keyAge = now - keyData.createdAt
136
- if (keyAge > this.keyRotationConfig.maxKeyAge) {
137
- keysToDelete.push(keyId)
138
- }
139
- }
140
-
141
- // Keep at least the retention count of keys
142
- const sortedKeys = Array.from(this.keyHistory.entries())
143
- .sort((a, b) => b[1].createdAt - a[1].createdAt)
144
-
145
- if (sortedKeys.length > this.keyRotationConfig.keyRetentionCount) {
146
- const excessKeys = sortedKeys.slice(this.keyRotationConfig.keyRetentionCount)
147
- for (const [keyId] of excessKeys) {
148
- keysToDelete.push(keyId)
149
- }
150
- }
151
-
152
- for (const keyId of keysToDelete) {
153
- this.keyHistory.delete(keyId)
154
- }
155
-
156
- if (keysToDelete.length > 0) {
157
- console.log(`🧹 Cleaned up ${keysToDelete.length} old keys`)
158
- }
159
- }
160
-
161
- private getKeyById(keyId: string): string | null {
162
- const keyData = this.keyHistory.get(keyId)
163
- return keyData ? keyData.key : null
164
- }
165
-
166
- /**
167
- * Sign component state with enhanced security, compression, and encryption
168
- */
169
- public async signState<T>(
170
- componentId: string,
171
- data: T,
172
- version: number = 1,
173
- options?: {
174
- compress?: boolean
175
- encrypt?: boolean
176
- backup?: boolean
177
- }
178
- ): Promise<SignedState<T>> {
179
- const timestamp = Date.now()
180
- const keyId = this.getCurrentKeyId()
181
-
182
- let processedData = data
183
- let compressed = false
184
- let encrypted = false
185
-
186
- try {
187
- // Serialize data for processing
188
- const serializedData = JSON.stringify(data)
189
-
190
- // Compress if enabled and data is large enough
191
- if (this.compressionConfig.enabled &&
192
- (options?.compress !== false) &&
193
- Buffer.byteLength(serializedData, 'utf8') > this.compressionConfig.threshold) {
194
-
195
- const compressedBuffer = await gzipAsync(Buffer.from(serializedData, 'utf8'))
196
- processedData = compressedBuffer.toString('base64') as any
197
- compressed = true
198
-
199
- console.log(`🗜️ State compressed: ${Buffer.byteLength(serializedData, 'utf8')} -> ${compressedBuffer.length} bytes`)
200
- }
201
-
202
- // Encrypt sensitive data if requested
203
- if (options?.encrypt) {
204
- const encryptedData = await this.encryptData(processedData)
205
- processedData = encryptedData as any
206
- encrypted = true
207
-
208
- console.log('🔒 State encrypted for component:', componentId)
209
- }
210
-
211
- // Create payload for signing
212
- const payload = {
213
- data: processedData,
214
- componentId,
215
- timestamp,
216
- version,
217
- keyId,
218
- compressed,
219
- encrypted
220
- }
221
-
222
- // Generate signature with current key
223
- const signature = this.createSignature(payload)
224
-
225
- // Create backup if requested
226
- if (options?.backup) {
227
- await this.createStateBackup(componentId, data, version)
228
- }
229
-
230
- console.log('🔐 State signed:', {
231
- componentId,
232
- timestamp,
233
- version,
234
- keyId,
235
- compressed,
236
- encrypted,
237
- signature: signature.substring(0, 16) + '...'
238
- })
239
-
240
- return {
241
- data: processedData,
242
- signature,
243
- timestamp,
244
- componentId,
245
- version,
246
- keyId,
247
- compressed,
248
- encrypted
249
- }
250
-
251
- } catch (error) {
252
- console.error('❌ Failed to sign state:', error)
253
- throw new Error(`State signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
254
- }
255
- }
256
-
257
- /**
258
- * Validate signed state integrity with enhanced security checks
259
- */
260
- public async validateState<T>(signedState: SignedState<T>, maxAge?: number): Promise<StateValidationResult> {
261
- const { data, signature, timestamp, componentId, version, keyId, compressed, encrypted } = signedState
262
-
263
- try {
264
- // Check timestamp (prevent replay attacks)
265
- const age = Date.now() - timestamp
266
- const ageLimit = maxAge || this.maxAge
267
-
268
- if (age > ageLimit) {
269
- return {
270
- valid: false,
271
- error: 'State signature expired',
272
- expired: true
273
- }
274
- }
275
-
276
- // Determine which key to use for validation
277
- let validationKey = this.currentKey
278
- let keyRotated = false
279
-
280
- if (keyId) {
281
- const historicalKey = this.getKeyById(keyId)
282
- if (historicalKey) {
283
- validationKey = historicalKey
284
- keyRotated = keyId !== this.getCurrentKeyId()
285
- } else {
286
- return {
287
- valid: false,
288
- error: 'Signing key not found or expired',
289
- keyRotated: true
290
- }
291
- }
292
- }
293
-
294
- // Recreate payload for verification
295
- const payload = {
296
- data,
297
- componentId,
298
- timestamp,
299
- version,
300
- keyId,
301
- compressed,
302
- encrypted
303
- }
304
-
305
- // Verify signature with appropriate key
306
- const expectedSignature = this.createSignature(payload, validationKey)
307
-
308
- if (!this.constantTimeEquals(signature, expectedSignature)) {
309
- console.warn('⚠️ State signature mismatch:', {
310
- componentId,
311
- expected: expectedSignature.substring(0, 16) + '...',
312
- received: signature.substring(0, 16) + '...'
313
- })
314
-
315
- return {
316
- valid: false,
317
- error: 'State signature invalid - possible tampering',
318
- tampered: true
319
- }
320
- }
321
-
322
- console.log('✅ State signature valid:', {
323
- componentId,
324
- age: `${Math.round(age / 1000)}s`,
325
- version
326
- })
327
-
328
- return { valid: true }
329
-
330
- } catch (error: any) {
331
- return {
332
- valid: false,
333
- error: `Validation error: ${error.message}`
334
- }
335
- }
336
- }
337
-
338
- /**
339
- * Create HMAC signature for payload using specified key
340
- */
341
- private createSignature(payload: any, key?: string): string {
342
- // Stringify deterministically (sorted keys)
343
- const normalizedPayload = JSON.stringify(payload, Object.keys(payload).sort())
344
-
345
- return createHmac('sha256', key || this.currentKey)
346
- .update(normalizedPayload)
347
- .digest('hex')
348
- }
349
-
350
- /**
351
- * Encrypt sensitive data
352
- */
353
- private async encryptData<T>(data: T): Promise<string> {
354
- try {
355
- const serializedData = JSON.stringify(data)
356
- const key = await scryptAsync(this.currentKey, 'salt', 32) as Buffer
357
- const iv = randomBytes(16)
358
- const cipher = createCipheriv('aes-256-cbc', key, iv)
359
-
360
- let encrypted = cipher.update(serializedData, 'utf8', 'hex')
361
- encrypted += cipher.final('hex')
362
-
363
- return iv.toString('hex') + ':' + encrypted
364
- } catch (error) {
365
- throw new Error(`Encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
366
- }
367
- }
368
-
369
- /**
370
- * Decrypt sensitive data
371
- */
372
- private async decryptData(encryptedData: string, key?: string): Promise<any> {
373
- try {
374
- const [ivHex, encrypted] = encryptedData.split(':')
375
- const iv = Buffer.from(ivHex, 'hex')
376
- const derivedKey = await scryptAsync(key || this.currentKey, 'salt', 32) as Buffer
377
- const decipher = createDecipheriv('aes-256-cbc', derivedKey, iv)
378
-
379
- let decrypted = decipher.update(encrypted, 'hex', 'utf8')
380
- decrypted += decipher.final('utf8')
381
-
382
- return JSON.parse(decrypted)
383
- } catch (error) {
384
- throw new Error(`Decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
385
- }
386
- }
387
-
388
- /**
389
- * Decompress state data
390
- */
391
- private async decompressData(compressedData: string): Promise<any> {
392
- try {
393
- const compressedBuffer = Buffer.from(compressedData, 'base64')
394
- const decompressedBuffer = await gunzipAsync(compressedBuffer)
395
- return JSON.parse(decompressedBuffer.toString('utf8'))
396
- } catch (error) {
397
- throw new Error(`Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
398
- }
399
- }
400
-
401
- /**
402
- * Create state backup
403
- */
404
- private async createStateBackup<T>(componentId: string, state: T, version: number): Promise<void> {
405
- try {
406
- const backup: StateBackup<T> = {
407
- componentId,
408
- state,
409
- timestamp: Date.now(),
410
- version,
411
- checksum: createHmac('sha256', this.currentKey).update(JSON.stringify(state)).digest('hex')
412
- }
413
-
414
- let backups = this.backups.get(componentId) || []
415
- backups.push(backup)
416
-
417
- // Keep only last 10 backups per component
418
- if (backups.length > 10) {
419
- backups = backups.slice(-10)
420
- }
421
-
422
- this.backups.set(componentId, backups)
423
-
424
- console.log(`💾 State backup created for component ${componentId} v${version}`)
425
- } catch (error) {
426
- console.error(`❌ Failed to create backup for component ${componentId}:`, error)
427
- }
428
- }
429
-
430
- /**
431
- * Constant-time string comparison to prevent timing attacks
432
- */
433
- private constantTimeEquals(a: string, b: string): boolean {
434
- if (a.length !== b.length) {
435
- return false
436
- }
437
-
438
- let result = 0
439
- for (let i = 0; i < a.length; i++) {
440
- result |= a.charCodeAt(i) ^ b.charCodeAt(i)
441
- }
442
-
443
- return result === 0
444
- }
445
-
446
- /**
447
- * Extract and process signed state data (decompression, decryption)
448
- */
449
- public async extractData<T>(signedState: SignedState<T>): Promise<T> {
450
- let data = signedState.data
451
-
452
- try {
453
- // Decrypt if encrypted
454
- if (signedState.encrypted) {
455
- const keyToUse = signedState.keyId ? this.getKeyById(signedState.keyId) : this.currentKey
456
- if (!keyToUse) {
457
- throw new Error('Decryption key not available')
458
- }
459
- data = await this.decryptData(data as string, keyToUse)
460
- }
461
-
462
- // Decompress if compressed
463
- if (signedState.compressed) {
464
- data = await this.decompressData(data as string)
465
- }
466
-
467
- return data
468
- } catch (error) {
469
- console.error('❌ Failed to extract state data:', error)
470
- throw error
471
- }
472
- }
473
-
474
- /**
475
- * Update signature for new state version with enhanced options
476
- */
477
- public async updateSignature<T>(
478
- signedState: SignedState<T>,
479
- newData: T,
480
- options?: {
481
- compress?: boolean
482
- encrypt?: boolean
483
- backup?: boolean
484
- }
485
- ): Promise<SignedState<T>> {
486
- return this.signState(
487
- signedState.componentId,
488
- newData,
489
- signedState.version + 1,
490
- options
491
- )
492
- }
493
-
494
- /**
495
- * Register state migration function
496
- */
497
- public registerMigration(fromVersion: string, toVersion: string, migrationFn: (state: any) => any): void {
498
- const key = `${fromVersion}->${toVersion}`
499
- this.migrationFunctions.set(key, migrationFn)
500
- console.log(`📋 Registered migration: ${key}`)
501
- }
502
-
503
- /**
504
- * Migrate state to new version
505
- */
506
- public async migrateState<T>(signedState: SignedState<T>, targetVersion: string): Promise<SignedState<T> | null> {
507
- const currentVersion = signedState.version.toString()
508
- const migrationKey = `${currentVersion}->${targetVersion}`
509
-
510
- const migrationFn = this.migrationFunctions.get(migrationKey)
511
- if (!migrationFn) {
512
- console.warn(`⚠️ No migration function found for ${migrationKey}`)
513
- return null
514
- }
515
-
516
- try {
517
- // Extract current data
518
- const currentData = await this.extractData(signedState)
519
-
520
- // Apply migration
521
- const migratedData = migrationFn(currentData)
522
-
523
- // Create new signed state
524
- const newSignedState = await this.signState(
525
- signedState.componentId,
526
- migratedData,
527
- parseInt(targetVersion),
528
- {
529
- compress: signedState.compressed,
530
- encrypt: signedState.encrypted,
531
- backup: true
532
- }
533
- )
534
-
535
- console.log(`✅ State migrated from v${currentVersion} to v${targetVersion} for component ${signedState.componentId}`)
536
- return newSignedState
537
-
538
- } catch (error) {
539
- console.error(`❌ State migration failed for ${migrationKey}:`, error)
540
- return null
541
- }
542
- }
543
-
544
- /**
545
- * Recover state from backup
546
- */
547
- public recoverStateFromBackup<T>(componentId: string, version?: number): StateBackup<T> | null {
548
- const backups = this.backups.get(componentId)
549
- if (!backups || backups.length === 0) {
550
- return null
551
- }
552
-
553
- if (version !== undefined) {
554
- // Find specific version
555
- return backups.find(backup => backup.version === version) || null
556
- } else {
557
- // Return latest backup
558
- return backups[backups.length - 1] || null
559
- }
560
- }
561
-
562
- /**
563
- * Get all backups for a component
564
- */
565
- public getComponentBackups(componentId: string): StateBackup[] {
566
- return this.backups.get(componentId) || []
567
- }
568
-
569
- /**
570
- * Verify backup integrity
571
- */
572
- public verifyBackup<T>(backup: StateBackup<T>): boolean {
573
- try {
574
- const expectedChecksum = createHmac('sha256', this.currentKey)
575
- .update(JSON.stringify(backup.state))
576
- .digest('hex')
577
-
578
- return this.constantTimeEquals(backup.checksum, expectedChecksum)
579
- } catch {
580
- return false
581
- }
582
- }
583
-
584
- /**
585
- * Clean up old backups
586
- */
587
- public cleanupBackups(maxAge: number = 7 * 24 * 60 * 60 * 1000): void {
588
- const now = Date.now()
589
- let totalCleaned = 0
590
-
591
- for (const [componentId, backups] of this.backups) {
592
- const validBackups = backups.filter(backup => {
593
- const age = now - backup.timestamp
594
- return age <= maxAge
595
- })
596
-
597
- const cleaned = backups.length - validBackups.length
598
- totalCleaned += cleaned
599
-
600
- if (validBackups.length === 0) {
601
- this.backups.delete(componentId)
602
- } else {
603
- this.backups.set(componentId, validBackups)
604
- }
605
- }
606
-
607
- if (totalCleaned > 0) {
608
- console.log(`🧹 Cleaned up ${totalCleaned} old state backups`)
609
- }
610
- }
611
-
612
- /**
613
- * Get server's signature info for debugging
614
- */
615
- public getSignatureInfo() {
616
- return {
617
- algorithm: 'HMAC-SHA256',
618
- keyLength: this.currentKey.length,
619
- maxAge: this.maxAge,
620
- keyPreview: this.currentKey.substring(0, 8) + '...',
621
- currentKeyId: this.getCurrentKeyId(),
622
- keyHistoryCount: this.keyHistory.size,
623
- compressionEnabled: this.compressionConfig.enabled,
624
- rotationInterval: this.keyRotationConfig.rotationInterval
625
- }
626
- }
627
- }
628
-
629
- // Global instance with enhanced configuration
630
- export const stateSignature = StateSignature.getInstance(
631
- process.env.FLUXSTACK_STATE_SECRET || undefined,
632
- {
633
- keyRotation: {
634
- rotationInterval: parseInt(process.env.FLUXSTACK_KEY_ROTATION_INTERVAL || '604800000'), // 7 days
635
- maxKeyAge: parseInt(process.env.FLUXSTACK_MAX_KEY_AGE || '2592000000'), // 30 days
636
- keyRetentionCount: parseInt(process.env.FLUXSTACK_KEY_RETENTION_COUNT || '5')
637
- },
638
- compression: {
639
- enabled: process.env.FLUXSTACK_COMPRESSION_ENABLED !== 'false',
640
- threshold: parseInt(process.env.FLUXSTACK_COMPRESSION_THRESHOLD || '1024'), // 1KB
641
- level: parseInt(process.env.FLUXSTACK_COMPRESSION_LEVEL || '6')
642
- }
643
- }
1
+ // 🔐 FluxStack Enhanced State Signature System - Advanced cryptographic validation with key rotation and compression
2
+
3
+ import { createHmac, randomBytes, createCipheriv, createDecipheriv, scrypt } from 'crypto'
4
+ import { promisify } from 'util'
5
+ import { gzip, gunzip } from 'zlib'
6
+
7
+ const scryptAsync = promisify(scrypt)
8
+ const gzipAsync = promisify(gzip)
9
+ const gunzipAsync = promisify(gunzip)
10
+
11
+ export interface SignedState<T = any> {
12
+ data: T
13
+ signature: string
14
+ timestamp: number
15
+ componentId: string
16
+ version: number
17
+ keyId?: string // For key rotation
18
+ compressed?: boolean // For state compression
19
+ encrypted?: boolean // For sensitive data
20
+ }
21
+
22
+ export interface StateValidationResult {
23
+ valid: boolean
24
+ error?: string
25
+ tampered?: boolean
26
+ expired?: boolean
27
+ keyRotated?: boolean
28
+ }
29
+
30
+ export interface StateBackup<T = any> {
31
+ componentId: string
32
+ state: T
33
+ timestamp: number
34
+ version: number
35
+ checksum: string
36
+ }
37
+
38
+ export interface KeyRotationConfig {
39
+ rotationInterval: number // milliseconds
40
+ maxKeyAge: number // milliseconds
41
+ keyRetentionCount: number // number of old keys to keep
42
+ }
43
+
44
+ export interface CompressionConfig {
45
+ enabled: boolean
46
+ threshold: number // bytes - compress if state is larger than this
47
+ level: number // compression level 1-9
48
+ }
49
+
50
+ export class StateSignature {
51
+ private static instance: StateSignature
52
+ private currentKey: string
53
+ private keyHistory: Map<string, { key: string; createdAt: number }> = new Map()
54
+ private readonly maxAge = 24 * 60 * 60 * 1000 // 24 hours default
55
+ private keyRotationConfig: KeyRotationConfig
56
+ private compressionConfig: CompressionConfig
57
+ private backups = new Map<string, StateBackup[]>() // componentId -> backups
58
+ private migrationFunctions = new Map<string, (state: any) => any>() // version -> migration function
59
+
60
+ constructor(secretKey?: string, options?: {
61
+ keyRotation?: Partial<KeyRotationConfig>
62
+ compression?: Partial<CompressionConfig>
63
+ }) {
64
+ this.currentKey = secretKey || this.generateSecretKey()
65
+ this.keyHistory.set(this.getCurrentKeyId(), {
66
+ key: this.currentKey,
67
+ createdAt: Date.now()
68
+ })
69
+
70
+ this.keyRotationConfig = {
71
+ rotationInterval: 7 * 24 * 60 * 60 * 1000, // 7 days
72
+ maxKeyAge: 30 * 24 * 60 * 60 * 1000, // 30 days
73
+ keyRetentionCount: 5,
74
+ ...options?.keyRotation
75
+ }
76
+
77
+ this.compressionConfig = {
78
+ enabled: true,
79
+ threshold: 1024, // 1KB
80
+ level: 6,
81
+ ...options?.compression
82
+ }
83
+
84
+ this.setupKeyRotation()
85
+ }
86
+
87
+ public static getInstance(secretKey?: string, options?: {
88
+ keyRotation?: Partial<KeyRotationConfig>
89
+ compression?: Partial<CompressionConfig>
90
+ }): StateSignature {
91
+ if (!StateSignature.instance) {
92
+ StateSignature.instance = new StateSignature(secretKey, options)
93
+ }
94
+ return StateSignature.instance
95
+ }
96
+
97
+ private generateSecretKey(): string {
98
+ return randomBytes(32).toString('hex')
99
+ }
100
+
101
+ private getCurrentKeyId(): string {
102
+ return createHmac('sha256', this.currentKey).update('keyid').digest('hex').substring(0, 8)
103
+ }
104
+
105
+ private setupKeyRotation(): void {
106
+ // Rotate keys periodically
107
+ setInterval(() => {
108
+ this.rotateKey()
109
+ }, this.keyRotationConfig.rotationInterval)
110
+
111
+ // Cleanup old keys
112
+ setInterval(() => {
113
+ this.cleanupOldKeys()
114
+ }, 24 * 60 * 60 * 1000) // Daily cleanup
115
+ }
116
+
117
+ private rotateKey(): void {
118
+ const oldKeyId = this.getCurrentKeyId()
119
+ this.currentKey = this.generateSecretKey()
120
+ const newKeyId = this.getCurrentKeyId()
121
+
122
+ this.keyHistory.set(newKeyId, {
123
+ key: this.currentKey,
124
+ createdAt: Date.now()
125
+ })
126
+
127
+ console.log(`🔄 Key rotated from ${oldKeyId} to ${newKeyId}`)
128
+ }
129
+
130
+ private cleanupOldKeys(): void {
131
+ const now = Date.now()
132
+ const keysToDelete: string[] = []
133
+
134
+ for (const [keyId, keyData] of this.keyHistory) {
135
+ const keyAge = now - keyData.createdAt
136
+ if (keyAge > this.keyRotationConfig.maxKeyAge) {
137
+ keysToDelete.push(keyId)
138
+ }
139
+ }
140
+
141
+ // Keep at least the retention count of keys
142
+ const sortedKeys = Array.from(this.keyHistory.entries())
143
+ .sort((a, b) => b[1].createdAt - a[1].createdAt)
144
+
145
+ if (sortedKeys.length > this.keyRotationConfig.keyRetentionCount) {
146
+ const excessKeys = sortedKeys.slice(this.keyRotationConfig.keyRetentionCount)
147
+ for (const [keyId] of excessKeys) {
148
+ keysToDelete.push(keyId)
149
+ }
150
+ }
151
+
152
+ for (const keyId of keysToDelete) {
153
+ this.keyHistory.delete(keyId)
154
+ }
155
+
156
+ if (keysToDelete.length > 0) {
157
+ console.log(`🧹 Cleaned up ${keysToDelete.length} old keys`)
158
+ }
159
+ }
160
+
161
+ private getKeyById(keyId: string): string | null {
162
+ const keyData = this.keyHistory.get(keyId)
163
+ return keyData ? keyData.key : null
164
+ }
165
+
166
+ /**
167
+ * Sign component state with enhanced security, compression, and encryption
168
+ */
169
+ public async signState<T>(
170
+ componentId: string,
171
+ data: T,
172
+ version: number = 1,
173
+ options?: {
174
+ compress?: boolean
175
+ encrypt?: boolean
176
+ backup?: boolean
177
+ }
178
+ ): Promise<SignedState<T>> {
179
+ const timestamp = Date.now()
180
+ const keyId = this.getCurrentKeyId()
181
+
182
+ let processedData = data
183
+ let compressed = false
184
+ let encrypted = false
185
+
186
+ try {
187
+ // Serialize data for processing
188
+ const serializedData = JSON.stringify(data)
189
+
190
+ // Compress if enabled and data is large enough
191
+ if (this.compressionConfig.enabled &&
192
+ (options?.compress !== false) &&
193
+ Buffer.byteLength(serializedData, 'utf8') > this.compressionConfig.threshold) {
194
+
195
+ const compressedBuffer = await gzipAsync(Buffer.from(serializedData, 'utf8'))
196
+ processedData = compressedBuffer.toString('base64') as any
197
+ compressed = true
198
+
199
+ console.log(`🗜️ State compressed: ${Buffer.byteLength(serializedData, 'utf8')} -> ${compressedBuffer.length} bytes`)
200
+ }
201
+
202
+ // Encrypt sensitive data if requested
203
+ if (options?.encrypt) {
204
+ const encryptedData = await this.encryptData(processedData)
205
+ processedData = encryptedData as any
206
+ encrypted = true
207
+
208
+ console.log('🔒 State encrypted for component:', componentId)
209
+ }
210
+
211
+ // Create payload for signing
212
+ const payload = {
213
+ data: processedData,
214
+ componentId,
215
+ timestamp,
216
+ version,
217
+ keyId,
218
+ compressed,
219
+ encrypted
220
+ }
221
+
222
+ // Generate signature with current key
223
+ const signature = this.createSignature(payload)
224
+
225
+ // Create backup if requested
226
+ if (options?.backup) {
227
+ await this.createStateBackup(componentId, data, version)
228
+ }
229
+
230
+ console.log('🔐 State signed:', {
231
+ componentId,
232
+ timestamp,
233
+ version,
234
+ keyId,
235
+ compressed,
236
+ encrypted,
237
+ signature: signature.substring(0, 16) + '...'
238
+ })
239
+
240
+ return {
241
+ data: processedData,
242
+ signature,
243
+ timestamp,
244
+ componentId,
245
+ version,
246
+ keyId,
247
+ compressed,
248
+ encrypted
249
+ }
250
+
251
+ } catch (error) {
252
+ console.error('❌ Failed to sign state:', error)
253
+ throw new Error(`State signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Validate signed state integrity with enhanced security checks
259
+ */
260
+ public async validateState<T>(signedState: SignedState<T>, maxAge?: number): Promise<StateValidationResult> {
261
+ const { data, signature, timestamp, componentId, version, keyId, compressed, encrypted } = signedState
262
+
263
+ try {
264
+ // Check timestamp (prevent replay attacks)
265
+ const age = Date.now() - timestamp
266
+ const ageLimit = maxAge || this.maxAge
267
+
268
+ if (age > ageLimit) {
269
+ return {
270
+ valid: false,
271
+ error: 'State signature expired',
272
+ expired: true
273
+ }
274
+ }
275
+
276
+ // Determine which key to use for validation
277
+ let validationKey = this.currentKey
278
+ let keyRotated = false
279
+
280
+ if (keyId) {
281
+ const historicalKey = this.getKeyById(keyId)
282
+ if (historicalKey) {
283
+ validationKey = historicalKey
284
+ keyRotated = keyId !== this.getCurrentKeyId()
285
+ } else {
286
+ return {
287
+ valid: false,
288
+ error: 'Signing key not found or expired',
289
+ keyRotated: true
290
+ }
291
+ }
292
+ }
293
+
294
+ // Recreate payload for verification
295
+ const payload = {
296
+ data,
297
+ componentId,
298
+ timestamp,
299
+ version,
300
+ keyId,
301
+ compressed,
302
+ encrypted
303
+ }
304
+
305
+ // Verify signature with appropriate key
306
+ const expectedSignature = this.createSignature(payload, validationKey)
307
+
308
+ if (!this.constantTimeEquals(signature, expectedSignature)) {
309
+ console.warn('⚠️ State signature mismatch:', {
310
+ componentId,
311
+ expected: expectedSignature.substring(0, 16) + '...',
312
+ received: signature.substring(0, 16) + '...'
313
+ })
314
+
315
+ return {
316
+ valid: false,
317
+ error: 'State signature invalid - possible tampering',
318
+ tampered: true
319
+ }
320
+ }
321
+
322
+ console.log('✅ State signature valid:', {
323
+ componentId,
324
+ age: `${Math.round(age / 1000)}s`,
325
+ version
326
+ })
327
+
328
+ return { valid: true }
329
+
330
+ } catch (error: any) {
331
+ return {
332
+ valid: false,
333
+ error: `Validation error: ${error.message}`
334
+ }
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Create HMAC signature for payload using specified key
340
+ */
341
+ private createSignature(payload: any, key?: string): string {
342
+ // Stringify deterministically (sorted keys)
343
+ const normalizedPayload = JSON.stringify(payload, Object.keys(payload).sort())
344
+
345
+ return createHmac('sha256', key || this.currentKey)
346
+ .update(normalizedPayload)
347
+ .digest('hex')
348
+ }
349
+
350
+ /**
351
+ * Encrypt sensitive data
352
+ */
353
+ private async encryptData<T>(data: T): Promise<string> {
354
+ try {
355
+ const serializedData = JSON.stringify(data)
356
+ const key = await scryptAsync(this.currentKey, 'salt', 32) as Buffer
357
+ const iv = randomBytes(16)
358
+ const cipher = createCipheriv('aes-256-cbc', key, iv)
359
+
360
+ let encrypted = cipher.update(serializedData, 'utf8', 'hex')
361
+ encrypted += cipher.final('hex')
362
+
363
+ return iv.toString('hex') + ':' + encrypted
364
+ } catch (error) {
365
+ throw new Error(`Encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Decrypt sensitive data
371
+ */
372
+ private async decryptData(encryptedData: string, key?: string): Promise<any> {
373
+ try {
374
+ const [ivHex, encrypted] = encryptedData.split(':')
375
+ const iv = Buffer.from(ivHex, 'hex')
376
+ const derivedKey = await scryptAsync(key || this.currentKey, 'salt', 32) as Buffer
377
+ const decipher = createDecipheriv('aes-256-cbc', derivedKey, iv)
378
+
379
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8')
380
+ decrypted += decipher.final('utf8')
381
+
382
+ return JSON.parse(decrypted)
383
+ } catch (error) {
384
+ throw new Error(`Decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Decompress state data
390
+ */
391
+ private async decompressData(compressedData: string): Promise<any> {
392
+ try {
393
+ const compressedBuffer = Buffer.from(compressedData, 'base64')
394
+ const decompressedBuffer = await gunzipAsync(compressedBuffer)
395
+ return JSON.parse(decompressedBuffer.toString('utf8'))
396
+ } catch (error) {
397
+ throw new Error(`Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Create state backup
403
+ */
404
+ private async createStateBackup<T>(componentId: string, state: T, version: number): Promise<void> {
405
+ try {
406
+ const backup: StateBackup<T> = {
407
+ componentId,
408
+ state,
409
+ timestamp: Date.now(),
410
+ version,
411
+ checksum: createHmac('sha256', this.currentKey).update(JSON.stringify(state)).digest('hex')
412
+ }
413
+
414
+ let backups = this.backups.get(componentId) || []
415
+ backups.push(backup)
416
+
417
+ // Keep only last 10 backups per component
418
+ if (backups.length > 10) {
419
+ backups = backups.slice(-10)
420
+ }
421
+
422
+ this.backups.set(componentId, backups)
423
+
424
+ console.log(`💾 State backup created for component ${componentId} v${version}`)
425
+ } catch (error) {
426
+ console.error(`❌ Failed to create backup for component ${componentId}:`, error)
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Constant-time string comparison to prevent timing attacks
432
+ */
433
+ private constantTimeEquals(a: string, b: string): boolean {
434
+ if (a.length !== b.length) {
435
+ return false
436
+ }
437
+
438
+ let result = 0
439
+ for (let i = 0; i < a.length; i++) {
440
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i)
441
+ }
442
+
443
+ return result === 0
444
+ }
445
+
446
+ /**
447
+ * Extract and process signed state data (decompression, decryption)
448
+ */
449
+ public async extractData<T>(signedState: SignedState<T>): Promise<T> {
450
+ let data = signedState.data
451
+
452
+ try {
453
+ // Decrypt if encrypted
454
+ if (signedState.encrypted) {
455
+ const keyToUse = signedState.keyId ? this.getKeyById(signedState.keyId) : this.currentKey
456
+ if (!keyToUse) {
457
+ throw new Error('Decryption key not available')
458
+ }
459
+ data = await this.decryptData(data as string, keyToUse)
460
+ }
461
+
462
+ // Decompress if compressed
463
+ if (signedState.compressed) {
464
+ data = await this.decompressData(data as string)
465
+ }
466
+
467
+ return data
468
+ } catch (error) {
469
+ console.error('❌ Failed to extract state data:', error)
470
+ throw error
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Update signature for new state version with enhanced options
476
+ */
477
+ public async updateSignature<T>(
478
+ signedState: SignedState<T>,
479
+ newData: T,
480
+ options?: {
481
+ compress?: boolean
482
+ encrypt?: boolean
483
+ backup?: boolean
484
+ }
485
+ ): Promise<SignedState<T>> {
486
+ return this.signState(
487
+ signedState.componentId,
488
+ newData,
489
+ signedState.version + 1,
490
+ options
491
+ )
492
+ }
493
+
494
+ /**
495
+ * Register state migration function
496
+ */
497
+ public registerMigration(fromVersion: string, toVersion: string, migrationFn: (state: any) => any): void {
498
+ const key = `${fromVersion}->${toVersion}`
499
+ this.migrationFunctions.set(key, migrationFn)
500
+ console.log(`📋 Registered migration: ${key}`)
501
+ }
502
+
503
+ /**
504
+ * Migrate state to new version
505
+ */
506
+ public async migrateState<T>(signedState: SignedState<T>, targetVersion: string): Promise<SignedState<T> | null> {
507
+ const currentVersion = signedState.version.toString()
508
+ const migrationKey = `${currentVersion}->${targetVersion}`
509
+
510
+ const migrationFn = this.migrationFunctions.get(migrationKey)
511
+ if (!migrationFn) {
512
+ console.warn(`⚠️ No migration function found for ${migrationKey}`)
513
+ return null
514
+ }
515
+
516
+ try {
517
+ // Extract current data
518
+ const currentData = await this.extractData(signedState)
519
+
520
+ // Apply migration
521
+ const migratedData = migrationFn(currentData)
522
+
523
+ // Create new signed state
524
+ const newSignedState = await this.signState(
525
+ signedState.componentId,
526
+ migratedData,
527
+ parseInt(targetVersion),
528
+ {
529
+ compress: signedState.compressed,
530
+ encrypt: signedState.encrypted,
531
+ backup: true
532
+ }
533
+ )
534
+
535
+ console.log(`✅ State migrated from v${currentVersion} to v${targetVersion} for component ${signedState.componentId}`)
536
+ return newSignedState
537
+
538
+ } catch (error) {
539
+ console.error(`❌ State migration failed for ${migrationKey}:`, error)
540
+ return null
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Recover state from backup
546
+ */
547
+ public recoverStateFromBackup<T>(componentId: string, version?: number): StateBackup<T> | null {
548
+ const backups = this.backups.get(componentId)
549
+ if (!backups || backups.length === 0) {
550
+ return null
551
+ }
552
+
553
+ if (version !== undefined) {
554
+ // Find specific version
555
+ return backups.find(backup => backup.version === version) || null
556
+ } else {
557
+ // Return latest backup
558
+ return backups[backups.length - 1] || null
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Get all backups for a component
564
+ */
565
+ public getComponentBackups(componentId: string): StateBackup[] {
566
+ return this.backups.get(componentId) || []
567
+ }
568
+
569
+ /**
570
+ * Verify backup integrity
571
+ */
572
+ public verifyBackup<T>(backup: StateBackup<T>): boolean {
573
+ try {
574
+ const expectedChecksum = createHmac('sha256', this.currentKey)
575
+ .update(JSON.stringify(backup.state))
576
+ .digest('hex')
577
+
578
+ return this.constantTimeEquals(backup.checksum, expectedChecksum)
579
+ } catch {
580
+ return false
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Clean up old backups
586
+ */
587
+ public cleanupBackups(maxAge: number = 7 * 24 * 60 * 60 * 1000): void {
588
+ const now = Date.now()
589
+ let totalCleaned = 0
590
+
591
+ for (const [componentId, backups] of this.backups) {
592
+ const validBackups = backups.filter(backup => {
593
+ const age = now - backup.timestamp
594
+ return age <= maxAge
595
+ })
596
+
597
+ const cleaned = backups.length - validBackups.length
598
+ totalCleaned += cleaned
599
+
600
+ if (validBackups.length === 0) {
601
+ this.backups.delete(componentId)
602
+ } else {
603
+ this.backups.set(componentId, validBackups)
604
+ }
605
+ }
606
+
607
+ if (totalCleaned > 0) {
608
+ console.log(`🧹 Cleaned up ${totalCleaned} old state backups`)
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Get server's signature info for debugging
614
+ */
615
+ public getSignatureInfo() {
616
+ return {
617
+ algorithm: 'HMAC-SHA256',
618
+ keyLength: this.currentKey.length,
619
+ maxAge: this.maxAge,
620
+ keyPreview: this.currentKey.substring(0, 8) + '...',
621
+ currentKeyId: this.getCurrentKeyId(),
622
+ keyHistoryCount: this.keyHistory.size,
623
+ compressionEnabled: this.compressionConfig.enabled,
624
+ rotationInterval: this.keyRotationConfig.rotationInterval
625
+ }
626
+ }
627
+ }
628
+
629
+ // Global instance with enhanced configuration
630
+ export const stateSignature = StateSignature.getInstance(
631
+ process.env.FLUXSTACK_STATE_SECRET || undefined,
632
+ {
633
+ keyRotation: {
634
+ rotationInterval: parseInt(process.env.FLUXSTACK_KEY_ROTATION_INTERVAL || '604800000'), // 7 days
635
+ maxKeyAge: parseInt(process.env.FLUXSTACK_MAX_KEY_AGE || '2592000000'), // 30 days
636
+ keyRetentionCount: parseInt(process.env.FLUXSTACK_KEY_RETENTION_COUNT || '5')
637
+ },
638
+ compression: {
639
+ enabled: process.env.FLUXSTACK_COMPRESSION_ENABLED !== 'false',
640
+ threshold: parseInt(process.env.FLUXSTACK_COMPRESSION_THRESHOLD || '1024'), // 1KB
641
+ level: parseInt(process.env.FLUXSTACK_COMPRESSION_LEVEL || '6')
642
+ }
643
+ }
644
644
  )