create-fluxstack 1.12.1 → 1.14.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 (116) hide show
  1. package/LLMD/INDEX.md +8 -1
  2. package/LLMD/agent.md +867 -0
  3. package/LLMD/config/environment-vars.md +30 -0
  4. package/LLMD/patterns/anti-patterns.md +100 -0
  5. package/LLMD/reference/routing.md +39 -39
  6. package/LLMD/resources/live-auth.md +465 -0
  7. package/LLMD/resources/live-components.md +168 -26
  8. package/LLMD/resources/live-logging.md +220 -0
  9. package/LLMD/resources/live-upload.md +59 -8
  10. package/LLMD/resources/rest-auth.md +290 -0
  11. package/README.md +520 -340
  12. package/app/client/index.html +2 -2
  13. package/app/client/public/favicon.svg +46 -0
  14. package/app/client/src/App.tsx +13 -1
  15. package/app/client/src/assets/fluxstack-static.svg +46 -0
  16. package/app/client/src/assets/fluxstack.svg +183 -0
  17. package/app/client/src/components/AppLayout.tsx +139 -9
  18. package/app/client/src/components/BackButton.tsx +13 -13
  19. package/app/client/src/components/DemoPage.tsx +4 -4
  20. package/app/client/src/live/AuthDemo.tsx +334 -0
  21. package/app/client/src/live/ChatDemo.tsx +2 -2
  22. package/app/client/src/live/CounterDemo.tsx +12 -12
  23. package/app/client/src/live/FormDemo.tsx +2 -2
  24. package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
  25. package/app/client/src/live/RoomChatDemo.tsx +24 -16
  26. package/app/client/src/main.tsx +13 -13
  27. package/app/client/src/pages/ApiTestPage.tsx +6 -6
  28. package/app/client/src/pages/HomePage.tsx +80 -52
  29. package/app/server/auth/AuthManager.ts +213 -0
  30. package/app/server/auth/DevAuthProvider.ts +66 -0
  31. package/app/server/auth/HashManager.ts +123 -0
  32. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  33. package/app/server/auth/RateLimiter.ts +106 -0
  34. package/app/server/auth/contracts.ts +192 -0
  35. package/app/server/auth/guards/SessionGuard.ts +167 -0
  36. package/app/server/auth/guards/TokenGuard.ts +202 -0
  37. package/app/server/auth/index.ts +174 -0
  38. package/app/server/auth/middleware.ts +163 -0
  39. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  40. package/app/server/auth/sessions/SessionManager.ts +164 -0
  41. package/app/server/cache/CacheManager.ts +81 -0
  42. package/app/server/cache/MemoryDriver.ts +112 -0
  43. package/app/server/cache/contracts.ts +49 -0
  44. package/app/server/cache/index.ts +42 -0
  45. package/app/server/index.ts +14 -0
  46. package/app/server/live/LiveAdminPanel.ts +174 -0
  47. package/app/server/live/LiveChat.ts +78 -77
  48. package/app/server/live/LiveCounter.ts +1 -0
  49. package/app/server/live/LiveForm.ts +1 -0
  50. package/app/server/live/LiveLocalCounter.ts +38 -32
  51. package/app/server/live/LiveProtectedChat.ts +151 -0
  52. package/app/server/live/LiveRoomChat.ts +1 -0
  53. package/app/server/live/LiveUpload.ts +1 -0
  54. package/app/server/live/register-components.ts +19 -19
  55. package/app/server/routes/auth.routes.ts +278 -0
  56. package/app/server/routes/index.ts +2 -0
  57. package/config/index.ts +8 -0
  58. package/config/system/auth.config.ts +49 -0
  59. package/config/system/runtime.config.ts +4 -0
  60. package/config/system/session.config.ts +33 -0
  61. package/core/build/optimizer.ts +235 -235
  62. package/core/client/LiveComponentsProvider.tsx +76 -5
  63. package/core/client/components/Live.tsx +17 -10
  64. package/core/client/components/LiveDebugger.tsx +1324 -0
  65. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  66. package/core/client/hooks/useLiveComponent.ts +58 -5
  67. package/core/client/hooks/useLiveDebugger.ts +392 -0
  68. package/core/client/index.ts +16 -1
  69. package/core/framework/server.ts +36 -4
  70. package/core/plugins/built-in/index.ts +134 -134
  71. package/core/plugins/built-in/live-components/commands/create-live-component.ts +19 -8
  72. package/core/plugins/built-in/monitoring/index.ts +10 -3
  73. package/core/plugins/built-in/vite/index.ts +151 -20
  74. package/core/plugins/config.ts +5 -4
  75. package/core/plugins/discovery.ts +11 -2
  76. package/core/plugins/manager.ts +11 -5
  77. package/core/plugins/module-resolver.ts +1 -1
  78. package/core/plugins/registry.ts +53 -25
  79. package/core/server/index.ts +15 -15
  80. package/core/server/live/ComponentRegistry.ts +134 -50
  81. package/core/server/live/FileUploadManager.ts +188 -24
  82. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  83. package/core/server/live/LiveDebugger.ts +462 -0
  84. package/core/server/live/LiveLogger.ts +144 -0
  85. package/core/server/live/LiveRoomManager.ts +22 -5
  86. package/core/server/live/StateSignature.ts +704 -643
  87. package/core/server/live/WebSocketConnectionManager.ts +11 -10
  88. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  89. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  90. package/core/server/live/auth/index.ts +19 -0
  91. package/core/server/live/auth/types.ts +179 -0
  92. package/core/server/live/auto-generated-components.ts +8 -2
  93. package/core/server/live/index.ts +16 -0
  94. package/core/server/live/websocket-plugin.ts +323 -22
  95. package/core/server/plugins/static-files-plugin.ts +179 -69
  96. package/core/templates/create-project.ts +0 -3
  97. package/core/types/build.ts +219 -219
  98. package/core/types/plugin.ts +107 -107
  99. package/core/types/types.ts +278 -22
  100. package/core/utils/index.ts +17 -17
  101. package/core/utils/logger/index.ts +5 -2
  102. package/core/utils/logger/startup-banner.ts +82 -82
  103. package/core/utils/version.ts +6 -6
  104. package/package.json +1 -8
  105. package/plugins/crypto-auth/index.ts +6 -0
  106. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  107. package/plugins/crypto-auth/server/index.ts +24 -21
  108. package/rest-tests/README.md +57 -0
  109. package/rest-tests/auth-token.http +113 -0
  110. package/rest-tests/auth.http +112 -0
  111. package/rest-tests/rooms-token.http +69 -0
  112. package/rest-tests/users-token.http +62 -0
  113. package/.dockerignore +0 -81
  114. package/Dockerfile +0 -70
  115. package/LIVE_COMPONENTS_REVIEW.md +0 -781
  116. package/app/client/src/assets/react.svg +0 -1
@@ -1,11 +1,13 @@
1
1
  // 🔥 Auto-generated Live Components Registration
2
2
  // This file is automatically generated during build time - DO NOT EDIT MANUALLY
3
- // Generated at: 2026-02-09T22:13:45.496Z
3
+ // Generated at: 2026-02-21T20:03:53.569Z
4
4
 
5
+ import { LiveAdminPanel } from "@app/server/live/LiveAdminPanel"
5
6
  import { LiveChat } from "@app/server/live/LiveChat"
6
7
  import { LiveCounter } from "@app/server/live/LiveCounter"
7
8
  import { LiveForm } from "@app/server/live/LiveForm"
8
9
  import { LiveLocalCounter } from "@app/server/live/LiveLocalCounter"
10
+ import { LiveProtectedChat } from "@app/server/live/LiveProtectedChat"
9
11
  import { LiveRoomChat } from "@app/server/live/LiveRoomChat"
10
12
  import { LiveUpload } from "@app/server/live/LiveUpload"
11
13
  import { componentRegistry } from "@core/server/live/ComponentRegistry"
@@ -14,14 +16,16 @@ import { componentRegistry } from "@core/server/live/ComponentRegistry"
14
16
  function registerAllComponents() {
15
17
  try {
16
18
  // Auto-generated component registrations
19
+ componentRegistry.registerComponentClass('LiveAdminPanel', LiveAdminPanel)
17
20
  componentRegistry.registerComponentClass('LiveChat', LiveChat)
18
21
  componentRegistry.registerComponentClass('LiveCounter', LiveCounter)
19
22
  componentRegistry.registerComponentClass('LiveForm', LiveForm)
20
23
  componentRegistry.registerComponentClass('LiveLocalCounter', LiveLocalCounter)
24
+ componentRegistry.registerComponentClass('LiveProtectedChat', LiveProtectedChat)
21
25
  componentRegistry.registerComponentClass('LiveRoomChat', LiveRoomChat)
22
26
  componentRegistry.registerComponentClass('LiveUpload', LiveUpload)
23
27
 
24
- console.log('📝 Live components registered successfully! (6 components)')
28
+ console.log('📝 Live components registered successfully! (8 components)')
25
29
  } catch (error) {
26
30
  console.warn('⚠️ Error registering components:', error)
27
31
  }
@@ -32,10 +36,12 @@ registerAllComponents()
32
36
 
33
37
  // Export all components to ensure they're included in the bundle
34
38
  export {
39
+ LiveAdminPanel,
35
40
  LiveChat,
36
41
  LiveCounter,
37
42
  LiveForm,
38
43
  LiveLocalCounter,
44
+ LiveProtectedChat,
39
45
  LiveRoomChat,
40
46
  LiveUpload
41
47
  }
@@ -12,3 +12,19 @@ export { connectionManager } from './WebSocketConnectionManager'
12
12
  export { fileUploadManager } from './FileUploadManager'
13
13
  export { stateSignature } from './StateSignature'
14
14
  export { performanceMonitor } from './LiveComponentPerformanceMonitor'
15
+ export { liveLog, liveWarn, registerComponentLogging, unregisterComponentLogging } from './LiveLogger'
16
+ export type { LiveLogCategory, LiveLogConfig } from './LiveLogger'
17
+
18
+ // 🔒 Auth system
19
+ export { liveAuthManager, LiveAuthManager } from './auth/LiveAuthManager'
20
+ export { AuthenticatedContext, AnonymousContext, ANONYMOUS_CONTEXT } from './auth/LiveAuthContext'
21
+ export type {
22
+ LiveAuthProvider,
23
+ LiveAuthCredentials,
24
+ LiveAuthUser,
25
+ LiveAuthContext,
26
+ LiveComponentAuth,
27
+ LiveActionAuth,
28
+ LiveActionAuthMap,
29
+ LiveAuthResult,
30
+ } from './auth/types'
@@ -5,10 +5,14 @@ import { fileUploadManager } from './FileUploadManager'
5
5
  import { connectionManager } from './WebSocketConnectionManager'
6
6
  import { performanceMonitor } from './LiveComponentPerformanceMonitor'
7
7
  import { liveRoomManager, type RoomMessage } from './LiveRoomManager'
8
+ import { liveAuthManager } from './auth/LiveAuthManager'
9
+ import { ANONYMOUS_CONTEXT } from './auth/LiveAuthContext'
8
10
  import type { LiveMessage, FileUploadStartMessage, FileUploadChunkMessage, FileUploadCompleteMessage, BinaryChunkHeader, FluxStackWebSocket, FluxStackWSData } from '@core/types/types'
9
11
  import type { Plugin, PluginContext } from '@core/index'
10
12
  import { t, Elysia } from 'elysia'
11
13
  import path from 'path'
14
+ import { liveLog } from './LiveLogger'
15
+ import { liveDebugger } from './LiveDebugger'
12
16
 
13
17
  // ===== Response Schemas for Live Components Routes =====
14
18
 
@@ -113,6 +117,39 @@ const LiveAlertResolveSchema = t.Object({
113
117
  description: 'Result of alert resolution operation'
114
118
  })
115
119
 
120
+ // 🔒 Per-connection rate limiter to prevent WebSocket message flooding
121
+ class ConnectionRateLimiter {
122
+ private tokens: number
123
+ private lastRefill: number
124
+ private readonly maxTokens: number
125
+ private readonly refillRate: number // tokens per second
126
+
127
+ constructor(maxTokens = 100, refillRate = 50) {
128
+ this.maxTokens = maxTokens
129
+ this.tokens = maxTokens
130
+ this.refillRate = refillRate
131
+ this.lastRefill = Date.now()
132
+ }
133
+
134
+ tryConsume(count = 1): boolean {
135
+ this.refill()
136
+ if (this.tokens >= count) {
137
+ this.tokens -= count
138
+ return true
139
+ }
140
+ return false
141
+ }
142
+
143
+ private refill(): void {
144
+ const now = Date.now()
145
+ const elapsed = (now - this.lastRefill) / 1000
146
+ this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate)
147
+ this.lastRefill = now
148
+ }
149
+ }
150
+
151
+ const connectionRateLimiters = new Map<string, ConnectionRateLimiter>()
152
+
116
153
  export const liveComponentsPlugin: Plugin = {
117
154
  name: 'live-components',
118
155
  version: '1.0.0',
@@ -138,10 +175,13 @@ export const liveComponentsPlugin: Plugin = {
138
175
  // Binary messages will be ArrayBuffer/Uint8Array, JSON will be parsed objects
139
176
  body: t.Any(),
140
177
 
141
- open(ws) {
178
+ async open(ws) {
142
179
  const socket = ws as unknown as FluxStackWebSocket
143
- const connectionId = `ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
144
- console.log(`🔌 Live Components WebSocket connected: ${connectionId}`)
180
+ const connectionId = `ws-${crypto.randomUUID()}`
181
+ liveLog('websocket', null, `🔌 Live Components WebSocket connected: ${connectionId}`)
182
+
183
+ // 🔒 Initialize rate limiter for this connection
184
+ connectionRateLimiters.set(connectionId, new ConnectionRateLimiter())
145
185
 
146
186
  // Register connection with enhanced connection manager
147
187
  connectionManager.registerConnection(ws as unknown as FluxStackWebSocket, connectionId, 'live-components')
@@ -164,16 +204,42 @@ export const liveComponentsPlugin: Plugin = {
164
204
  socket.data.connectedAt = new Date()
165
205
  }
166
206
 
207
+ // 🔒 Try to authenticate from query params (token=xxx)
208
+ try {
209
+ const query = (ws as any).data?.query as Record<string, string> | undefined
210
+ const token = query?.token
211
+ if (token && liveAuthManager.hasProviders()) {
212
+ const authContext = await liveAuthManager.authenticate({ token })
213
+ socket.data.authContext = authContext
214
+ if (authContext.authenticated) {
215
+ socket.data.userId = authContext.user?.id
216
+ liveLog('websocket', null, `🔒 WebSocket authenticated via query: user=${authContext.user?.id}`)
217
+ } else {
218
+ // 🔒 Log failed auth attempts (token was provided but auth failed)
219
+ liveLog('websocket', null, `🔒 WebSocket authentication failed via query token`)
220
+ }
221
+ }
222
+ } catch (authError) {
223
+ // 🔒 Log auth errors instead of silently ignoring them
224
+ console.warn('🔒 WebSocket query auth error:', authError instanceof Error ? authError.message : 'Unknown error')
225
+ }
226
+
227
+ // Debug: track connection
228
+ liveDebugger.trackConnection(connectionId)
229
+
167
230
  // Send connection confirmation
168
231
  ws.send(JSON.stringify({
169
232
  type: 'CONNECTION_ESTABLISHED',
170
233
  connectionId,
171
234
  timestamp: Date.now(),
235
+ authenticated: socket.data.authContext?.authenticated ?? false,
236
+ userId: socket.data.authContext?.user?.id,
172
237
  features: {
173
238
  compression: true,
174
239
  encryption: true,
175
240
  offlineQueue: true,
176
- loadBalancing: true
241
+ loadBalancing: true,
242
+ auth: liveAuthManager.hasProviders()
177
243
  }
178
244
  }))
179
245
  },
@@ -181,6 +247,20 @@ export const liveComponentsPlugin: Plugin = {
181
247
  async message(ws: unknown, rawMessage: LiveMessage | ArrayBuffer | Uint8Array) {
182
248
  const socket = ws as FluxStackWebSocket
183
249
  try {
250
+ // 🔒 Rate limiting: reject messages if connection exceeds rate limit
251
+ const connId = socket.data?.connectionId
252
+ if (connId) {
253
+ const limiter = connectionRateLimiters.get(connId)
254
+ if (limiter && !limiter.tryConsume()) {
255
+ socket.send(JSON.stringify({
256
+ type: 'ERROR',
257
+ error: 'Rate limit exceeded. Please slow down.',
258
+ timestamp: Date.now()
259
+ }))
260
+ return
261
+ }
262
+ }
263
+
184
264
  let message: LiveMessage
185
265
  let binaryChunkData: Buffer | null = null
186
266
 
@@ -201,7 +281,7 @@ export const liveComponentsPlugin: Plugin = {
201
281
  // Extract binary chunk data
202
282
  binaryChunkData = buffer.slice(4 + headerLength)
203
283
 
204
- console.log(`📦 Binary chunk received: ${binaryChunkData.length} bytes for upload ${header.uploadId}`)
284
+ liveLog('messages', null, `📦 Binary chunk received: ${binaryChunkData.length} bytes for upload ${header.uploadId}`)
205
285
 
206
286
  // Create message with binary data attached
207
287
  message = {
@@ -215,7 +295,7 @@ export const liveComponentsPlugin: Plugin = {
215
295
  message.timestamp = Date.now()
216
296
  }
217
297
 
218
- console.log(`📨 Received message:`, {
298
+ liveLog('messages', message.componentId || null, `📨 Received message:`, {
219
299
  type: message.type,
220
300
  componentId: message.componentId,
221
301
  action: message.action,
@@ -243,6 +323,9 @@ export const liveComponentsPlugin: Plugin = {
243
323
  case 'COMPONENT_PING':
244
324
  await handleComponentPing(socket, message)
245
325
  break
326
+ case 'AUTH':
327
+ await handleAuth(socket, message)
328
+ break
246
329
  case 'FILE_UPLOAD_START':
247
330
  await handleFileUploadStart(socket, message as FileUploadStartMessage)
248
331
  break
@@ -286,7 +369,18 @@ export const liveComponentsPlugin: Plugin = {
286
369
  close(ws) {
287
370
  const socket = ws as unknown as FluxStackWebSocket
288
371
  const connectionId = socket.data?.connectionId
289
- console.log(`🔌 Live Components WebSocket disconnected: ${connectionId}`)
372
+ liveLog('websocket', null, `🔌 Live Components WebSocket disconnected: ${connectionId}`)
373
+
374
+ // Debug: track disconnection
375
+ const componentCount = socket.data?.components?.size ?? 0
376
+ if (connectionId) {
377
+ liveDebugger.trackDisconnection(connectionId, componentCount)
378
+ }
379
+
380
+ // 🔒 Cleanup rate limiter
381
+ if (connectionId) {
382
+ connectionRateLimiters.delete(connectionId)
383
+ }
290
384
 
291
385
  // Cleanup connection in connection manager
292
386
  if (connectionId) {
@@ -486,6 +580,132 @@ export const liveComponentsPlugin: Plugin = {
486
580
  response: LiveAlertResolveSchema
487
581
  })
488
582
 
583
+ // ===== Live Component Debugger Routes =====
584
+
585
+ // Debug WebSocket - streams debug events in real-time
586
+ .ws('/debug/ws', {
587
+ body: t.Any(),
588
+
589
+ open(ws) {
590
+ const socket = ws as unknown as FluxStackWebSocket
591
+ liveLog('websocket', null, '🔍 Debug client connected')
592
+ liveDebugger.registerDebugClient(socket)
593
+ },
594
+
595
+ message() {
596
+ // Debug clients are read-only, no incoming messages to handle
597
+ },
598
+
599
+ close(ws) {
600
+ const socket = ws as unknown as FluxStackWebSocket
601
+ liveLog('websocket', null, '🔍 Debug client disconnected')
602
+ liveDebugger.unregisterDebugClient(socket)
603
+ }
604
+ })
605
+
606
+ // Debug snapshot - current state of all components
607
+ .get('/debug/snapshot', () => {
608
+ return {
609
+ success: true,
610
+ snapshot: liveDebugger.getSnapshot(),
611
+ timestamp: new Date().toISOString()
612
+ }
613
+ }, {
614
+ detail: {
615
+ summary: 'Debug Snapshot',
616
+ description: 'Returns current state of all active Live Components for debugging',
617
+ tags: ['Live Components', 'Debug']
618
+ }
619
+ })
620
+
621
+ // Debug events - recent event history
622
+ .get('/debug/events', ({ query }) => {
623
+ const filter: { componentId?: string; type?: any; limit?: number } = {}
624
+ if (query.componentId) filter.componentId = query.componentId as string
625
+ if (query.type) filter.type = query.type
626
+ if (query.limit) filter.limit = parseInt(query.limit as string, 10)
627
+
628
+ return {
629
+ success: true,
630
+ events: liveDebugger.getEvents(filter),
631
+ timestamp: new Date().toISOString()
632
+ }
633
+ }, {
634
+ detail: {
635
+ summary: 'Debug Events',
636
+ description: 'Returns recent debug events, optionally filtered by component or type',
637
+ tags: ['Live Components', 'Debug']
638
+ },
639
+ query: t.Object({
640
+ componentId: t.Optional(t.String()),
641
+ type: t.Optional(t.String()),
642
+ limit: t.Optional(t.String())
643
+ })
644
+ })
645
+
646
+ // Debug toggle - enable/disable debugger at runtime
647
+ .post('/debug/toggle', ({ body }) => {
648
+ const enabled = (body as any)?.enabled
649
+ if (typeof enabled === 'boolean') {
650
+ liveDebugger.enabled = enabled
651
+ } else {
652
+ liveDebugger.enabled = !liveDebugger.enabled
653
+ }
654
+ return {
655
+ success: true,
656
+ enabled: liveDebugger.enabled,
657
+ timestamp: new Date().toISOString()
658
+ }
659
+ }, {
660
+ detail: {
661
+ summary: 'Toggle Debugger',
662
+ description: 'Enable or disable the Live Component debugger at runtime',
663
+ tags: ['Live Components', 'Debug']
664
+ }
665
+ })
666
+
667
+ // Debug component state - get specific component state
668
+ .get('/debug/components/:componentId', ({ params }) => {
669
+ const snapshot = liveDebugger.getComponentState(params.componentId)
670
+ if (!snapshot) {
671
+ return { success: false, error: 'Component not found' }
672
+ }
673
+ return {
674
+ success: true,
675
+ component: snapshot,
676
+ events: liveDebugger.getEvents({
677
+ componentId: params.componentId,
678
+ limit: 50
679
+ }),
680
+ timestamp: new Date().toISOString()
681
+ }
682
+ }, {
683
+ detail: {
684
+ summary: 'Debug Component State',
685
+ description: 'Returns current state and recent events for a specific component',
686
+ tags: ['Live Components', 'Debug']
687
+ },
688
+ params: t.Object({
689
+ componentId: t.String()
690
+ })
691
+ })
692
+
693
+ // Clear debug events
694
+ .post('/debug/clear', () => {
695
+ liveDebugger.clearEvents()
696
+ return {
697
+ success: true,
698
+ message: 'Debug events cleared',
699
+ timestamp: new Date().toISOString()
700
+ }
701
+ }, {
702
+ detail: {
703
+ summary: 'Clear Debug Events',
704
+ description: 'Clears the debug event history buffer',
705
+ tags: ['Live Components', 'Debug']
706
+ }
707
+ })
708
+
489
709
  // Register the grouped routes with the main app
490
710
  context.app.use(liveRoutes)
491
711
  },
@@ -514,7 +734,7 @@ async function handleComponentMount(ws: FluxStackWebSocket, message: LiveMessage
514
734
  }
515
735
 
516
736
  async function handleComponentRehydrate(ws: FluxStackWebSocket, message: LiveMessage) {
517
- console.log('🔄 Processing component re-hydration request:', {
737
+ liveLog('lifecycle', message.componentId, '🔄 Processing component re-hydration request:', {
518
738
  componentId: message.componentId,
519
739
  payload: message.payload
520
740
  })
@@ -547,7 +767,7 @@ async function handleComponentRehydrate(ws: FluxStackWebSocket, message: LiveMes
547
767
  timestamp: Date.now()
548
768
  }
549
769
 
550
- console.log('📤 Sending COMPONENT_REHYDRATED response:', {
770
+ liveLog('lifecycle', message.componentId, '📤 Sending COMPONENT_REHYDRATED response:', {
551
771
  type: response.type,
552
772
  success: response.success,
553
773
  newComponentId: response.result?.newComponentId,
@@ -638,12 +858,65 @@ async function handleComponentPing(ws: FluxStackWebSocket, message: LiveMessage)
638
858
  ws.send(JSON.stringify(response))
639
859
  }
640
860
 
861
+ // ===== Auth Handler =====
862
+
863
+ async function handleAuth(ws: FluxStackWebSocket, message: LiveMessage) {
864
+ liveLog('websocket', null, '🔒 Processing WebSocket authentication request')
865
+
866
+ try {
867
+ const credentials = message.payload || {}
868
+ const providerName = credentials.provider as string | undefined
869
+
870
+ if (!liveAuthManager.hasProviders()) {
871
+ ws.send(JSON.stringify({
872
+ type: 'AUTH_RESPONSE',
873
+ success: false,
874
+ error: 'No auth providers configured',
875
+ requestId: message.requestId,
876
+ timestamp: Date.now()
877
+ }))
878
+ return
879
+ }
880
+
881
+ const authContext = await liveAuthManager.authenticate(credentials, providerName)
882
+
883
+ // Store auth context on the WebSocket connection
884
+ ws.data.authContext = authContext
885
+
886
+ if (authContext.authenticated) {
887
+ ws.data.userId = authContext.user?.id
888
+ liveLog('websocket', null, `🔒 WebSocket authenticated: user=${authContext.user?.id}`)
889
+ }
890
+
891
+ ws.send(JSON.stringify({
892
+ type: 'AUTH_RESPONSE',
893
+ success: authContext.authenticated,
894
+ authenticated: authContext.authenticated,
895
+ userId: authContext.user?.id,
896
+ roles: authContext.user?.roles,
897
+ requestId: message.requestId,
898
+ timestamp: Date.now()
899
+ }))
900
+ } catch (error: any) {
901
+ console.error('🔒 WebSocket auth error:', error.message)
902
+ ws.send(JSON.stringify({
903
+ type: 'AUTH_RESPONSE',
904
+ success: false,
905
+ error: error.message,
906
+ requestId: message.requestId,
907
+ timestamp: Date.now()
908
+ }))
909
+ }
910
+ }
911
+
641
912
  // File Upload Handler Functions
642
913
  async function handleFileUploadStart(ws: FluxStackWebSocket, message: FileUploadStartMessage) {
643
- console.log('📤 Starting file upload:', message.uploadId)
644
-
645
- const result = await fileUploadManager.startUpload(message)
646
-
914
+ liveLog('messages', message.componentId || null, '📤 Starting file upload:', message.uploadId)
915
+
916
+ // 🔒 Pass userId for per-user upload quota enforcement
917
+ const userId = ws.data?.userId || ws.data?.authContext?.user?.id
918
+ const result = await fileUploadManager.startUpload(message, userId)
919
+
647
920
  const response = {
648
921
  type: 'FILE_UPLOAD_START_RESPONSE',
649
922
  componentId: message.componentId,
@@ -653,12 +926,12 @@ async function handleFileUploadStart(ws: FluxStackWebSocket, message: FileUpload
653
926
  requestId: message.requestId,
654
927
  timestamp: Date.now()
655
928
  }
656
-
929
+
657
930
  ws.send(JSON.stringify(response))
658
931
  }
659
932
 
660
933
  async function handleFileUploadChunk(ws: FluxStackWebSocket, message: FileUploadChunkMessage, binaryData: Buffer | null = null) {
661
- console.log(`📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}${binaryData ? ' (binary)' : ' (base64)'}`)
934
+ liveLog('messages', message.componentId || null, `📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}${binaryData ? ' (binary)' : ' (base64)'}`)
662
935
 
663
936
  const progressResponse = await fileUploadManager.receiveChunk(message, ws, binaryData)
664
937
 
@@ -686,7 +959,7 @@ async function handleFileUploadChunk(ws: FluxStackWebSocket, message: FileUpload
686
959
  }
687
960
 
688
961
  async function handleFileUploadComplete(ws: FluxStackWebSocket, message: FileUploadCompleteMessage) {
689
- console.log('✅ Completing file upload:', message.uploadId)
962
+ liveLog('messages', null, '✅ Completing file upload:', message.uploadId)
690
963
 
691
964
  const completeResponse = await fileUploadManager.completeUpload(message)
692
965
 
@@ -702,14 +975,26 @@ async function handleFileUploadComplete(ws: FluxStackWebSocket, message: FileUpl
702
975
  // ===== Room System Handlers =====
703
976
 
704
977
  async function handleRoomJoin(ws: FluxStackWebSocket, message: RoomMessage) {
705
- console.log(`🚪 Component ${message.componentId} joining room ${message.roomId}`)
978
+ liveLog('rooms', message.componentId, `🚪 Component ${message.componentId} joining room ${message.roomId}`)
706
979
 
707
980
  try {
981
+ // 🔒 Validate room name format (alphanumeric, hyphens, underscores, max 64 chars)
982
+ if (!message.roomId || !/^[a-zA-Z0-9_:.-]{1,64}$/.test(message.roomId)) {
983
+ throw new Error('Invalid room name. Must be 1-64 alphanumeric characters, hyphens, underscores, dots, or colons.')
984
+ }
985
+
986
+ // 🔒 Room authorization check
987
+ const authContext = ws.data?.authContext || ANONYMOUS_CONTEXT
988
+ const authResult = await liveAuthManager.authorizeRoom(authContext, message.roomId)
989
+ if (!authResult.allowed) {
990
+ throw new Error(`Room access denied: ${authResult.reason}`)
991
+ }
992
+
708
993
  const result = liveRoomManager.joinRoom(
709
994
  message.componentId,
710
995
  message.roomId,
711
996
  ws,
712
- message.data?.initialState
997
+ undefined // 🔒 Don't allow client to set initial room state - server controls this
713
998
  )
714
999
 
715
1000
  const response = {
@@ -736,7 +1021,7 @@ async function handleRoomJoin(ws: FluxStackWebSocket, message: RoomMessage) {
736
1021
  }
737
1022
 
738
1023
  async function handleRoomLeave(ws: FluxStackWebSocket, message: RoomMessage) {
739
- console.log(`🚶 Component ${message.componentId} leaving room ${message.roomId}`)
1024
+ liveLog('rooms', message.componentId, `🚶 Component ${message.componentId} leaving room ${message.roomId}`)
740
1025
 
741
1026
  try {
742
1027
  liveRoomManager.leaveRoom(message.componentId, message.roomId)
@@ -764,9 +1049,14 @@ async function handleRoomLeave(ws: FluxStackWebSocket, message: RoomMessage) {
764
1049
  }
765
1050
 
766
1051
  async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
767
- console.log(`📡 Component ${message.componentId} emitting '${message.event}' to room ${message.roomId}`)
1052
+ liveLog('rooms', message.componentId, `📡 Component ${message.componentId} emitting '${message.event}' to room ${message.roomId}`)
768
1053
 
769
1054
  try {
1055
+ // 🔒 Validate room name
1056
+ if (!message.roomId || !/^[a-zA-Z0-9_:.-]{1,64}$/.test(message.roomId)) {
1057
+ throw new Error('Invalid room name')
1058
+ }
1059
+
770
1060
  const count = liveRoomManager.emitToRoom(
771
1061
  message.roomId,
772
1062
  message.event!,
@@ -774,7 +1064,7 @@ async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
774
1064
  message.componentId // Excluir quem enviou
775
1065
  )
776
1066
 
777
- console.log(` → Notified ${count} components`)
1067
+ liveLog('rooms', message.componentId, ` → Notified ${count} components`)
778
1068
  } catch (error: any) {
779
1069
  ws.send(JSON.stringify({
780
1070
  type: 'ERROR',
@@ -787,9 +1077,20 @@ async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
787
1077
  }
788
1078
 
789
1079
  async function handleRoomStateSet(ws: FluxStackWebSocket, message: RoomMessage) {
790
- console.log(`📝 Component ${message.componentId} updating state in room ${message.roomId}`)
1080
+ liveLog('rooms', message.componentId, `📝 Component ${message.componentId} updating state in room ${message.roomId}`)
791
1081
 
792
1082
  try {
1083
+ // 🔒 Validate room name
1084
+ if (!message.roomId || !/^[a-zA-Z0-9_:.-]{1,64}$/.test(message.roomId)) {
1085
+ throw new Error('Invalid room name')
1086
+ }
1087
+
1088
+ // 🔒 Validate state size (max 1MB per update to prevent memory attacks)
1089
+ const stateStr = JSON.stringify(message.data ?? {})
1090
+ if (stateStr.length > 1024 * 1024) {
1091
+ throw new Error('Room state update too large (max 1MB)')
1092
+ }
1093
+
793
1094
  liveRoomManager.setRoomState(
794
1095
  message.roomId,
795
1096
  message.data ?? {},