create-fluxstack 1.13.0 → 1.15.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.
- package/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +20 -2
- package/LLMD/resources/live-components.md +300 -21
- package/LLMD/resources/live-logging.md +95 -33
- package/LLMD/resources/live-upload.md +59 -8
- package/app/client/.live-stubs/LiveAdminPanel.js +5 -0
- package/app/client/.live-stubs/LiveChat.js +7 -0
- package/app/client/.live-stubs/LiveCounter.js +9 -0
- package/app/client/.live-stubs/LiveForm.js +11 -0
- package/app/client/.live-stubs/LiveLocalCounter.js +8 -0
- package/app/client/.live-stubs/LiveRoomChat.js +10 -0
- package/app/client/.live-stubs/LiveTodoList.js +9 -0
- package/app/client/.live-stubs/LiveUpload.js +15 -0
- package/app/client/index.html +2 -2
- package/app/client/public/favicon.svg +46 -0
- package/app/client/src/App.tsx +13 -1
- package/app/client/src/assets/fluxstack-static.svg +46 -0
- package/app/client/src/assets/fluxstack.svg +183 -0
- package/app/client/src/components/AppLayout.tsx +146 -9
- package/app/client/src/components/BackButton.tsx +13 -13
- package/app/client/src/components/DemoPage.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +23 -21
- package/app/client/src/live/ChatDemo.tsx +2 -2
- package/app/client/src/live/CounterDemo.tsx +12 -12
- package/app/client/src/live/FormDemo.tsx +2 -2
- package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
- package/app/client/src/live/RoomChatDemo.tsx +24 -16
- package/app/client/src/live/TodoListDemo.tsx +158 -0
- package/app/client/src/main.tsx +13 -13
- package/app/client/src/pages/ApiTestPage.tsx +6 -6
- package/app/client/src/pages/HomePage.tsx +80 -52
- package/app/server/auth/DevAuthProvider.ts +2 -2
- package/app/server/auth/JWTAuthProvider.example.ts +2 -2
- package/app/server/index.ts +2 -2
- package/app/server/live/LiveAdminPanel.ts +2 -1
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -1
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -37
- package/app/server/live/LiveProtectedChat.ts +2 -1
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveTodoList.ts +110 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/app/server/routes/room.routes.ts +1 -2
- package/config/system/runtime.config.ts +4 -0
- package/core/build/live-components-generator.ts +1 -1
- package/core/build/optimizer.ts +235 -235
- package/core/build/vite-plugins.ts +28 -0
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/useLiveUpload.ts +3 -4
- package/core/client/index.ts +41 -21
- package/core/framework/server.ts +1 -1
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +4 -0
- package/core/plugins/built-in/vite/index.ts +75 -21
- package/core/server/index.ts +14 -15
- package/core/server/live/auto-generated-components.ts +6 -3
- package/core/server/live/index.ts +95 -21
- package/core/server/live/websocket-plugin.ts +27 -862
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +77 -890
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +1 -1
- package/package.json +5 -1
- package/plugins/crypto-auth/index.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +2 -2
- package/vite.config.ts +40 -12
- package/app/client/src/assets/react.svg +0 -1
- package/core/client/LiveComponentsProvider.tsx +0 -531
- package/core/client/components/Live.tsx +0 -105
- package/core/client/hooks/AdaptiveChunkSizer.ts +0 -215
- package/core/client/hooks/state-validator.ts +0 -130
- package/core/client/hooks/useChunkedUpload.ts +0 -359
- package/core/client/hooks/useLiveChunkedUpload.ts +0 -87
- package/core/client/hooks/useLiveComponent.ts +0 -843
- package/core/client/hooks/useRoom.ts +0 -409
- package/core/client/hooks/useRoomProxy.ts +0 -382
- package/core/server/live/ComponentRegistry.ts +0 -1099
- package/core/server/live/FileUploadManager.ts +0 -282
- package/core/server/live/LiveComponentPerformanceMonitor.ts +0 -931
- package/core/server/live/LiveLogger.ts +0 -111
- package/core/server/live/LiveRoomManager.ts +0 -262
- package/core/server/live/RoomEventBus.ts +0 -234
- package/core/server/live/RoomStateManager.ts +0 -172
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +0 -645
- package/core/server/live/WebSocketConnectionManager.ts +0 -709
- package/core/server/live/auth/LiveAuthContext.ts +0 -71
- package/core/server/live/auth/LiveAuthManager.ts +0 -304
- package/core/server/live/auth/index.ts +0 -19
- package/core/server/live/auth/types.ts +0 -179
|
@@ -277,6 +277,100 @@ if (import.meta.env.DEV) {
|
|
|
277
277
|
}
|
|
278
278
|
```
|
|
279
279
|
|
|
280
|
+
## Live Component Security Anti-Patterns
|
|
281
|
+
|
|
282
|
+
### Never Omit publicActions
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// ❌ WRONG - No publicActions = ALL remote actions blocked (secure by default)
|
|
286
|
+
export class MyComponent extends LiveComponent<State> {
|
|
287
|
+
static componentName = 'MyComponent'
|
|
288
|
+
static defaultState = { count: 0 }
|
|
289
|
+
|
|
290
|
+
async increment() { this.state.count++ } // Client CANNOT call this!
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ✅ CORRECT - Explicitly whitelist callable methods
|
|
294
|
+
export class MyComponent extends LiveComponent<State> {
|
|
295
|
+
static componentName = 'MyComponent'
|
|
296
|
+
static publicActions = ['increment'] as const // Only increment is callable
|
|
297
|
+
static defaultState = { count: 0 }
|
|
298
|
+
|
|
299
|
+
async increment() { this.state.count++ }
|
|
300
|
+
|
|
301
|
+
// Internal helper - not in publicActions, so not callable from client
|
|
302
|
+
private _recalculate() { /* ... */ }
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Why**: Components without `publicActions` deny ALL remote actions. This is secure by default - if you forget, nothing is exposed rather than everything.
|
|
307
|
+
|
|
308
|
+
### Never Include setValue Without Careful Consideration
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// ❌ DANGEROUS - setValue allows client to set ANY state key
|
|
312
|
+
static publicActions = ['sendMessage', 'setValue'] as const
|
|
313
|
+
// Client can now do: component.setValue({ key: 'isAdmin', value: true })
|
|
314
|
+
|
|
315
|
+
// ✅ CORRECT - Only expose specific, safe actions
|
|
316
|
+
static publicActions = ['sendMessage', 'deleteMessage'] as const
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Why**: `setValue` is a generic action that allows the client to modify any state property. Only include it if all state fields are safe for clients to modify.
|
|
320
|
+
|
|
321
|
+
### Never Trust MIME Types Alone for Uploads
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// ❌ WRONG - Only checking MIME type header (easily spoofed)
|
|
325
|
+
if (file.type === 'image/jpeg') {
|
|
326
|
+
// Accept file - but it could be an EXE with a fake MIME header!
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ✅ CORRECT - Framework validates magic bytes automatically
|
|
330
|
+
// FileUploadManager.validateContentMagicBytes() runs on completeUpload()
|
|
331
|
+
// No manual code needed - the framework handles this
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Why**: MIME types come from the client and can be spoofed. The framework validates actual file content (magic bytes) against the claimed type.
|
|
335
|
+
|
|
336
|
+
### Never Store Sensitive Data in State
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// ❌ WRONG - Token goes to the client via STATE_UPDATE/STATE_DELTA
|
|
340
|
+
export class Chat extends LiveComponent<State> {
|
|
341
|
+
static defaultState = { messages: [], token: '' } // token synced to client!
|
|
342
|
+
static publicActions = ['connect'] as const
|
|
343
|
+
|
|
344
|
+
async connect(payload: { token: string }) {
|
|
345
|
+
this.state.token = payload.token // 💀 Visible in browser DevTools!
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ✅ CORRECT - Use $private for server-only data
|
|
350
|
+
export class Chat extends LiveComponent<State> {
|
|
351
|
+
static defaultState = { messages: [] as string[] }
|
|
352
|
+
static publicActions = ['connect'] as const
|
|
353
|
+
|
|
354
|
+
async connect(payload: { token: string }) {
|
|
355
|
+
this.$private.token = payload.token // 🔒 Never leaves the server
|
|
356
|
+
this.state.messages = await fetch(this.$private.token)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Why**: Everything in `state` is serialized and sent to the client via WebSocket. Use `$private` for tokens, API keys, internal IDs, or any data the client should not see.
|
|
362
|
+
|
|
363
|
+
### Never Ignore Double Extensions
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// ❌ WRONG - Only checking last extension
|
|
367
|
+
const ext = filename.split('.').pop() // Returns 'jpg' for 'malware.exe.jpg'
|
|
368
|
+
|
|
369
|
+
// ✅ CORRECT - Framework checks all intermediate extensions automatically
|
|
370
|
+
// FileUploadManager blocks files like 'malware.exe.jpg'
|
|
371
|
+
// No manual code needed - handled at framework level
|
|
372
|
+
```
|
|
373
|
+
|
|
280
374
|
## Summary Table
|
|
281
375
|
|
|
282
376
|
| Anti-Pattern | Impact | Solution |
|
|
@@ -288,6 +382,10 @@ if (import.meta.env.DEV) {
|
|
|
288
382
|
| Deep relative imports | Fragile paths | Use aliases |
|
|
289
383
|
| NPM plugins without whitelist | Security risk | Set PLUGINS_ALLOWED |
|
|
290
384
|
| Business logic in routes | Unmaintainable | Use controllers |
|
|
385
|
+
| Missing `publicActions` | All actions blocked | Always define whitelist |
|
|
386
|
+
| Including `setValue` carelessly | Privilege escalation | Use specific actions |
|
|
387
|
+
| Sensitive data in `state` | Data leak to client | Use `$private` instead |
|
|
388
|
+
| Trusting MIME types alone | File disguise attacks | Framework validates magic bytes |
|
|
291
389
|
|
|
292
390
|
## Related
|
|
293
391
|
|
|
@@ -295,3 +393,5 @@ if (import.meta.env.DEV) {
|
|
|
295
393
|
- [Type Safety](./type-safety.md)
|
|
296
394
|
- [Plugin Security](../core/plugin-system.md)
|
|
297
395
|
- [Routes with Eden Treaty](../resources/routes-eden.md)
|
|
396
|
+
- [Live Components](../resources/live-components.md)
|
|
397
|
+
- [Live Upload](../resources/live-upload.md)
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
# Routing (React Router v7)
|
|
2
|
-
|
|
3
|
-
FluxStack uses **React Router v7** via the `react-router` package for web routing.
|
|
4
|
-
|
|
5
|
-
## Where It Lives
|
|
6
|
-
|
|
7
|
-
- Router provider: `app/client/src/main.tsx`
|
|
8
|
-
- Routes and pages: `app/client/src/App.tsx`
|
|
9
|
-
- Pages: `app/client/src/pages/*`
|
|
10
|
-
- Shared layout: `app/client/src/components/AppLayout.tsx`
|
|
11
|
-
|
|
12
|
-
## Why `react-router` (not `react-router-dom`)
|
|
13
|
-
|
|
14
|
-
In v7, the React Router team recommends using the core `react-router` package
|
|
15
|
-
directly for web apps. The `react-router-dom` package remains as a compatibility
|
|
16
|
-
re-export for older apps, but new projects should import from `react-router`.
|
|
17
|
-
|
|
18
|
-
## Example: Adding a New Route
|
|
19
|
-
|
|
20
|
-
1. Create a page in `app/client/src/pages/MyPage.tsx`
|
|
21
|
-
2. Add a route in `app/client/src/App.tsx`:
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import { MyPage } from './pages/MyPage'
|
|
25
|
-
|
|
26
|
-
<Route path="/my-page" element={<MyPage />} />
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
3. Add a nav link in `app/client/src/components/AppLayout.tsx`
|
|
30
|
-
|
|
31
|
-
## Current Demo Routes
|
|
32
|
-
|
|
33
|
-
- `/` Home
|
|
34
|
-
- `/counter` Live Counter
|
|
35
|
-
- `/form` Live Form
|
|
36
|
-
- `/upload` Live Upload
|
|
37
|
-
- `/chat` Live Chat
|
|
38
|
-
- `/api-test` Eden Treaty API Test
|
|
39
|
-
|
|
1
|
+
# Routing (React Router v7)
|
|
2
|
+
|
|
3
|
+
FluxStack uses **React Router v7** via the `react-router` package for web routing.
|
|
4
|
+
|
|
5
|
+
## Where It Lives
|
|
6
|
+
|
|
7
|
+
- Router provider: `app/client/src/main.tsx`
|
|
8
|
+
- Routes and pages: `app/client/src/App.tsx`
|
|
9
|
+
- Pages: `app/client/src/pages/*`
|
|
10
|
+
- Shared layout: `app/client/src/components/AppLayout.tsx`
|
|
11
|
+
|
|
12
|
+
## Why `react-router` (not `react-router-dom`)
|
|
13
|
+
|
|
14
|
+
In v7, the React Router team recommends using the core `react-router` package
|
|
15
|
+
directly for web apps. The `react-router-dom` package remains as a compatibility
|
|
16
|
+
re-export for older apps, but new projects should import from `react-router`.
|
|
17
|
+
|
|
18
|
+
## Example: Adding a New Route
|
|
19
|
+
|
|
20
|
+
1. Create a page in `app/client/src/pages/MyPage.tsx`
|
|
21
|
+
2. Add a route in `app/client/src/App.tsx`:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { MyPage } from './pages/MyPage'
|
|
25
|
+
|
|
26
|
+
<Route path="/my-page" element={<MyPage />} />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. Add a nav link in `app/client/src/components/AppLayout.tsx`
|
|
30
|
+
|
|
31
|
+
## Current Demo Routes
|
|
32
|
+
|
|
33
|
+
- `/` Home
|
|
34
|
+
- `/counter` Live Counter
|
|
35
|
+
- `/form` Live Form
|
|
36
|
+
- `/upload` Live Upload
|
|
37
|
+
- `/chat` Live Chat
|
|
38
|
+
- `/api-test` Eden Treaty API Test
|
|
39
|
+
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Quick Facts
|
|
6
6
|
|
|
7
|
+
- **`publicActions` is the foundation** - Only whitelisted methods can be called remotely
|
|
7
8
|
- Declarative auth configuration via `static auth` and `static actionAuth`
|
|
8
9
|
- Role-based access control (RBAC) with OR logic
|
|
9
10
|
- Permission-based access control with AND logic
|
|
@@ -22,6 +23,7 @@ import type { LiveComponentAuth } from '@core/server/live/auth/types'
|
|
|
22
23
|
|
|
23
24
|
export class ProtectedChat extends LiveComponent<typeof ProtectedChat.defaultState> {
|
|
24
25
|
static componentName = 'ProtectedChat'
|
|
26
|
+
static publicActions = ['sendMessage'] as const // 🔒 REQUIRED
|
|
25
27
|
static defaultState = {
|
|
26
28
|
messages: [] as string[]
|
|
27
29
|
}
|
|
@@ -47,6 +49,7 @@ import type { LiveComponentAuth } from '@core/server/live/auth/types'
|
|
|
47
49
|
|
|
48
50
|
export class AdminPanel extends LiveComponent<typeof AdminPanel.defaultState> {
|
|
49
51
|
static componentName = 'AdminPanel'
|
|
52
|
+
static publicActions = ['deleteUser'] as const // 🔒 REQUIRED
|
|
50
53
|
static defaultState = {
|
|
51
54
|
users: [] as { id: string; name: string; role: string }[]
|
|
52
55
|
}
|
|
@@ -74,6 +77,7 @@ import type { LiveComponentAuth } from '@core/server/live/auth/types'
|
|
|
74
77
|
|
|
75
78
|
export class ContentEditor extends LiveComponent<typeof ContentEditor.defaultState> {
|
|
76
79
|
static componentName = 'ContentEditor'
|
|
80
|
+
static publicActions = ['editContent', 'saveContent'] as const // 🔒 REQUIRED
|
|
77
81
|
static defaultState = {
|
|
78
82
|
content: ''
|
|
79
83
|
}
|
|
@@ -95,6 +99,7 @@ import type { LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/aut
|
|
|
95
99
|
|
|
96
100
|
export class ModerationPanel extends LiveComponent<typeof ModerationPanel.defaultState> {
|
|
97
101
|
static componentName = 'ModerationPanel'
|
|
102
|
+
static publicActions = ['getReports', 'deleteReport', 'banUser'] as const // 🔒 REQUIRED
|
|
98
103
|
static defaultState = {
|
|
99
104
|
reports: [] as any[]
|
|
100
105
|
}
|
|
@@ -104,7 +109,7 @@ export class ModerationPanel extends LiveComponent<typeof ModerationPanel.defaul
|
|
|
104
109
|
required: true
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
// Per-action auth
|
|
112
|
+
// Per-action auth (works together with publicActions)
|
|
108
113
|
static actionAuth: LiveActionAuthMap = {
|
|
109
114
|
deleteReport: { permissions: ['reports.delete'] },
|
|
110
115
|
banUser: { roles: ['admin', 'moderator'] }
|
|
@@ -382,7 +387,7 @@ For testing, a `DevAuthProvider` with simple tokens is available:
|
|
|
382
387
|
│ 1. WebSocket connect → store authContext on ws.data │
|
|
383
388
|
│ 2. AUTH message → liveAuthManager.authenticate() │
|
|
384
389
|
│ 3. COMPONENT_MOUNT → check static auth config │
|
|
385
|
-
│ 4. CALL_ACTION → check
|
|
390
|
+
│ 4. CALL_ACTION → check blocklist → publicActions → actionAuth │
|
|
386
391
|
│ 5. Component has access to this.$auth │
|
|
387
392
|
└─────────────────────────────────────────────────────────────┘
|
|
388
393
|
```
|
|
@@ -416,9 +421,21 @@ interface LiveAuthCredentials {
|
|
|
416
421
|
}
|
|
417
422
|
```
|
|
418
423
|
|
|
424
|
+
## Security Layers (Action Execution Order)
|
|
425
|
+
|
|
426
|
+
When a client calls an action, the server checks in this order:
|
|
427
|
+
|
|
428
|
+
1. **Blocklist** - Internal methods (destroy, setState, emit, etc.) are always blocked
|
|
429
|
+
2. **Private methods** - Methods starting with `_` or `#` are blocked
|
|
430
|
+
3. **publicActions** - Action must be in the whitelist (mandatory, no fallback)
|
|
431
|
+
4. **actionAuth** - Per-action role/permission check (if defined)
|
|
432
|
+
5. **Method exists** - Action must exist on the component instance
|
|
433
|
+
6. **Object.prototype** - Blocks toString, valueOf, hasOwnProperty
|
|
434
|
+
|
|
419
435
|
## Critical Rules
|
|
420
436
|
|
|
421
437
|
**ALWAYS:**
|
|
438
|
+
- Define `static publicActions` listing all client-callable methods (MANDATORY)
|
|
422
439
|
- Define `static auth` for protected components
|
|
423
440
|
- Define `static actionAuth` for protected actions
|
|
424
441
|
- Use `$auth.hasRole()` / `$auth.hasPermission()` in action logic
|
|
@@ -426,6 +443,7 @@ interface LiveAuthCredentials {
|
|
|
426
443
|
- Handle `AUTH_DENIED` errors in client UI
|
|
427
444
|
|
|
428
445
|
**NEVER:**
|
|
446
|
+
- Omit `publicActions` (component will deny ALL remote actions)
|
|
429
447
|
- Store sensitive data in component state
|
|
430
448
|
- Trust client-side auth checks alone (always verify server-side)
|
|
431
449
|
- Expose tokens in error messages
|