create-fluxstack 1.12.1 → 1.13.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 (80) 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/resources/live-auth.md +447 -0
  5. package/LLMD/resources/live-components.md +79 -21
  6. package/LLMD/resources/live-logging.md +158 -0
  7. package/LLMD/resources/live-upload.md +1 -1
  8. package/LLMD/resources/rest-auth.md +290 -0
  9. package/README.md +520 -340
  10. package/app/client/src/App.tsx +11 -0
  11. package/app/client/src/components/AppLayout.tsx +1 -0
  12. package/app/client/src/live/AuthDemo.tsx +332 -0
  13. package/app/server/auth/AuthManager.ts +213 -0
  14. package/app/server/auth/DevAuthProvider.ts +66 -0
  15. package/app/server/auth/HashManager.ts +123 -0
  16. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  17. package/app/server/auth/RateLimiter.ts +106 -0
  18. package/app/server/auth/contracts.ts +192 -0
  19. package/app/server/auth/guards/SessionGuard.ts +167 -0
  20. package/app/server/auth/guards/TokenGuard.ts +202 -0
  21. package/app/server/auth/index.ts +174 -0
  22. package/app/server/auth/middleware.ts +163 -0
  23. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  24. package/app/server/auth/sessions/SessionManager.ts +164 -0
  25. package/app/server/cache/CacheManager.ts +81 -0
  26. package/app/server/cache/MemoryDriver.ts +112 -0
  27. package/app/server/cache/contracts.ts +49 -0
  28. package/app/server/cache/index.ts +42 -0
  29. package/app/server/index.ts +14 -0
  30. package/app/server/live/LiveAdminPanel.ts +173 -0
  31. package/app/server/live/LiveCounter.ts +1 -0
  32. package/app/server/live/LiveLocalCounter.ts +13 -8
  33. package/app/server/live/LiveProtectedChat.ts +150 -0
  34. package/app/server/routes/auth.routes.ts +278 -0
  35. package/app/server/routes/index.ts +2 -0
  36. package/config/index.ts +8 -0
  37. package/config/system/auth.config.ts +49 -0
  38. package/config/system/session.config.ts +33 -0
  39. package/core/client/LiveComponentsProvider.tsx +76 -5
  40. package/core/client/components/Live.tsx +2 -1
  41. package/core/client/hooks/useLiveComponent.ts +47 -4
  42. package/core/client/index.ts +2 -1
  43. package/core/framework/server.ts +36 -4
  44. package/core/plugins/built-in/live-components/commands/create-live-component.ts +15 -8
  45. package/core/plugins/built-in/monitoring/index.ts +10 -3
  46. package/core/plugins/built-in/vite/index.ts +95 -18
  47. package/core/plugins/config.ts +5 -4
  48. package/core/plugins/discovery.ts +11 -2
  49. package/core/plugins/manager.ts +11 -5
  50. package/core/plugins/module-resolver.ts +1 -1
  51. package/core/plugins/registry.ts +53 -25
  52. package/core/server/live/ComponentRegistry.ts +79 -24
  53. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  54. package/core/server/live/LiveLogger.ts +111 -0
  55. package/core/server/live/LiveRoomManager.ts +5 -4
  56. package/core/server/live/StateSignature.ts +644 -643
  57. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  58. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  59. package/core/server/live/auth/index.ts +19 -0
  60. package/core/server/live/auth/types.ts +179 -0
  61. package/core/server/live/auto-generated-components.ts +8 -2
  62. package/core/server/live/index.ts +16 -0
  63. package/core/server/live/websocket-plugin.ts +92 -16
  64. package/core/templates/create-project.ts +0 -3
  65. package/core/types/types.ts +133 -13
  66. package/core/utils/index.ts +17 -17
  67. package/core/utils/logger/index.ts +5 -2
  68. package/core/utils/version.ts +1 -1
  69. package/package.json +1 -8
  70. package/plugins/crypto-auth/index.ts +6 -0
  71. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  72. package/plugins/crypto-auth/server/index.ts +24 -21
  73. package/rest-tests/README.md +57 -0
  74. package/rest-tests/auth-token.http +113 -0
  75. package/rest-tests/auth.http +112 -0
  76. package/rest-tests/rooms-token.http +69 -0
  77. package/rest-tests/users-token.http +62 -0
  78. package/.dockerignore +0 -81
  79. package/Dockerfile +0 -70
  80. package/LIVE_COMPONENTS_REVIEW.md +0 -781
@@ -7,6 +7,7 @@ import { CounterDemo } from './live/CounterDemo'
7
7
  import { UploadDemo } from './live/UploadDemo'
8
8
  import { ChatDemo } from './live/ChatDemo'
9
9
  import { RoomChatDemo } from './live/RoomChatDemo'
10
+ import { AuthDemo } from './live/AuthDemo'
10
11
  import { AppLayout } from './components/AppLayout'
11
12
  import { DemoPage } from './components/DemoPage'
12
13
  import { HomePage } from './pages/HomePage'
@@ -126,6 +127,16 @@ function AppContent() {
126
127
  </DemoPage>
127
128
  }
128
129
  />
130
+ <Route
131
+ path="/auth"
132
+ element={
133
+ <DemoPage
134
+ note={<>🔒 Sistema de autenticação declarativo para Live Components com <code className="text-purple-400">$auth</code>!</>}
135
+ >
136
+ <AuthDemo />
137
+ </DemoPage>
138
+ }
139
+ />
129
140
  <Route path="*" element={<HomePage apiStatus={apiStatus} />} />
130
141
  </Route>
131
142
  </Routes>
@@ -8,6 +8,7 @@ const navItems = [
8
8
  { to: '/upload', label: 'Upload' },
9
9
  { to: '/chat', label: 'Chat' },
10
10
  { to: '/room-chat', label: 'Room Chat' },
11
+ { to: '/auth', label: 'Auth' },
11
12
  { to: '/api-test', label: 'API Test' }
12
13
  ]
13
14
 
@@ -0,0 +1,332 @@
1
+ // 🔒 AuthDemo - Exemplo completo de autenticação em Live Components
2
+ //
3
+ // Demonstra:
4
+ // 1. Conexão autenticada via LiveComponentsProvider
5
+ // 2. Auth dinâmico via useLiveComponents().authenticate()
6
+ // 3. Componente público (sem auth)
7
+ // 4. Componente protegido (requer auth + role)
8
+ // 5. Actions protegidas por permissão
9
+ // 6. Leitura de $authenticated no proxy
10
+
11
+ import { useState } from 'react'
12
+ import { Live, useLiveComponents } from '@/core/client'
13
+ import type { LiveAuthOptions } from '@/core/client'
14
+ import { LiveCounter } from '@server/live/LiveCounter'
15
+ import { LiveAdminPanel } from '@server/live/LiveAdminPanel'
16
+
17
+ // ───────────────────────────────────────
18
+ // 1. Componente público (sem auth)
19
+ // Funciona para qualquer visitante
20
+ // ───────────────────────────────────────
21
+
22
+ function PublicSection() {
23
+ const counter = Live.use(LiveCounter, {
24
+ room: 'public-counter',
25
+ initialState: LiveCounter.defaultState,
26
+ persistState: false,
27
+ })
28
+
29
+ return (
30
+ <div className="bg-white/5 border border-white/10 rounded-xl p-6">
31
+ <h3 className="text-lg font-semibold text-white mb-1">Contador Público</h3>
32
+ <p className="text-gray-400 text-xs mb-4">Sem autenticação necessária</p>
33
+
34
+ <div className="flex items-center gap-4">
35
+ <button
36
+ onClick={() => counter.decrement()}
37
+ className="px-3 py-1 rounded bg-red-500/20 text-red-300 hover:bg-red-500/30"
38
+ >
39
+
40
+ </button>
41
+ <span className="text-3xl font-bold text-white">{counter.$state.count}</span>
42
+ <button
43
+ onClick={() => counter.increment()}
44
+ className="px-3 py-1 rounded bg-emerald-500/20 text-emerald-300 hover:bg-emerald-500/30"
45
+ >
46
+ +
47
+ </button>
48
+ </div>
49
+
50
+ <div className="mt-3 text-xs text-gray-500">
51
+ $authenticated: <code className="text-yellow-300">{String(counter.$authenticated)}</code>
52
+ </div>
53
+ </div>
54
+ )
55
+ }
56
+
57
+ // ───────────────────────────────────────
58
+ // 2. Painel admin (requer auth + role)
59
+ // Demonstra $auth no servidor
60
+ // ───────────────────────────────────────
61
+
62
+ function AdminSection() {
63
+ const [newUserName, setNewUserName] = useState('')
64
+ const [error, setError] = useState<string | null>(null)
65
+
66
+ const panel = Live.use(LiveAdminPanel, { persistState: false })
67
+
68
+ // Se não autenticado ou sem permissão, o mount falha com AUTH_DENIED
69
+ if (panel.$error?.includes('AUTH_DENIED')) {
70
+ return (
71
+ <div className="bg-red-500/10 border border-red-500/30 rounded-xl p-6">
72
+ <h3 className="text-lg font-semibold text-red-300 mb-2">Painel Admin</h3>
73
+ <p className="text-red-400 text-sm">
74
+ Acesso negado: {panel.$error}
75
+ </p>
76
+ <p className="text-gray-400 text-xs mt-2">
77
+ Autentique-se com role &quot;admin&quot; para acessar.
78
+ </p>
79
+ </div>
80
+ )
81
+ }
82
+
83
+ if (panel.$status === 'mounting' || panel.$loading) {
84
+ return (
85
+ <div className="bg-white/5 border border-white/10 rounded-xl p-6">
86
+ <div className="flex items-center gap-2 text-gray-400">
87
+ <div className="w-4 h-4 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
88
+ Montando painel admin...
89
+ </div>
90
+ </div>
91
+ )
92
+ }
93
+
94
+ const handleAddUser = async () => {
95
+ if (!newUserName.trim()) return
96
+ try {
97
+ await panel.addUser({ name: newUserName.trim(), role: 'user' })
98
+ setNewUserName('')
99
+ setError(null)
100
+ } catch (e: any) {
101
+ setError(e.message)
102
+ }
103
+ }
104
+
105
+ const handleDeleteUser = async (userId: string) => {
106
+ try {
107
+ await panel.deleteUser({ userId })
108
+ setError(null)
109
+ } catch (e: any) {
110
+ // Se AUTH_DENIED, significa que faltou a permissão 'users.delete'
111
+ setError(e.message)
112
+ }
113
+ }
114
+
115
+ return (
116
+ <div className="bg-white/5 border border-white/10 rounded-xl p-6">
117
+ <div className="flex items-center justify-between mb-4">
118
+ <div>
119
+ <h3 className="text-lg font-semibold text-white">Painel Admin</h3>
120
+ <p className="text-gray-400 text-xs">
121
+ Requer: <code className="text-purple-300">auth.required + roles: [&apos;admin&apos;]</code>
122
+ </p>
123
+ </div>
124
+ <div className="text-xs text-right">
125
+ <div className="text-gray-400">
126
+ User: <span className="text-emerald-300">{panel.$state.currentUser || '...'}</span>
127
+ </div>
128
+ <div className="text-gray-400">
129
+ Roles: <span className="text-yellow-300">{panel.$state.currentRoles.join(', ') || '...'}</span>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ {error && (
135
+ <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 mb-4 text-red-300 text-sm">
136
+ {error}
137
+ </div>
138
+ )}
139
+
140
+ {/* User list */}
141
+ <div className="space-y-2 mb-4">
142
+ {panel.$state.users.map(user => (
143
+ <div key={user.id} className="flex items-center justify-between bg-black/20 rounded-lg px-4 py-2">
144
+ <div>
145
+ <span className="text-white font-medium">{user.name}</span>
146
+ <span className="text-gray-500 text-xs ml-2">({user.role})</span>
147
+ </div>
148
+ <button
149
+ onClick={() => handleDeleteUser(user.id)}
150
+ className="text-xs px-2 py-1 rounded bg-red-500/20 text-red-300 hover:bg-red-500/30"
151
+ title="Requer permissão 'users.delete'"
152
+ >
153
+ Deletar
154
+ </button>
155
+ </div>
156
+ ))}
157
+ </div>
158
+
159
+ {/* Add user */}
160
+ <div className="flex gap-2">
161
+ <input
162
+ value={newUserName}
163
+ onChange={e => setNewUserName(e.target.value)}
164
+ onKeyDown={e => e.key === 'Enter' && handleAddUser()}
165
+ placeholder="Nome do usuário..."
166
+ className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
167
+ />
168
+ <button
169
+ onClick={handleAddUser}
170
+ className="px-4 py-2 rounded-lg bg-purple-500/20 border border-purple-500/30 text-purple-200 text-sm hover:bg-purple-500/30"
171
+ >
172
+ Adicionar
173
+ </button>
174
+ </div>
175
+
176
+ {/* Audit log */}
177
+ {panel.$state.audit.length > 0 && (
178
+ <div className="mt-4 pt-4 border-t border-white/10">
179
+ <div className="flex items-center justify-between mb-2">
180
+ <h4 className="text-sm font-semibold text-gray-300">Audit Log</h4>
181
+ <button
182
+ onClick={() => panel.clearAudit()}
183
+ className="text-xs px-2 py-1 rounded bg-gray-500/20 text-gray-400 hover:bg-gray-500/30"
184
+ title="Requer role 'admin'"
185
+ >
186
+ Limpar
187
+ </button>
188
+ </div>
189
+ <div className="space-y-1 max-h-32 overflow-auto">
190
+ {panel.$state.audit.map((entry, i) => (
191
+ <div key={i} className="text-xs text-gray-500">
192
+ <span className="text-gray-400">{new Date(entry.timestamp).toLocaleTimeString()}</span>
193
+ {' '}<span className="text-blue-300">{entry.action}</span>
194
+ {' '}by <span className="text-emerald-300">{entry.performedBy}</span>
195
+ {entry.target && <> on <span className="text-yellow-300">{entry.target}</span></>}
196
+ </div>
197
+ ))}
198
+ </div>
199
+ </div>
200
+ )}
201
+ </div>
202
+ )
203
+ }
204
+
205
+ // ───────────────────────────────────────
206
+ // 3. Controle de autenticação
207
+ // Simula login/logout via authenticate()
208
+ // ───────────────────────────────────────
209
+
210
+ function AuthControls() {
211
+ const { authenticated, authenticate, reconnect } = useLiveComponents()
212
+ const [token, setToken] = useState('')
213
+ const [isLoggingIn, setIsLoggingIn] = useState(false)
214
+
215
+ const handleLogin = async () => {
216
+ if (!token.trim()) return
217
+ setIsLoggingIn(true)
218
+ await authenticate({ token: token.trim() })
219
+ setIsLoggingIn(false)
220
+ // Componentes detectam automaticamente a mudança de auth e remontam
221
+ }
222
+
223
+ const handleLogout = () => {
224
+ setToken('')
225
+ // Reconectar sem token = nova conexão anônima
226
+ reconnect()
227
+ }
228
+
229
+ return (
230
+ <div className="bg-white/5 border border-white/10 rounded-xl p-6 mb-6">
231
+ <h3 className="text-lg font-semibold text-white mb-2">Autenticação</h3>
232
+
233
+ <div className="flex items-center gap-3 mb-4">
234
+ <div className={`w-3 h-3 rounded-full ${authenticated ? 'bg-emerald-400' : 'bg-gray-500'}`} />
235
+ <span className={`text-sm ${authenticated ? 'text-emerald-300' : 'text-gray-400'}`}>
236
+ {authenticated ? 'Autenticado' : 'Não autenticado'}
237
+ </span>
238
+ </div>
239
+
240
+ <div className="flex gap-2">
241
+ <input
242
+ value={token}
243
+ onChange={e => setToken(e.target.value)}
244
+ onKeyDown={e => e.key === 'Enter' && handleLogin()}
245
+ placeholder="Token (JWT, API key, etc.)"
246
+ className="flex-1 px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
247
+ />
248
+ <button
249
+ onClick={handleLogin}
250
+ disabled={isLoggingIn}
251
+ className="px-4 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-200 text-sm hover:bg-emerald-500/30 disabled:opacity-50"
252
+ >
253
+ {isLoggingIn ? 'Autenticando...' : 'Login'}
254
+ </button>
255
+ {authenticated && (
256
+ <button
257
+ onClick={handleLogout}
258
+ className="px-4 py-2 rounded-lg bg-red-500/20 border border-red-500/30 text-red-200 text-sm hover:bg-red-500/30"
259
+ >
260
+ Logout
261
+ </button>
262
+ )}
263
+ </div>
264
+
265
+ <div className="mt-4 p-3 bg-emerald-500/10 border border-emerald-500/20 rounded-lg">
266
+ <p className="text-emerald-300 text-xs font-semibold mb-2">Tokens de teste (dev only):</p>
267
+ <div className="grid grid-cols-3 gap-2 text-xs">
268
+ <button
269
+ onClick={() => { setToken('admin-token'); }}
270
+ className="px-2 py-1 rounded bg-purple-500/20 text-purple-300 hover:bg-purple-500/30"
271
+ >
272
+ admin-token
273
+ </button>
274
+ <button
275
+ onClick={() => { setToken('user-token'); }}
276
+ className="px-2 py-1 rounded bg-blue-500/20 text-blue-300 hover:bg-blue-500/30"
277
+ >
278
+ user-token
279
+ </button>
280
+ <button
281
+ onClick={() => { setToken('mod-token'); }}
282
+ className="px-2 py-1 rounded bg-yellow-500/20 text-yellow-300 hover:bg-yellow-500/30"
283
+ >
284
+ mod-token
285
+ </button>
286
+ </div>
287
+ <p className="text-gray-500 text-xs mt-2">
288
+ Clique para preencher o campo, depois clique em Login.
289
+ </p>
290
+ </div>
291
+
292
+ <p className="text-gray-500 text-xs mt-3">
293
+ Fluxo: <code>authenticate(&#123; token &#125;)</code> envia mensagem <code>AUTH</code> via WebSocket.
294
+ O servidor valida via <code>LiveAuthProvider</code> registrado.
295
+ </p>
296
+ </div>
297
+ )
298
+ }
299
+
300
+ // ───────────────────────────────────────
301
+ // 4. Demo principal
302
+ // ───────────────────────────────────────
303
+
304
+ export function AuthDemo() {
305
+ return (
306
+ <div className="space-y-6 w-full max-w-2xl mx-auto">
307
+ <div className="text-center mb-8">
308
+ <h2 className="text-3xl font-bold text-white mb-2">Live Components Auth</h2>
309
+ <p className="text-gray-400">
310
+ Sistema de autenticação declarativo para componentes real-time
311
+ </p>
312
+ </div>
313
+
314
+ <AuthControls />
315
+
316
+ <div className="grid gap-6">
317
+ <PublicSection />
318
+ <AdminSection />
319
+ </div>
320
+
321
+ <div className="bg-white/5 border border-white/10 rounded-xl p-6 text-xs text-gray-500 space-y-2">
322
+ <h4 className="text-sm font-semibold text-gray-300 mb-3">Como funciona</h4>
323
+ <p><strong className="text-purple-300">Server:</strong> <code>static auth = &#123; required: true, roles: [&apos;admin&apos;] &#125;</code></p>
324
+ <p><strong className="text-purple-300">Server:</strong> <code>static actionAuth = &#123; deleteUser: &#123; permissions: [&apos;users.delete&apos;] &#125; &#125;</code></p>
325
+ <p><strong className="text-purple-300">Server:</strong> <code>this.$auth.hasRole(&apos;admin&apos;)</code> dentro das actions</p>
326
+ <p><strong className="text-blue-300">Client:</strong> <code>component.$authenticated</code> no proxy</p>
327
+ <p><strong className="text-blue-300">Client:</strong> <code>useLiveComponents().authenticate(&#123; token &#125;)</code> para login</p>
328
+ <p><strong className="text-blue-300">Client:</strong> <code>&lt;LiveComponentsProvider auth=&#123;&#123; token &#125;&#125;&gt;</code> para auth na conexão</p>
329
+ </div>
330
+ </div>
331
+ )
332
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * FluxStack Auth - Auth Manager
3
+ *
4
+ * Orquestrador central do sistema de autenticação.
5
+ * Factory pattern com lazy resolution e cache de guards.
6
+ *
7
+ * Inspirado no AuthManager do Laravel:
8
+ * - Resolve guards por nome (ou default)
9
+ * - Extensível com extend() para guards customizados
10
+ * - Resolve providers automaticamente da config
11
+ *
12
+ * ```ts
13
+ * // Usar guard padrão
14
+ * const user = await auth.guard().user()
15
+ *
16
+ * // Usar guard específico
17
+ * const apiUser = await auth.guard('api').user()
18
+ *
19
+ * // Registrar guard customizado
20
+ * auth.extend('jwt', (name, config, provider) => new JWTGuard(...))
21
+ * ```
22
+ */
23
+
24
+ import type {
25
+ Guard,
26
+ UserProvider,
27
+ GuardConfig,
28
+ GuardFactory,
29
+ RequestContext,
30
+ } from './contracts'
31
+ import { SessionGuard } from './guards/SessionGuard'
32
+ import { TokenGuard } from './guards/TokenGuard'
33
+ import { SessionManager } from './sessions/SessionManager'
34
+ import { cacheManager } from '@server/cache'
35
+
36
+ export interface AuthManagerConfig {
37
+ defaults: {
38
+ guard: string
39
+ provider: string
40
+ }
41
+ guards: Record<string, GuardConfig>
42
+ providers: Record<string, ProviderConfig>
43
+ }
44
+
45
+ export interface ProviderConfig {
46
+ driver: string
47
+ [key: string]: unknown
48
+ }
49
+
50
+ export class AuthManager {
51
+ private config: AuthManagerConfig
52
+ private guards = new Map<string, Guard>()
53
+ private customGuardFactories = new Map<string, GuardFactory>()
54
+ private providerInstances = new Map<string, UserProvider>()
55
+ private customProviderFactories = new Map<string, (config: ProviderConfig) => UserProvider>()
56
+ private sessionManager: SessionManager
57
+
58
+ constructor(config: AuthManagerConfig, sessionManager: SessionManager) {
59
+ this.config = config
60
+ this.sessionManager = sessionManager
61
+ }
62
+
63
+ /**
64
+ * Retorna um guard por nome (ou o default).
65
+ * Guards são criados uma vez e reutilizados.
66
+ */
67
+ guard(name?: string): Guard {
68
+ const guardName = name ?? this.config.defaults.guard
69
+
70
+ if (this.guards.has(guardName)) {
71
+ return this.guards.get(guardName)!
72
+ }
73
+
74
+ const guard = this.resolve(guardName)
75
+ this.guards.set(guardName, guard)
76
+ return guard
77
+ }
78
+
79
+ /**
80
+ * Inicializa todos os guards com o contexto da request.
81
+ * Deve ser chamado no middleware, antes de qualquer uso.
82
+ */
83
+ setRequest(context: RequestContext): void {
84
+ for (const guard of this.guards.values()) {
85
+ guard.setRequest(context)
86
+ }
87
+ // Também resetar guards não-instanciados para forçar re-resolve com novo context
88
+ }
89
+
90
+ /**
91
+ * Cria um guard novo (sem cache) com o context da request.
92
+ * Útil quando precisa de um guard fresco para cada request.
93
+ */
94
+ freshGuard(name?: string, context?: RequestContext): Guard {
95
+ const guardName = name ?? this.config.defaults.guard
96
+ const guard = this.resolve(guardName)
97
+ if (context) {
98
+ guard.setRequest(context)
99
+ }
100
+ return guard
101
+ }
102
+
103
+ /**
104
+ * Registra um guard driver customizado.
105
+ *
106
+ * ```ts
107
+ * auth.extend('jwt', (name, config, provider) => {
108
+ * return new JWTGuard(name, provider, config.secret)
109
+ * })
110
+ * ```
111
+ */
112
+ extend(driver: string, factory: GuardFactory): void {
113
+ this.customGuardFactories.set(driver, factory)
114
+ }
115
+
116
+ /**
117
+ * Registra um provider customizado.
118
+ *
119
+ * ```ts
120
+ * auth.extendProvider('drizzle', (config) => {
121
+ * return new DrizzleUserProvider(db, config.table)
122
+ * })
123
+ * ```
124
+ */
125
+ extendProvider(driver: string, factory: (config: ProviderConfig) => UserProvider): void {
126
+ this.customProviderFactories.set(driver, factory)
127
+ }
128
+
129
+ /**
130
+ * Registra uma instância de provider diretamente.
131
+ */
132
+ registerProvider(name: string, provider: UserProvider): void {
133
+ this.providerInstances.set(name, provider)
134
+ }
135
+
136
+ /** Retorna o nome do guard padrão */
137
+ getDefaultGuardName(): string {
138
+ return this.config.defaults.guard
139
+ }
140
+
141
+ /** Retorna a config */
142
+ getConfig(): AuthManagerConfig {
143
+ return this.config
144
+ }
145
+
146
+ /** Resolve um guard por nome */
147
+ private resolve(name: string): Guard {
148
+ const guardConfig = this.config.guards[name]
149
+ if (!guardConfig) {
150
+ throw new Error(
151
+ `Auth guard '${name}' not configured. ` +
152
+ `Available: ${Object.keys(this.config.guards).join(', ')}`
153
+ )
154
+ }
155
+
156
+ // Resolver provider
157
+ const provider = this.resolveProvider(guardConfig.provider)
158
+
159
+ // Verificar custom factory primeiro
160
+ if (this.customGuardFactories.has(guardConfig.driver)) {
161
+ return this.customGuardFactories.get(guardConfig.driver)!(name, guardConfig, provider)
162
+ }
163
+
164
+ // Built-in drivers
165
+ switch (guardConfig.driver) {
166
+ case 'session':
167
+ return new SessionGuard(name, provider, this.sessionManager)
168
+
169
+ case 'token':
170
+ return new TokenGuard(
171
+ name,
172
+ provider,
173
+ cacheManager.driver(),
174
+ guardConfig.tokenTtl as number | undefined
175
+ )
176
+
177
+ default:
178
+ throw new Error(
179
+ `Auth guard driver '${guardConfig.driver}' not supported. ` +
180
+ `Use auth.extend('${guardConfig.driver}', factory) to register it.`
181
+ )
182
+ }
183
+ }
184
+
185
+ /** Resolve um provider por nome */
186
+ private resolveProvider(name: string): UserProvider {
187
+ // Cache de instâncias
188
+ if (this.providerInstances.has(name)) {
189
+ return this.providerInstances.get(name)!
190
+ }
191
+
192
+ const providerConfig = this.config.providers[name]
193
+ if (!providerConfig) {
194
+ throw new Error(
195
+ `Auth provider '${name}' not configured. ` +
196
+ `Available: ${Object.keys(this.config.providers).join(', ')}`
197
+ )
198
+ }
199
+
200
+ // Custom factory
201
+ if (this.customProviderFactories.has(providerConfig.driver)) {
202
+ const provider = this.customProviderFactories.get(providerConfig.driver)!(providerConfig)
203
+ this.providerInstances.set(name, provider)
204
+ return provider
205
+ }
206
+
207
+ throw new Error(
208
+ `Auth provider driver '${providerConfig.driver}' not supported. ` +
209
+ `Use auth.extendProvider('${providerConfig.driver}', factory) to register it, ` +
210
+ `or use auth.registerProvider('${name}', providerInstance) to register directly.`
211
+ )
212
+ }
213
+ }
@@ -0,0 +1,66 @@
1
+ // 🧪 DevAuthProvider - Provider de desenvolvimento para testes de auth
2
+ //
3
+ // Aceita tokens simples para facilitar testes da demo de autenticação.
4
+ // NÃO USAR EM PRODUÇÃO!
5
+ //
6
+ // Tokens válidos:
7
+ // - "admin-token" → role: admin, permissions: all
8
+ // - "user-token" → role: user, permissions: básicas
9
+ // - "mod-token" → role: moderator, permissions: moderação
10
+
11
+ import type {
12
+ LiveAuthProvider,
13
+ LiveAuthCredentials,
14
+ LiveAuthContext,
15
+ } from '@core/server/live/auth/types'
16
+ import { AuthenticatedContext } from '@core/server/live/auth/LiveAuthContext'
17
+
18
+ interface DevUser {
19
+ id: string
20
+ name: string
21
+ roles: string[]
22
+ permissions: string[]
23
+ }
24
+
25
+ const DEV_USERS: Record<string, DevUser> = {
26
+ 'admin-token': {
27
+ id: 'admin-1',
28
+ name: 'Admin User',
29
+ roles: ['admin', 'user'],
30
+ permissions: ['users.read', 'users.write', 'users.delete', 'chat.read', 'chat.write', 'chat.admin'],
31
+ },
32
+ 'user-token': {
33
+ id: 'user-1',
34
+ name: 'Regular User',
35
+ roles: ['user'],
36
+ permissions: ['chat.read', 'chat.write'],
37
+ },
38
+ 'mod-token': {
39
+ id: 'mod-1',
40
+ name: 'Moderator',
41
+ roles: ['moderator', 'user'],
42
+ permissions: ['chat.read', 'chat.write', 'chat.moderate'],
43
+ },
44
+ }
45
+
46
+ export class DevAuthProvider implements LiveAuthProvider {
47
+ readonly name = 'dev'
48
+
49
+ async authenticate(credentials: LiveAuthCredentials): Promise<LiveAuthContext | null> {
50
+ const token = credentials.token as string
51
+ if (!token) return null
52
+
53
+ const user = DEV_USERS[token]
54
+ if (!user) return null
55
+
56
+ return new AuthenticatedContext(
57
+ {
58
+ id: user.id,
59
+ name: user.name,
60
+ roles: user.roles,
61
+ permissions: user.permissions,
62
+ },
63
+ token
64
+ )
65
+ }
66
+ }