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.
- package/LLMD/INDEX.md +8 -1
- package/LLMD/agent.md +867 -0
- package/LLMD/config/environment-vars.md +30 -0
- package/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +465 -0
- package/LLMD/resources/live-components.md +168 -26
- package/LLMD/resources/live-logging.md +220 -0
- package/LLMD/resources/live-upload.md +59 -8
- package/LLMD/resources/rest-auth.md +290 -0
- package/README.md +520 -340
- 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 +139 -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 +334 -0
- 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/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/AuthManager.ts +213 -0
- package/app/server/auth/DevAuthProvider.ts +66 -0
- package/app/server/auth/HashManager.ts +123 -0
- package/app/server/auth/JWTAuthProvider.example.ts +101 -0
- package/app/server/auth/RateLimiter.ts +106 -0
- package/app/server/auth/contracts.ts +192 -0
- package/app/server/auth/guards/SessionGuard.ts +167 -0
- package/app/server/auth/guards/TokenGuard.ts +202 -0
- package/app/server/auth/index.ts +174 -0
- package/app/server/auth/middleware.ts +163 -0
- package/app/server/auth/providers/InMemoryProvider.ts +162 -0
- package/app/server/auth/sessions/SessionManager.ts +164 -0
- package/app/server/cache/CacheManager.ts +81 -0
- package/app/server/cache/MemoryDriver.ts +112 -0
- package/app/server/cache/contracts.ts +49 -0
- package/app/server/cache/index.ts +42 -0
- package/app/server/index.ts +14 -0
- package/app/server/live/LiveAdminPanel.ts +174 -0
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -0
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -32
- package/app/server/live/LiveProtectedChat.ts +151 -0
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/app/server/routes/auth.routes.ts +278 -0
- package/app/server/routes/index.ts +2 -0
- package/config/index.ts +8 -0
- package/config/system/auth.config.ts +49 -0
- package/config/system/runtime.config.ts +4 -0
- package/config/system/session.config.ts +33 -0
- package/core/build/optimizer.ts +235 -235
- package/core/client/LiveComponentsProvider.tsx +76 -5
- package/core/client/components/Live.tsx +17 -10
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/useLiveComponent.ts +58 -5
- package/core/client/hooks/useLiveDebugger.ts +392 -0
- package/core/client/index.ts +16 -1
- package/core/framework/server.ts +36 -4
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +19 -8
- package/core/plugins/built-in/monitoring/index.ts +10 -3
- package/core/plugins/built-in/vite/index.ts +151 -20
- package/core/plugins/config.ts +5 -4
- package/core/plugins/discovery.ts +11 -2
- package/core/plugins/manager.ts +11 -5
- package/core/plugins/module-resolver.ts +1 -1
- package/core/plugins/registry.ts +53 -25
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +134 -50
- package/core/server/live/FileUploadManager.ts +188 -24
- package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
- package/core/server/live/LiveDebugger.ts +462 -0
- package/core/server/live/LiveLogger.ts +144 -0
- package/core/server/live/LiveRoomManager.ts +22 -5
- package/core/server/live/StateSignature.ts +704 -643
- package/core/server/live/WebSocketConnectionManager.ts +11 -10
- package/core/server/live/auth/LiveAuthContext.ts +71 -0
- package/core/server/live/auth/LiveAuthManager.ts +304 -0
- package/core/server/live/auth/index.ts +19 -0
- package/core/server/live/auth/types.ts +179 -0
- package/core/server/live/auto-generated-components.ts +8 -2
- package/core/server/live/index.ts +16 -0
- package/core/server/live/websocket-plugin.ts +323 -22
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/templates/create-project.ts +0 -3
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +278 -22
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/index.ts +5 -2
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/package.json +1 -8
- package/plugins/crypto-auth/index.ts +6 -0
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
- package/plugins/crypto-auth/server/index.ts +24 -21
- package/rest-tests/README.md +57 -0
- package/rest-tests/auth-token.http +113 -0
- package/rest-tests/auth.http +112 -0
- package/rest-tests/rooms-token.http +69 -0
- package/rest-tests/users-token.http +62 -0
- package/.dockerignore +0 -81
- package/Dockerfile +0 -70
- package/LIVE_COMPONENTS_REVIEW.md +0 -781
- package/app/client/src/assets/react.svg +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Auth - Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Gerencia sessões de usuários usando o cache system modular.
|
|
5
|
+
* Cada sessão é um registro no cache com TTL automático.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const sessionId = await sessionManager.create({ userId: 1 })
|
|
9
|
+
* const data = await sessionManager.read(sessionId)
|
|
10
|
+
* await sessionManager.destroy(sessionId)
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CacheDriver } from '@server/cache/contracts'
|
|
15
|
+
import { cacheManager } from '@server/cache'
|
|
16
|
+
|
|
17
|
+
export interface SessionData {
|
|
18
|
+
[key: string]: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SessionConfig {
|
|
22
|
+
/** Tempo de vida da sessão em segundos (default: 7200 = 2h) */
|
|
23
|
+
lifetime: number
|
|
24
|
+
/** Nome do cookie (default: 'fluxstack_session') */
|
|
25
|
+
cookieName: string
|
|
26
|
+
/** Cookie httpOnly (default: true) */
|
|
27
|
+
httpOnly: boolean
|
|
28
|
+
/** Cookie secure (default: false em dev, true em prod) */
|
|
29
|
+
secure: boolean
|
|
30
|
+
/** Cookie sameSite (default: 'lax') */
|
|
31
|
+
sameSite: 'strict' | 'lax' | 'none'
|
|
32
|
+
/** Cookie path (default: '/') */
|
|
33
|
+
path: string
|
|
34
|
+
/** Cookie domain (default: undefined = current domain) */
|
|
35
|
+
domain?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DEFAULT_CONFIG: SessionConfig = {
|
|
39
|
+
lifetime: 7200,
|
|
40
|
+
cookieName: 'fluxstack_session',
|
|
41
|
+
httpOnly: true,
|
|
42
|
+
secure: false,
|
|
43
|
+
sameSite: 'lax',
|
|
44
|
+
path: '/',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class SessionManager {
|
|
48
|
+
private cache: CacheDriver
|
|
49
|
+
private config: SessionConfig
|
|
50
|
+
|
|
51
|
+
constructor(cache?: CacheDriver, config?: Partial<SessionConfig>) {
|
|
52
|
+
this.cache = cache ?? cacheManager.driver()
|
|
53
|
+
this.config = { ...DEFAULT_CONFIG, ...config }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Cria uma nova sessão e retorna o ID.
|
|
58
|
+
*/
|
|
59
|
+
async create(data: SessionData = {}): Promise<string> {
|
|
60
|
+
const sessionId = this.generateId()
|
|
61
|
+
await this.cache.set(
|
|
62
|
+
this.cacheKey(sessionId),
|
|
63
|
+
{ ...data, _createdAt: Date.now() },
|
|
64
|
+
this.config.lifetime
|
|
65
|
+
)
|
|
66
|
+
return sessionId
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Lê os dados de uma sessão.
|
|
71
|
+
*/
|
|
72
|
+
async read(sessionId: string): Promise<SessionData | null> {
|
|
73
|
+
return this.cache.get<SessionData>(this.cacheKey(sessionId))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Atualiza os dados de uma sessão (merge com dados existentes).
|
|
78
|
+
*/
|
|
79
|
+
async update(sessionId: string, data: SessionData): Promise<void> {
|
|
80
|
+
const existing = await this.read(sessionId)
|
|
81
|
+
if (!existing) return
|
|
82
|
+
|
|
83
|
+
await this.cache.set(
|
|
84
|
+
this.cacheKey(sessionId),
|
|
85
|
+
{ ...existing, ...data },
|
|
86
|
+
this.config.lifetime
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Define um valor específico na sessão.
|
|
92
|
+
*/
|
|
93
|
+
async put(sessionId: string, key: string, value: unknown): Promise<void> {
|
|
94
|
+
const existing = await this.read(sessionId)
|
|
95
|
+
if (!existing) return
|
|
96
|
+
|
|
97
|
+
existing[key] = value
|
|
98
|
+
await this.cache.set(
|
|
99
|
+
this.cacheKey(sessionId),
|
|
100
|
+
existing,
|
|
101
|
+
this.config.lifetime
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove um valor específico da sessão.
|
|
107
|
+
*/
|
|
108
|
+
async forget(sessionId: string, key: string): Promise<void> {
|
|
109
|
+
const existing = await this.read(sessionId)
|
|
110
|
+
if (!existing) return
|
|
111
|
+
|
|
112
|
+
delete existing[key]
|
|
113
|
+
await this.cache.set(
|
|
114
|
+
this.cacheKey(sessionId),
|
|
115
|
+
existing,
|
|
116
|
+
this.config.lifetime
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Destroi uma sessão.
|
|
122
|
+
*/
|
|
123
|
+
async destroy(sessionId: string): Promise<void> {
|
|
124
|
+
await this.cache.delete(this.cacheKey(sessionId))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Regenera o ID da sessão (mantém os dados).
|
|
129
|
+
* Importante após login para prevenir session fixation.
|
|
130
|
+
*/
|
|
131
|
+
async regenerate(oldSessionId: string): Promise<string> {
|
|
132
|
+
const data = await this.read(oldSessionId)
|
|
133
|
+
await this.destroy(oldSessionId)
|
|
134
|
+
|
|
135
|
+
const newSessionId = this.generateId()
|
|
136
|
+
if (data) {
|
|
137
|
+
await this.cache.set(
|
|
138
|
+
this.cacheKey(newSessionId),
|
|
139
|
+
data,
|
|
140
|
+
this.config.lifetime
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
return newSessionId
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Retorna a configuração de sessão */
|
|
147
|
+
getConfig(): SessionConfig {
|
|
148
|
+
return this.config
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Gera um session ID criptograficamente seguro */
|
|
152
|
+
private generateId(): string {
|
|
153
|
+
const bytes = new Uint8Array(32)
|
|
154
|
+
crypto.getRandomValues(bytes)
|
|
155
|
+
return Array.from(bytes)
|
|
156
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
157
|
+
.join('')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Chave no cache para a sessão */
|
|
161
|
+
private cacheKey(sessionId: string): string {
|
|
162
|
+
return `session:${sessionId}`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Cache - Cache Manager
|
|
3
|
+
*
|
|
4
|
+
* Factory para drivers de cache. Extensível com extend().
|
|
5
|
+
*
|
|
6
|
+
* Uso:
|
|
7
|
+
* ```ts
|
|
8
|
+
* const cache = cacheManager.driver() // driver padrão (memory)
|
|
9
|
+
* const redis = cacheManager.driver('redis') // driver específico
|
|
10
|
+
*
|
|
11
|
+
* await cache.set('key', 'value', 60)
|
|
12
|
+
* const val = await cache.get('key')
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { CacheDriver } from './contracts'
|
|
17
|
+
import { MemoryCacheDriver } from './MemoryDriver'
|
|
18
|
+
|
|
19
|
+
type DriverFactory = () => CacheDriver
|
|
20
|
+
|
|
21
|
+
export class CacheManager {
|
|
22
|
+
private drivers = new Map<string, CacheDriver>()
|
|
23
|
+
private factories = new Map<string, DriverFactory>()
|
|
24
|
+
private defaultDriverName: string
|
|
25
|
+
|
|
26
|
+
constructor(defaultDriver: string = 'memory') {
|
|
27
|
+
this.defaultDriverName = defaultDriver
|
|
28
|
+
|
|
29
|
+
// Registrar driver padrão
|
|
30
|
+
this.factories.set('memory', () => new MemoryCacheDriver())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Retorna uma instância do driver de cache.
|
|
35
|
+
* Drivers são criados uma vez e reutilizados (singleton por nome).
|
|
36
|
+
*/
|
|
37
|
+
driver(name?: string): CacheDriver {
|
|
38
|
+
const driverName = name ?? this.defaultDriverName
|
|
39
|
+
|
|
40
|
+
// Cache de instâncias
|
|
41
|
+
if (this.drivers.has(driverName)) {
|
|
42
|
+
return this.drivers.get(driverName)!
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const factory = this.factories.get(driverName)
|
|
46
|
+
if (!factory) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Cache driver '${driverName}' not registered. ` +
|
|
49
|
+
`Available: ${Array.from(this.factories.keys()).join(', ')}`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const driver = factory()
|
|
54
|
+
this.drivers.set(driverName, driver)
|
|
55
|
+
return driver
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Registra um driver customizado.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* cacheManager.extend('redis', () => new RedisCacheDriver({ url: '...' }))
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
extend(name: string, factory: DriverFactory): void {
|
|
66
|
+
this.factories.set(name, factory)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Retorna o nome do driver padrão */
|
|
70
|
+
getDefaultDriver(): string {
|
|
71
|
+
return this.defaultDriverName
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Altera o driver padrão */
|
|
75
|
+
setDefaultDriver(name: string): void {
|
|
76
|
+
this.defaultDriverName = name
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Instância global do cache manager */
|
|
81
|
+
export const cacheManager = new CacheManager()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Cache - Memory Driver
|
|
3
|
+
*
|
|
4
|
+
* Armazena cache na memória do processo.
|
|
5
|
+
* Ideal para desenvolvimento e testes.
|
|
6
|
+
*
|
|
7
|
+
* Para produção, troque por Redis, Memcached, etc.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CacheDriver } from './contracts'
|
|
11
|
+
|
|
12
|
+
interface CacheEntry<T = unknown> {
|
|
13
|
+
value: T
|
|
14
|
+
expiresAt: number | null // null = sem expiração
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class MemoryCacheDriver implements CacheDriver {
|
|
18
|
+
private store = new Map<string, CacheEntry>()
|
|
19
|
+
private gcInterval: ReturnType<typeof setInterval> | null = null
|
|
20
|
+
|
|
21
|
+
constructor(gcIntervalMs: number = 60_000) {
|
|
22
|
+
// GC periódico para limpar entradas expiradas
|
|
23
|
+
this.gcInterval = setInterval(() => this.gc(), gcIntervalMs)
|
|
24
|
+
if (this.gcInterval.unref) {
|
|
25
|
+
this.gcInterval.unref()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async get<T = unknown>(key: string): Promise<T | null> {
|
|
30
|
+
const entry = this.store.get(key)
|
|
31
|
+
if (!entry) return null
|
|
32
|
+
|
|
33
|
+
// Verificar expiração
|
|
34
|
+
if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
|
|
35
|
+
this.store.delete(key)
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return entry.value as T
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void> {
|
|
43
|
+
const expiresAt = ttlSeconds && ttlSeconds > 0
|
|
44
|
+
? Date.now() + (ttlSeconds * 1000)
|
|
45
|
+
: null
|
|
46
|
+
|
|
47
|
+
this.store.set(key, { value, expiresAt })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async has(key: string): Promise<boolean> {
|
|
51
|
+
const value = await this.get(key)
|
|
52
|
+
return value !== null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async delete(key: string): Promise<boolean> {
|
|
56
|
+
return this.store.delete(key)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async flush(): Promise<void> {
|
|
60
|
+
this.store.clear()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async increment(key: string, amount: number = 1): Promise<number> {
|
|
64
|
+
const current = await this.get<number>(key)
|
|
65
|
+
const newValue = (current ?? 0) + amount
|
|
66
|
+
|
|
67
|
+
// Preservar TTL original se existir
|
|
68
|
+
const entry = this.store.get(key)
|
|
69
|
+
const remainingTtl = entry?.expiresAt
|
|
70
|
+
? Math.max(0, Math.ceil((entry.expiresAt - Date.now()) / 1000))
|
|
71
|
+
: undefined
|
|
72
|
+
|
|
73
|
+
await this.set(key, newValue, remainingTtl)
|
|
74
|
+
return newValue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async decrement(key: string, amount: number = 1): Promise<number> {
|
|
78
|
+
return this.increment(key, -amount)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async remember<T = unknown>(key: string, ttlSeconds: number, callback: () => Promise<T>): Promise<T> {
|
|
82
|
+
const cached = await this.get<T>(key)
|
|
83
|
+
if (cached !== null) return cached
|
|
84
|
+
|
|
85
|
+
const value = await callback()
|
|
86
|
+
await this.set(key, value, ttlSeconds)
|
|
87
|
+
return value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async gc(): Promise<void> {
|
|
91
|
+
const now = Date.now()
|
|
92
|
+
for (const [key, entry] of this.store) {
|
|
93
|
+
if (entry.expiresAt !== null && now > entry.expiresAt) {
|
|
94
|
+
this.store.delete(key)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Para testes: retorna quantidade de itens no cache */
|
|
100
|
+
get size(): number {
|
|
101
|
+
return this.store.size
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Cleanup ao destruir */
|
|
105
|
+
destroy(): void {
|
|
106
|
+
if (this.gcInterval) {
|
|
107
|
+
clearInterval(this.gcInterval)
|
|
108
|
+
this.gcInterval = null
|
|
109
|
+
}
|
|
110
|
+
this.store.clear()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Cache System - Contracts
|
|
3
|
+
*
|
|
4
|
+
* Interface modular para cache. Hoje usa memória do processo,
|
|
5
|
+
* amanhã pode trocar para Redis, Memcached, SQLite, etc.
|
|
6
|
+
*
|
|
7
|
+
* Inspirado no Cache do Laravel: driver swappable via config.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Interface que todo driver de cache deve implementar.
|
|
12
|
+
*
|
|
13
|
+
* Para criar um driver customizado (ex: Redis):
|
|
14
|
+
* ```ts
|
|
15
|
+
* class RedisCacheDriver implements CacheDriver {
|
|
16
|
+
* async get(key) { return redis.get(key) }
|
|
17
|
+
* async set(key, value, ttl) { redis.setex(key, ttl, value) }
|
|
18
|
+
* // ...
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export interface CacheDriver {
|
|
23
|
+
/** Busca valor do cache. Retorna null se não existe ou expirou. */
|
|
24
|
+
get<T = unknown>(key: string): Promise<T | null>
|
|
25
|
+
|
|
26
|
+
/** Armazena valor no cache. TTL em segundos (0 = sem expiração). */
|
|
27
|
+
set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void>
|
|
28
|
+
|
|
29
|
+
/** Verifica se a chave existe no cache. */
|
|
30
|
+
has(key: string): Promise<boolean>
|
|
31
|
+
|
|
32
|
+
/** Remove uma chave do cache. */
|
|
33
|
+
delete(key: string): Promise<boolean>
|
|
34
|
+
|
|
35
|
+
/** Remove todas as chaves do cache. */
|
|
36
|
+
flush(): Promise<void>
|
|
37
|
+
|
|
38
|
+
/** Incrementa um valor numérico. Retorna o novo valor. */
|
|
39
|
+
increment(key: string, amount?: number): Promise<number>
|
|
40
|
+
|
|
41
|
+
/** Decrementa um valor numérico. Retorna o novo valor. */
|
|
42
|
+
decrement(key: string, amount?: number): Promise<number>
|
|
43
|
+
|
|
44
|
+
/** Busca ou armazena: se não existe, executa callback e salva. */
|
|
45
|
+
remember<T = unknown>(key: string, ttlSeconds: number, callback: () => Promise<T>): Promise<T>
|
|
46
|
+
|
|
47
|
+
/** Remove entradas expiradas (garbage collection). */
|
|
48
|
+
gc(): Promise<void>
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Cache System
|
|
3
|
+
*
|
|
4
|
+
* Sistema modular de cache. Hoje usa memória, amanhã Redis.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { cache, cacheManager } from '@server/cache'
|
|
8
|
+
*
|
|
9
|
+
* // Usar cache padrão
|
|
10
|
+
* await cache.set('user:1', userData, 3600)
|
|
11
|
+
* const user = await cache.get('user:1')
|
|
12
|
+
*
|
|
13
|
+
* // Trocar driver
|
|
14
|
+
* cacheManager.extend('redis', () => new MyRedisDriver())
|
|
15
|
+
* const redis = cacheManager.driver('redis')
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type { CacheDriver } from './contracts'
|
|
20
|
+
export { MemoryCacheDriver } from './MemoryDriver'
|
|
21
|
+
export { CacheManager, cacheManager } from './CacheManager'
|
|
22
|
+
|
|
23
|
+
/** Atalho: driver de cache padrão (lazy-initialized) */
|
|
24
|
+
import type { CacheDriver as CacheDriverType } from './contracts'
|
|
25
|
+
import { cacheManager as _cm } from './CacheManager'
|
|
26
|
+
|
|
27
|
+
let _cacheInstance: CacheDriverType | null = null
|
|
28
|
+
|
|
29
|
+
function getCacheInstance(): CacheDriverType {
|
|
30
|
+
if (!_cacheInstance) {
|
|
31
|
+
_cacheInstance = _cm.driver()
|
|
32
|
+
}
|
|
33
|
+
return _cacheInstance
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const cache: CacheDriverType = new Proxy({} as CacheDriverType, {
|
|
37
|
+
get(_, prop: string) {
|
|
38
|
+
const instance = getCacheInstance()
|
|
39
|
+
const value = (instance as any)[prop]
|
|
40
|
+
return typeof value === 'function' ? value.bind(instance) : value
|
|
41
|
+
}
|
|
42
|
+
})
|
package/app/server/index.ts
CHANGED
|
@@ -17,6 +17,20 @@ import { liveComponentsPlugin } from "@core/server/live/websocket-plugin"
|
|
|
17
17
|
import { appInstance } from "@server/app"
|
|
18
18
|
import { appConfig } from "@config"
|
|
19
19
|
|
|
20
|
+
// 🔒 Auth provider para Live Components
|
|
21
|
+
import { liveAuthManager } from "@core/server/live/auth"
|
|
22
|
+
import { DevAuthProvider } from "./auth/DevAuthProvider"
|
|
23
|
+
|
|
24
|
+
// 🔐 Auth system (Guard + Provider, Laravel-inspired)
|
|
25
|
+
import { initAuth } from "@server/auth"
|
|
26
|
+
|
|
27
|
+
// Registrar provider de desenvolvimento (tokens simples para testes)
|
|
28
|
+
liveAuthManager.register(new DevAuthProvider())
|
|
29
|
+
console.log('🔓 DevAuthProvider registered')
|
|
30
|
+
|
|
31
|
+
// Inicializar sistema de autenticação
|
|
32
|
+
initAuth()
|
|
33
|
+
|
|
20
34
|
const framework = new FluxStackFramework()
|
|
21
35
|
.use(swaggerPlugin)
|
|
22
36
|
.use(liveComponentsPlugin)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// 🔒 LiveAdminPanel - Exemplo completo de Live Component com autenticação
|
|
2
|
+
//
|
|
3
|
+
// Demonstra todos os cenários de auth:
|
|
4
|
+
// 1. Componente público (sem auth) → LiveCounter
|
|
5
|
+
// 2. Componente protegido (auth required) → este componente
|
|
6
|
+
// 3. Componente com roles → este componente (role: admin)
|
|
7
|
+
// 4. Actions com permissões granulares → deleteUser requer 'users.delete'
|
|
8
|
+
// 5. Acesso ao $auth dentro de actions → getAuthInfo, audit trail
|
|
9
|
+
//
|
|
10
|
+
// Client: import { LiveAdminPanel } from '@server/live/LiveAdminPanel'
|
|
11
|
+
// Client link: import type { AdminPanelDemo as _Client } from '@client/src/live/AdminPanelDemo'
|
|
12
|
+
|
|
13
|
+
import { LiveComponent } from '@core/types/types'
|
|
14
|
+
import type { LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/auth/types'
|
|
15
|
+
|
|
16
|
+
// ===== State =====
|
|
17
|
+
|
|
18
|
+
interface User {
|
|
19
|
+
id: string
|
|
20
|
+
name: string
|
|
21
|
+
role: string
|
|
22
|
+
createdAt: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface AuditEntry {
|
|
26
|
+
action: string
|
|
27
|
+
performedBy: string
|
|
28
|
+
target?: string
|
|
29
|
+
timestamp: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface AdminPanelState {
|
|
33
|
+
users: User[]
|
|
34
|
+
audit: AuditEntry[]
|
|
35
|
+
currentUser: string | null
|
|
36
|
+
currentRoles: string[]
|
|
37
|
+
isAdmin: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ===== Component =====
|
|
41
|
+
|
|
42
|
+
export class LiveAdminPanel extends LiveComponent<AdminPanelState> {
|
|
43
|
+
static componentName = 'LiveAdminPanel'
|
|
44
|
+
static publicActions = ['getAuthInfo', 'init', 'listUsers', 'addUser', 'deleteUser', 'clearAudit'] as const
|
|
45
|
+
|
|
46
|
+
static defaultState: AdminPanelState = {
|
|
47
|
+
users: [
|
|
48
|
+
{ id: '1', name: 'Alice', role: 'admin', createdAt: Date.now() },
|
|
49
|
+
{ id: '2', name: 'Bob', role: 'user', createdAt: Date.now() },
|
|
50
|
+
{ id: '3', name: 'Carol', role: 'moderator', createdAt: Date.now() },
|
|
51
|
+
],
|
|
52
|
+
audit: [],
|
|
53
|
+
currentUser: null,
|
|
54
|
+
currentRoles: [],
|
|
55
|
+
isAdmin: false,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─────────────────────────────────────────
|
|
59
|
+
// 🔒 Auth: requer autenticação + role admin
|
|
60
|
+
// ─────────────────────────────────────────
|
|
61
|
+
static auth: LiveComponentAuth = {
|
|
62
|
+
required: true,
|
|
63
|
+
roles: ['admin'],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─────────────────────────────────────────
|
|
67
|
+
// 🔒 Auth por action: permissões granulares
|
|
68
|
+
// ─────────────────────────────────────────
|
|
69
|
+
static actionAuth: LiveActionAuthMap = {
|
|
70
|
+
deleteUser: { permissions: ['users.delete'] },
|
|
71
|
+
clearAudit: { roles: ['admin'] },
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ===== Actions =====
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Retorna info do usuário autenticado.
|
|
78
|
+
* Qualquer admin pode chamar (protegido pelo static auth do componente).
|
|
79
|
+
*/
|
|
80
|
+
async getAuthInfo() {
|
|
81
|
+
return {
|
|
82
|
+
authenticated: this.$auth.authenticated,
|
|
83
|
+
userId: this.$auth.user?.id,
|
|
84
|
+
roles: this.$auth.user?.roles || [],
|
|
85
|
+
permissions: this.$auth.user?.permissions || [],
|
|
86
|
+
isAdmin: this.$auth.hasRole('admin'),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Popula o state com info do usuário autenticado.
|
|
92
|
+
* Chamado pelo client após mount para exibir quem está logado.
|
|
93
|
+
*/
|
|
94
|
+
async init() {
|
|
95
|
+
this.setState({
|
|
96
|
+
currentUser: this.$auth.user?.id || null,
|
|
97
|
+
currentRoles: this.$auth.user?.roles || [],
|
|
98
|
+
isAdmin: this.$auth.hasRole('admin'),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
this.addAudit('LOGIN', this.$auth.user?.id || 'unknown')
|
|
102
|
+
|
|
103
|
+
return { success: true }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Lista usuários - qualquer admin pode.
|
|
108
|
+
*/
|
|
109
|
+
async listUsers() {
|
|
110
|
+
return { users: this.state.users }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Adiciona um usuário - qualquer admin pode.
|
|
115
|
+
*/
|
|
116
|
+
async addUser(payload: { name: string; role: string }) {
|
|
117
|
+
const user: User = {
|
|
118
|
+
id: String(Date.now()),
|
|
119
|
+
name: payload.name,
|
|
120
|
+
role: payload.role,
|
|
121
|
+
createdAt: Date.now(),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.setState({
|
|
125
|
+
users: [...this.state.users, user],
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
this.addAudit('ADD_USER', this.$auth.user?.id || 'unknown', user.name)
|
|
129
|
+
|
|
130
|
+
return { success: true, user }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 🔒 Deleta um usuário.
|
|
135
|
+
* Requer permissão 'users.delete' (via static actionAuth).
|
|
136
|
+
* Se o usuário não tiver essa permissão, o framework bloqueia ANTES
|
|
137
|
+
* de executar este método.
|
|
138
|
+
*/
|
|
139
|
+
async deleteUser(payload: { userId: string }) {
|
|
140
|
+
const user = this.state.users.find(u => u.id === payload.userId)
|
|
141
|
+
if (!user) throw new Error('User not found')
|
|
142
|
+
|
|
143
|
+
this.setState({
|
|
144
|
+
users: this.state.users.filter(u => u.id !== payload.userId),
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
this.addAudit('DELETE_USER', this.$auth.user?.id || 'unknown', user.name)
|
|
148
|
+
|
|
149
|
+
return { success: true }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 🔒 Limpa o audit log.
|
|
154
|
+
* Requer role 'admin' (via static actionAuth).
|
|
155
|
+
*/
|
|
156
|
+
async clearAudit() {
|
|
157
|
+
this.setState({ audit: [] })
|
|
158
|
+
return { success: true }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ===== Helpers (privados, não expostos como actions) =====
|
|
162
|
+
|
|
163
|
+
private addAudit(action: string, performedBy: string, target?: string) {
|
|
164
|
+
const entry: AuditEntry = {
|
|
165
|
+
action,
|
|
166
|
+
performedBy,
|
|
167
|
+
target,
|
|
168
|
+
timestamp: Date.now(),
|
|
169
|
+
}
|
|
170
|
+
this.setState({
|
|
171
|
+
audit: [...this.state.audit, entry].slice(-20),
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|