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
@@ -0,0 +1,202 @@
1
+ /**
2
+ * FluxStack Auth - Token Guard
3
+ *
4
+ * Guard baseado em Bearer token (header Authorization).
5
+ * Para APIs consumidas por mobile apps, CLIs, integrações.
6
+ *
7
+ * Fluxo:
8
+ * 1. Client envia: Authorization: Bearer <token>
9
+ * 2. Guard busca user pelo token no provider
10
+ * 3. Se válido, user está autenticado
11
+ *
12
+ * Tokens são armazenados no cache com referência ao userId.
13
+ */
14
+
15
+ import type {
16
+ Guard,
17
+ Authenticatable,
18
+ UserProvider,
19
+ RequestContext,
20
+ } from '../contracts'
21
+ import type { CacheDriver } from '@server/cache/contracts'
22
+
23
+ interface StoredToken {
24
+ userId: string | number
25
+ createdAt: number
26
+ expiresAt: number | null
27
+ }
28
+
29
+ export class TokenGuard implements Guard {
30
+ readonly name: string
31
+ private provider: UserProvider
32
+ private cache: CacheDriver
33
+ private request: RequestContext | null = null
34
+ private tokenTtl: number
35
+
36
+ /** Cache do usuário para a request atual */
37
+ private resolvedUser: Authenticatable | null | undefined = undefined
38
+
39
+ constructor(
40
+ name: string,
41
+ provider: UserProvider,
42
+ cache: CacheDriver,
43
+ tokenTtlSeconds: number = 86400 // 24h default
44
+ ) {
45
+ this.name = name
46
+ this.provider = provider
47
+ this.cache = cache
48
+ this.tokenTtl = tokenTtlSeconds
49
+ }
50
+
51
+ setRequest(context: RequestContext): void {
52
+ this.request = context
53
+ this.resolvedUser = undefined
54
+ }
55
+
56
+ async user(): Promise<Authenticatable | null> {
57
+ if (this.resolvedUser !== undefined) {
58
+ return this.resolvedUser
59
+ }
60
+
61
+ this.resolvedUser = null
62
+
63
+ if (!this.request) return null
64
+
65
+ // 1. Extrair token do header Authorization
66
+ const token = this.getBearerToken()
67
+ if (!token) return null
68
+
69
+ // 2. Buscar token no cache
70
+ const tokenData = await this.cache.get<StoredToken>(`auth_token:${this.hashToken(token)}`)
71
+ if (!tokenData) return null
72
+
73
+ // 3. Verificar expiração
74
+ if (tokenData.expiresAt && Date.now() > tokenData.expiresAt) {
75
+ await this.cache.delete(`auth_token:${this.hashToken(token)}`)
76
+ return null
77
+ }
78
+
79
+ // 4. Buscar usuário
80
+ const user = await this.provider.retrieveById(tokenData.userId)
81
+ if (user) {
82
+ this.resolvedUser = user
83
+ }
84
+
85
+ return this.resolvedUser
86
+ }
87
+
88
+ async id(): Promise<string | number | null> {
89
+ const user = await this.user()
90
+ return user?.getAuthId() ?? null
91
+ }
92
+
93
+ async check(): Promise<boolean> {
94
+ return (await this.user()) !== null
95
+ }
96
+
97
+ async guest(): Promise<boolean> {
98
+ return (await this.user()) === null
99
+ }
100
+
101
+ async attempt(credentials: Record<string, unknown>): Promise<Authenticatable | null> {
102
+ // 1. Buscar user
103
+ const user = await this.provider.retrieveByCredentials(credentials)
104
+ if (!user) return null
105
+
106
+ // 2. Validar password
107
+ const valid = await this.provider.validateCredentials(user, credentials)
108
+ if (!valid) return null
109
+
110
+ // 3. Gerar e armazenar token
111
+ await this.login(user)
112
+
113
+ return user
114
+ }
115
+
116
+ async login(user: Authenticatable): Promise<void> {
117
+ // Gerar token
118
+ const token = this.generateToken()
119
+ const hashedToken = this.hashToken(token)
120
+
121
+ // Armazenar no cache
122
+ const tokenData: StoredToken = {
123
+ userId: user.getAuthId(),
124
+ createdAt: Date.now(),
125
+ expiresAt: this.tokenTtl > 0 ? Date.now() + (this.tokenTtl * 1000) : null,
126
+ }
127
+ await this.cache.set(`auth_token:${hashedToken}`, tokenData, this.tokenTtl || undefined)
128
+
129
+ // Armazenar lista de tokens do user (para revogação em massa)
130
+ const userTokensKey = `user_tokens:${user.getAuthId()}`
131
+ const existingTokens = await this.cache.get<string[]>(userTokensKey) ?? []
132
+ existingTokens.push(hashedToken)
133
+ await this.cache.set(userTokensKey, existingTokens)
134
+
135
+ // Cache do user
136
+ this.resolvedUser = user
137
+
138
+ // Salvar token plain-text temporariamente para a response poder retorná-lo
139
+ ;(this as any)._lastGeneratedToken = token
140
+ }
141
+
142
+ async logout(): Promise<void> {
143
+ if (!this.request) return
144
+
145
+ const token = this.getBearerToken()
146
+ if (token) {
147
+ await this.cache.delete(`auth_token:${this.hashToken(token)}`)
148
+ }
149
+
150
+ this.resolvedUser = null
151
+ }
152
+
153
+ async validate(credentials: Record<string, unknown>): Promise<boolean> {
154
+ const user = await this.provider.retrieveByCredentials(credentials)
155
+ if (!user) return false
156
+ return this.provider.validateCredentials(user, credentials)
157
+ }
158
+
159
+ /**
160
+ * Revoga todos os tokens de um usuário.
161
+ */
162
+ async revokeAllTokens(userId: string | number): Promise<void> {
163
+ const userTokensKey = `user_tokens:${userId}`
164
+ const tokens = await this.cache.get<string[]>(userTokensKey) ?? []
165
+
166
+ for (const hashedToken of tokens) {
167
+ await this.cache.delete(`auth_token:${hashedToken}`)
168
+ }
169
+ await this.cache.delete(userTokensKey)
170
+ }
171
+
172
+ /** Retorna o último token gerado (para a response após login) */
173
+ getLastGeneratedToken(): string | null {
174
+ return (this as any)._lastGeneratedToken ?? null
175
+ }
176
+
177
+ /** Extrai Bearer token do header Authorization */
178
+ private getBearerToken(): string | null {
179
+ if (!this.request) return null
180
+
181
+ const authHeader = this.request.headers['authorization'] ?? this.request.headers['Authorization']
182
+ if (!authHeader?.startsWith('Bearer ')) return null
183
+
184
+ return authHeader.slice(7)
185
+ }
186
+
187
+ /** Gera um token criptograficamente seguro */
188
+ private generateToken(): string {
189
+ const bytes = new Uint8Array(32)
190
+ crypto.getRandomValues(bytes)
191
+ return Array.from(bytes)
192
+ .map(b => b.toString(16).padStart(2, '0'))
193
+ .join('')
194
+ }
195
+
196
+ /** Hash do token para armazenamento (nunca guardar plain-text) */
197
+ private hashToken(token: string): string {
198
+ const hasher = new Bun.CryptoHasher('sha256')
199
+ hasher.update(token)
200
+ return hasher.digest('hex')
201
+ }
202
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * FluxStack Auth System
3
+ *
4
+ * Sistema de autenticação modular inspirado no Laravel.
5
+ * Guard + Provider pattern com session e token support.
6
+ *
7
+ * ```ts
8
+ * import { auth, guest, initAuth, getAuthManager } from '@server/auth'
9
+ *
10
+ * // Inicializar (no boot da app)
11
+ * initAuth()
12
+ *
13
+ * // Proteger rotas
14
+ * app.use(auth()).get('/me', ({ user }) => user.toJSON())
15
+ * app.use(guest()).post('/login', loginHandler)
16
+ *
17
+ * // Usar auth manager diretamente
18
+ * const manager = getAuthManager()
19
+ * const guard = manager.guard('session')
20
+ * const user = await guard.attempt({ email, password })
21
+ * ```
22
+ */
23
+
24
+ // Contracts
25
+ export type {
26
+ Authenticatable,
27
+ Guard,
28
+ UserProvider,
29
+ RequestContext,
30
+ CookieOptions,
31
+ GuardConfig,
32
+ GuardFactory,
33
+ } from './contracts'
34
+
35
+ // Auth Manager
36
+ export { AuthManager } from './AuthManager'
37
+ export type { AuthManagerConfig, ProviderConfig } from './AuthManager'
38
+
39
+ // Hash
40
+ export { HashManager, Hash, getHashManager, setHashManager } from './HashManager'
41
+ export type { HashAlgorithm, HashOptions } from './HashManager'
42
+
43
+ // Rate Limiter
44
+ export { RateLimiter } from './RateLimiter'
45
+
46
+ // Guards
47
+ export { SessionGuard } from './guards/SessionGuard'
48
+ export { TokenGuard } from './guards/TokenGuard'
49
+
50
+ // Providers
51
+ export { InMemoryUserProvider, InMemoryUser } from './providers/InMemoryProvider'
52
+
53
+ // Sessions
54
+ export { SessionManager } from './sessions/SessionManager'
55
+ export type { SessionData, SessionConfig } from './sessions/SessionManager'
56
+
57
+ // Middleware
58
+ export { auth, guest, authOptional } from './middleware'
59
+
60
+ // ===== Boot =====
61
+
62
+ import { AuthManager } from './AuthManager'
63
+ import { HashManager, setHashManager } from './HashManager'
64
+ import { RateLimiter } from './RateLimiter'
65
+ import { SessionManager } from './sessions/SessionManager'
66
+ import { InMemoryUserProvider } from './providers/InMemoryProvider'
67
+ import { setAuthManagerForMiddleware, buildRequestContext } from './middleware'
68
+ import { cacheManager } from '@server/cache'
69
+ import { authConfig } from '@config/system/auth.config'
70
+ import { sessionConfig } from '@config/system/session.config'
71
+
72
+ let authManagerInstance: AuthManager | null = null
73
+ let rateLimiterInstance: RateLimiter | null = null
74
+ let sessionManagerInstance: SessionManager | null = null
75
+
76
+ /**
77
+ * Inicializa o sistema de auth.
78
+ * Deve ser chamado uma vez no boot da aplicação.
79
+ */
80
+ export function initAuth(): {
81
+ authManager: AuthManager
82
+ rateLimiter: RateLimiter
83
+ sessionManager: SessionManager
84
+ } {
85
+ // 1. Configurar Hash
86
+ const hashManager = new HashManager({
87
+ algorithm: authConfig.passwords.hashAlgorithm as 'bcrypt' | 'argon2id',
88
+ bcryptRounds: authConfig.passwords.bcryptRounds,
89
+ })
90
+ setHashManager(hashManager)
91
+
92
+ // 2. Criar Session Manager
93
+ const cache = cacheManager.driver()
94
+ sessionManagerInstance = new SessionManager(cache, {
95
+ lifetime: sessionConfig.lifetime,
96
+ cookieName: sessionConfig.cookieName,
97
+ httpOnly: sessionConfig.httpOnly,
98
+ secure: sessionConfig.secure,
99
+ sameSite: sessionConfig.sameSite as 'strict' | 'lax' | 'none',
100
+ path: sessionConfig.path,
101
+ domain: sessionConfig.domain || undefined,
102
+ })
103
+
104
+ // 3. Criar Rate Limiter
105
+ rateLimiterInstance = new RateLimiter(cache)
106
+
107
+ // 4. Criar Auth Manager
108
+ authManagerInstance = new AuthManager(
109
+ {
110
+ defaults: {
111
+ guard: authConfig.defaults.guard ?? 'session',
112
+ provider: authConfig.defaults.provider ?? 'memory',
113
+ },
114
+ guards: {
115
+ session: {
116
+ driver: 'session',
117
+ provider: 'memory',
118
+ },
119
+ token: {
120
+ driver: 'token',
121
+ provider: 'memory',
122
+ tokenTtl: authConfig.token.ttl,
123
+ },
124
+ },
125
+ providers: {
126
+ memory: {
127
+ driver: 'memory',
128
+ },
129
+ },
130
+ },
131
+ sessionManagerInstance
132
+ )
133
+
134
+ // 5. Registrar InMemoryProvider como default
135
+ const inMemoryProvider = new InMemoryUserProvider()
136
+ authManagerInstance.registerProvider('memory', inMemoryProvider)
137
+
138
+ // 6. Conectar middleware
139
+ setAuthManagerForMiddleware(authManagerInstance)
140
+
141
+ console.log(`🔐 Auth system initialized (guard: ${authConfig.defaults.guard}, hash: ${authConfig.passwords.hashAlgorithm})`)
142
+
143
+ return {
144
+ authManager: authManagerInstance,
145
+ rateLimiter: rateLimiterInstance,
146
+ sessionManager: sessionManagerInstance,
147
+ }
148
+ }
149
+
150
+ /** Retorna o AuthManager (deve ser chamado após initAuth) */
151
+ export function getAuthManager(): AuthManager {
152
+ if (!authManagerInstance) {
153
+ throw new Error('Auth not initialized. Call initAuth() first.')
154
+ }
155
+ return authManagerInstance
156
+ }
157
+
158
+ /** Retorna o RateLimiter (deve ser chamado após initAuth) */
159
+ export function getRateLimiter(): RateLimiter {
160
+ if (!rateLimiterInstance) {
161
+ throw new Error('Auth not initialized. Call initAuth() first.')
162
+ }
163
+ return rateLimiterInstance
164
+ }
165
+
166
+ /** Retorna o SessionManager (deve ser chamado após initAuth) */
167
+ export function getSessionManager(): SessionManager {
168
+ if (!sessionManagerInstance) {
169
+ throw new Error('Auth not initialized. Call initAuth() first.')
170
+ }
171
+ return sessionManagerInstance
172
+ }
173
+
174
+ export { buildRequestContext }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * FluxStack Auth - Elysia Middleware
3
+ *
4
+ * Middlewares prontos para proteger rotas:
5
+ *
6
+ * ```ts
7
+ * // Rota protegida (requer login)
8
+ * app.use(auth()).get('/me', ({ user }) => user.toJSON())
9
+ *
10
+ * // Rota de guest (requer NÃO estar logado)
11
+ * app.use(guest()).post('/login', loginHandler)
12
+ *
13
+ * // Guard específico
14
+ * app.use(auth('api')).get('/api/data', handler)
15
+ * ```
16
+ */
17
+
18
+ import { Elysia } from 'elysia'
19
+ import type { AuthManager } from './AuthManager'
20
+ import type { Authenticatable, RequestContext } from './contracts'
21
+
22
+ /** Referência ao AuthManager (setado no boot) */
23
+ let authManagerRef: AuthManager | null = null
24
+
25
+ export function setAuthManagerForMiddleware(manager: AuthManager): void {
26
+ authManagerRef = manager
27
+ }
28
+
29
+ /**
30
+ * Extrai RequestContext do context do Elysia.
31
+ */
32
+ function buildRequestContext(ctx: any): RequestContext {
33
+ const headers: Record<string, string | undefined> = {}
34
+ if (ctx.headers) {
35
+ for (const [key, value] of Object.entries(ctx.headers)) {
36
+ headers[key] = value as string | undefined
37
+ }
38
+ }
39
+
40
+ return {
41
+ headers,
42
+ cookie: ctx.cookie ?? {},
43
+ setCookie: (name: string, value: string, options?: any) => {
44
+ if (ctx.cookie) {
45
+ ctx.cookie[name].set({
46
+ value,
47
+ ...options,
48
+ })
49
+ }
50
+ },
51
+ removeCookie: (name: string) => {
52
+ if (ctx.cookie) {
53
+ ctx.cookie[name].set({
54
+ value: '',
55
+ maxAge: 0,
56
+ path: '/',
57
+ })
58
+ }
59
+ },
60
+ ip: ctx.request?.headers?.get('x-forwarded-for')
61
+ ?? ctx.request?.headers?.get('x-real-ip')
62
+ ?? ctx.server?.requestIP?.(ctx.request)?.address
63
+ ?? '127.0.0.1',
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Middleware que requer autenticação.
69
+ * Injeta `user` (Authenticatable) no context do Elysia.
70
+ *
71
+ * Retorna 401 se não autenticado.
72
+ */
73
+ export function auth(guardName?: string) {
74
+ return new Elysia({ name: `auth-guard${guardName ? `-${guardName}` : ''}` })
75
+ .derive(async (ctx) => {
76
+ if (!authManagerRef) {
77
+ throw new Error('Auth system not initialized. Did you forget to call initAuth()?')
78
+ }
79
+
80
+ const requestContext = buildRequestContext(ctx)
81
+ const guard = authManagerRef.freshGuard(guardName, requestContext)
82
+
83
+ const user = await guard.user()
84
+
85
+ return {
86
+ user: user as Authenticatable | null,
87
+ auth: guard,
88
+ }
89
+ })
90
+ .onBeforeHandle(async (ctx) => {
91
+ if (!(ctx as any).user) {
92
+ (ctx as any).set.status = 401
93
+ return {
94
+ success: false,
95
+ error: 'Unauthenticated',
96
+ message: 'You must be logged in to access this resource.',
97
+ }
98
+ }
99
+ })
100
+ .as('scoped')
101
+ }
102
+
103
+ /**
104
+ * Middleware que requer NÃO estar autenticado.
105
+ * Útil para rotas de login/register.
106
+ *
107
+ * Retorna 409 se já autenticado.
108
+ */
109
+ export function guest(guardName?: string) {
110
+ return new Elysia({ name: `guest-guard${guardName ? `-${guardName}` : ''}` })
111
+ .derive(async (ctx) => {
112
+ if (!authManagerRef) {
113
+ throw new Error('Auth system not initialized. Did you forget to call initAuth()?')
114
+ }
115
+
116
+ const requestContext = buildRequestContext(ctx)
117
+ const guard = authManagerRef.freshGuard(guardName, requestContext)
118
+
119
+ const user = await guard.user()
120
+
121
+ return {
122
+ user: user as Authenticatable | null,
123
+ auth: guard,
124
+ }
125
+ })
126
+ .onBeforeHandle(async (ctx) => {
127
+ if ((ctx as any).user) {
128
+ (ctx as any).set.status = 409
129
+ return {
130
+ success: false,
131
+ error: 'AlreadyAuthenticated',
132
+ message: 'You are already logged in.',
133
+ }
134
+ }
135
+ })
136
+ .as('scoped')
137
+ }
138
+
139
+ /**
140
+ * Middleware que resolve auth opcionalmente (não bloqueia).
141
+ * Útil para rotas que funcionam com ou sem login.
142
+ */
143
+ export function authOptional(guardName?: string) {
144
+ return new Elysia({ name: `auth-optional${guardName ? `-${guardName}` : ''}` })
145
+ .derive(async (ctx) => {
146
+ if (!authManagerRef) {
147
+ return { user: null, auth: null }
148
+ }
149
+
150
+ const requestContext = buildRequestContext(ctx)
151
+ const guard = authManagerRef.freshGuard(guardName, requestContext)
152
+
153
+ const user = await guard.user()
154
+
155
+ return {
156
+ user: user as Authenticatable | null,
157
+ auth: guard,
158
+ }
159
+ })
160
+ .as('scoped')
161
+ }
162
+
163
+ export { buildRequestContext }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * FluxStack Auth - In-Memory User Provider
3
+ *
4
+ * Provider que armazena usuários em memória.
5
+ * Ideal para desenvolvimento, demos e testes.
6
+ *
7
+ * Para produção, implemente UserProvider com seu ORM:
8
+ * ```ts
9
+ * class DrizzleUserProvider implements UserProvider {
10
+ * async retrieveById(id) {
11
+ * return db.select().from(users).where(eq(users.id, id)).get()
12
+ * }
13
+ * // ...
14
+ * }
15
+ * ```
16
+ */
17
+
18
+ import type { Authenticatable, UserProvider } from '../contracts'
19
+ import { Hash } from '../HashManager'
20
+
21
+ // ===== In-Memory User Model =====
22
+
23
+ export class InMemoryUser implements Authenticatable {
24
+ id: number
25
+ name: string
26
+ email: string
27
+ passwordHash: string
28
+ rememberToken: string | null = null
29
+ createdAt: Date
30
+
31
+ constructor(data: {
32
+ id: number
33
+ name: string
34
+ email: string
35
+ passwordHash: string
36
+ createdAt?: Date
37
+ }) {
38
+ this.id = data.id
39
+ this.name = data.name
40
+ this.email = data.email
41
+ this.passwordHash = data.passwordHash
42
+ this.createdAt = data.createdAt ?? new Date()
43
+ }
44
+
45
+ getAuthId(): number {
46
+ return this.id
47
+ }
48
+
49
+ getAuthIdField(): string {
50
+ return 'id'
51
+ }
52
+
53
+ getAuthPassword(): string {
54
+ return this.passwordHash
55
+ }
56
+
57
+ getRememberToken(): string | null {
58
+ return this.rememberToken
59
+ }
60
+
61
+ setRememberToken(token: string | null): void {
62
+ this.rememberToken = token
63
+ }
64
+
65
+ toJSON(): Record<string, unknown> {
66
+ return {
67
+ id: this.id,
68
+ name: this.name,
69
+ email: this.email,
70
+ createdAt: this.createdAt.toISOString(),
71
+ }
72
+ }
73
+ }
74
+
75
+ // ===== Provider =====
76
+
77
+ export class InMemoryUserProvider implements UserProvider {
78
+ private users: InMemoryUser[] = []
79
+ private nextId = 1
80
+
81
+ async retrieveById(id: string | number): Promise<Authenticatable | null> {
82
+ return this.users.find(u => u.id === Number(id)) ?? null
83
+ }
84
+
85
+ async retrieveByCredentials(credentials: Record<string, unknown>): Promise<Authenticatable | null> {
86
+ // Buscar por qualquer campo EXCETO password
87
+ const { password: _, ...searchFields } = credentials
88
+
89
+ return this.users.find(user => {
90
+ return Object.entries(searchFields).every(([key, value]) => {
91
+ return (user as any)[key] === value
92
+ })
93
+ }) ?? null
94
+ }
95
+
96
+ async validateCredentials(user: Authenticatable, credentials: Record<string, unknown>): Promise<boolean> {
97
+ const password = credentials.password as string | undefined
98
+ if (!password) return false
99
+
100
+ const valid = await Hash.check(password, user.getAuthPassword())
101
+
102
+ // Rehash transparente se necessário
103
+ if (valid && Hash.needsRehash(user.getAuthPassword())) {
104
+ const newHash = await Hash.make(password)
105
+ // Atualizar hash in-memory
106
+ const memUser = user as InMemoryUser
107
+ memUser.passwordHash = newHash
108
+ }
109
+
110
+ return valid
111
+ }
112
+
113
+ async retrieveByToken(id: string | number, token: string): Promise<Authenticatable | null> {
114
+ const user = this.users.find(u => u.id === Number(id))
115
+ if (!user) return null
116
+ if (user.getRememberToken() !== token) return null
117
+ return user
118
+ }
119
+
120
+ async updateRememberToken(user: Authenticatable, token: string | null): Promise<void> {
121
+ user.setRememberToken(token)
122
+ }
123
+
124
+ async createUser(data: Record<string, unknown>): Promise<Authenticatable> {
125
+ const name = data.name as string
126
+ const email = data.email as string
127
+ const password = data.password as string
128
+
129
+ if (!name || !email || !password) {
130
+ throw new Error('Name, email and password are required')
131
+ }
132
+
133
+ // Verificar email duplicado
134
+ const existing = this.users.find(u => u.email === email)
135
+ if (existing) {
136
+ throw new Error('Email already in use')
137
+ }
138
+
139
+ const passwordHash = await Hash.make(password)
140
+
141
+ const user = new InMemoryUser({
142
+ id: this.nextId++,
143
+ name,
144
+ email,
145
+ passwordHash,
146
+ })
147
+
148
+ this.users.push(user)
149
+ return user
150
+ }
151
+
152
+ /** Para testes: reset completo */
153
+ reset(): void {
154
+ this.users = []
155
+ this.nextId = 1
156
+ }
157
+
158
+ /** Para testes: retorna todos os usuários */
159
+ getAll(): InMemoryUser[] {
160
+ return [...this.users]
161
+ }
162
+ }