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
@@ -0,0 +1,150 @@
1
+ // 🔒 LiveProtectedChat - Exemplo de Live Component com autenticação
2
+ //
3
+ // Demonstra como usar o sistema de auth em Live Components:
4
+ // - static auth: define que o componente requer autenticação
5
+ // - static actionAuth: define permissões por action
6
+ // - this.$auth: acessa o contexto de auth dentro do componente
7
+ //
8
+ // Client usage:
9
+ // import type { LiveProtectedChat as _Client } from '@client/src/live/ProtectedChat'
10
+
11
+ import { LiveComponent } from '@core/types/types'
12
+ import type { LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/auth/types'
13
+
14
+ interface ChatMessage {
15
+ id: number
16
+ userId: string
17
+ text: string
18
+ timestamp: number
19
+ isAdmin: boolean
20
+ }
21
+
22
+ interface ProtectedChatState {
23
+ messages: ChatMessage[]
24
+ userCount: number
25
+ currentUser: string | null
26
+ isAdmin: boolean
27
+ }
28
+
29
+ export class LiveProtectedChat extends LiveComponent<ProtectedChatState> {
30
+ static componentName = 'LiveProtectedChat'
31
+
32
+ static defaultState: ProtectedChatState = {
33
+ messages: [],
34
+ userCount: 0,
35
+ currentUser: null,
36
+ isAdmin: false,
37
+ }
38
+
39
+ // 🔒 Auth: componente requer autenticação
40
+ static auth: LiveComponentAuth = {
41
+ required: true,
42
+ }
43
+
44
+ // 🔒 Auth por action: deleteMessage requer permissão 'chat.admin'
45
+ static actionAuth: LiveActionAuthMap = {
46
+ deleteMessage: { permissions: ['chat.admin'] },
47
+ clearMessages: { roles: ['admin'] },
48
+ }
49
+
50
+ protected roomType = 'protected-chat'
51
+
52
+ constructor(
53
+ initialState: Partial<ProtectedChatState>,
54
+ ws: any,
55
+ options?: { room?: string; userId?: string }
56
+ ) {
57
+ super(initialState, ws, options)
58
+
59
+ // Escutar mensagens de outros usuários na sala
60
+ this.onRoomEvent<ChatMessage>('NEW_MESSAGE', (msg) => {
61
+ const messages = [...this.state.messages, msg].slice(-50)
62
+ this.setState({ messages })
63
+ })
64
+
65
+ this.onRoomEvent<{ count: number }>('USER_COUNT', (data) => {
66
+ this.setState({ userCount: data.count })
67
+ })
68
+ }
69
+
70
+ /**
71
+ * Entra na sala e configura info do usuário autenticado
72
+ */
73
+ async join(payload: { room: string }) {
74
+ this.$room(payload.room).join()
75
+
76
+ // 🔒 Usar $auth para identificar o usuário
77
+ const userId = this.$auth.user?.id || this.userId || 'anonymous'
78
+ const isAdmin = this.$auth.hasRole('admin')
79
+
80
+ this.setState({
81
+ currentUser: userId,
82
+ isAdmin,
83
+ })
84
+
85
+ return { success: true, userId, isAdmin }
86
+ }
87
+
88
+ /**
89
+ * Envia mensagem - qualquer usuário autenticado pode enviar
90
+ */
91
+ async sendMessage(payload: { text: string }) {
92
+ if (!payload.text?.trim()) {
93
+ throw new Error('Message cannot be empty')
94
+ }
95
+
96
+ const message: ChatMessage = {
97
+ id: Date.now(),
98
+ userId: this.$auth.user?.id || this.userId || 'unknown',
99
+ text: payload.text.trim(),
100
+ timestamp: Date.now(),
101
+ isAdmin: this.$auth.hasRole('admin'),
102
+ }
103
+
104
+ // Atualiza estado local + notifica outros na sala
105
+ this.emitRoomEventWithState(
106
+ 'NEW_MESSAGE',
107
+ message,
108
+ { messages: [...this.state.messages, message].slice(-50) }
109
+ )
110
+
111
+ return { success: true, messageId: message.id }
112
+ }
113
+
114
+ /**
115
+ * Deleta uma mensagem - requer permissão 'chat.admin'
116
+ * (protegido via static actionAuth)
117
+ */
118
+ async deleteMessage(payload: { messageId: number }) {
119
+ const messages = this.state.messages.filter(m => m.id !== payload.messageId)
120
+ this.setState({ messages })
121
+
122
+ // Notificar outros na sala
123
+ this.emitRoomEvent('MESSAGE_DELETED', { messageId: payload.messageId })
124
+
125
+ return { success: true }
126
+ }
127
+
128
+ /**
129
+ * Limpa todas as mensagens - requer role 'admin'
130
+ * (protegido via static actionAuth)
131
+ */
132
+ async clearMessages() {
133
+ this.setState({ messages: [] })
134
+ this.emitRoomEvent('MESSAGES_CLEARED', {})
135
+ return { success: true }
136
+ }
137
+
138
+ /**
139
+ * Retorna info do usuário autenticado (sem restrição extra)
140
+ */
141
+ async getAuthInfo() {
142
+ return {
143
+ authenticated: this.$auth.authenticated,
144
+ userId: this.$auth.user?.id,
145
+ roles: this.$auth.user?.roles,
146
+ permissions: this.$auth.user?.permissions,
147
+ isAdmin: this.$auth.hasRole('admin'),
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,278 @@
1
+ /**
2
+ * FluxStack Auth Routes
3
+ *
4
+ * Endpoints de autenticação:
5
+ * - POST /api/auth/register — Registrar novo usuário
6
+ * - POST /api/auth/login — Login (email + password)
7
+ * - POST /api/auth/logout — Logout (destroi sessão)
8
+ * - GET /api/auth/me — Retorna usuário autenticado
9
+ */
10
+
11
+ import { Elysia, t } from 'elysia'
12
+ import {
13
+ getAuthManager,
14
+ getRateLimiter,
15
+ auth,
16
+ guest,
17
+ buildRequestContext,
18
+ } from '@server/auth'
19
+ import { authConfig } from '@config/system/auth.config'
20
+
21
+ // ===== Schemas TypeBox (para validação + Swagger) =====
22
+
23
+ const RegisterBodySchema = t.Object({
24
+ name: t.String({ minLength: 1, description: 'User name' }),
25
+ email: t.String({ format: 'email', description: 'User email' }),
26
+ password: t.String({ minLength: 6, description: 'User password (min 6 chars)' }),
27
+ })
28
+
29
+ const LoginBodySchema = t.Object({
30
+ email: t.String({ format: 'email', description: 'User email' }),
31
+ password: t.String({ minLength: 1, description: 'User password' }),
32
+ })
33
+
34
+ const AuthUserResponseSchema = t.Object({
35
+ success: t.Literal(true),
36
+ user: t.Object({
37
+ id: t.Union([t.String(), t.Number()]),
38
+ name: t.Optional(t.String()),
39
+ email: t.Optional(t.String()),
40
+ createdAt: t.Optional(t.String()),
41
+ }),
42
+ })
43
+
44
+ const AuthErrorResponseSchema = t.Object({
45
+ success: t.Literal(false),
46
+ error: t.String(),
47
+ message: t.Optional(t.String()),
48
+ })
49
+
50
+ const LoginResponseSchema = t.Object({
51
+ success: t.Literal(true),
52
+ user: t.Object({
53
+ id: t.Union([t.String(), t.Number()]),
54
+ name: t.Optional(t.String()),
55
+ email: t.Optional(t.String()),
56
+ createdAt: t.Optional(t.String()),
57
+ }),
58
+ token: t.Optional(t.String({ description: 'Bearer token (only for token guard)' })),
59
+ })
60
+
61
+ // ===== Routes =====
62
+
63
+ export const authRoutes = new Elysia({
64
+ prefix: '/auth',
65
+ tags: ['Auth'],
66
+ })
67
+
68
+ // ───── Register ─────
69
+ .post('/register', async (ctx) => {
70
+ const { body, set } = ctx
71
+
72
+ try {
73
+ const authManager = getAuthManager()
74
+ const guard = authManager.freshGuard(undefined, buildRequestContext(ctx))
75
+
76
+ // Resolver provider para criar user
77
+ const config = authManager.getConfig()
78
+ const providerName = config.guards[config.defaults.guard]?.provider ?? config.defaults.provider
79
+
80
+ // Acessar provider via guard.attempt com credenciais
81
+ // Alternativa: criar user via provider e depois login
82
+ const guardInstance = authManager.guard()
83
+
84
+ // Criar user diretamente via provider registrado
85
+ // O provider é acessível via attempt/validate, mas para register
86
+ // precisamos acessar createUser.
87
+ // Solução: usar o InMemoryProvider diretamente ou o guard.
88
+ // Por segurança, vamos usar attempt após criar o user.
89
+
90
+ // Buscar provider do auth manager config
91
+ const providers = (authManager as any).providerInstances as Map<string, any>
92
+ const provider = providers.get(providerName)
93
+
94
+ if (!provider) {
95
+ set.status = 500
96
+ return { success: false as const, error: 'ServerError', message: 'Auth provider not available' }
97
+ }
98
+
99
+ // Verificar se email já existe
100
+ const existing = await provider.retrieveByCredentials({ email: body.email })
101
+ if (existing) {
102
+ set.status = 422
103
+ return { success: false as const, error: 'ValidationError', message: 'Email already in use' }
104
+ }
105
+
106
+ // Criar usuário
107
+ const user = await provider.createUser({
108
+ name: body.name,
109
+ email: body.email,
110
+ password: body.password,
111
+ })
112
+
113
+ // Auto-login após registro
114
+ await guard.login(user)
115
+
116
+ set.status = 201
117
+ return {
118
+ success: true as const,
119
+ user: user.toJSON() as any,
120
+ }
121
+ } catch (error: any) {
122
+ set.status = 422
123
+ return {
124
+ success: false as const,
125
+ error: 'RegistrationFailed',
126
+ message: error.message ?? 'Failed to register user',
127
+ }
128
+ }
129
+ }, {
130
+ body: RegisterBodySchema,
131
+ response: {
132
+ 201: AuthUserResponseSchema,
133
+ 422: AuthErrorResponseSchema,
134
+ 500: AuthErrorResponseSchema,
135
+ },
136
+ detail: {
137
+ summary: 'Register',
138
+ description: 'Create a new user account and auto-login',
139
+ },
140
+ })
141
+
142
+ // ───── Login ─────
143
+ .post('/login', async (ctx) => {
144
+ const { body, set } = ctx
145
+
146
+ const rateLimiter = getRateLimiter()
147
+ const requestContext = buildRequestContext(ctx)
148
+ const throttleKey = `${body.email}|${requestContext.ip}`
149
+
150
+ // Rate limiting
151
+ const maxAttempts = authConfig.rateLimit.maxAttempts ?? 5
152
+ const decaySeconds = authConfig.rateLimit.decaySeconds ?? 60
153
+
154
+ if (await rateLimiter.tooManyAttempts(throttleKey, maxAttempts)) {
155
+ const retryAfter = await rateLimiter.availableIn(throttleKey)
156
+ set.status = 429
157
+ set.headers['Retry-After'] = String(retryAfter)
158
+ return {
159
+ success: false as const,
160
+ error: 'TooManyAttempts',
161
+ message: `Too many login attempts. Try again in ${retryAfter} seconds.`,
162
+ }
163
+ }
164
+
165
+ try {
166
+ const authManager = getAuthManager()
167
+ const guard = authManager.freshGuard(undefined, requestContext)
168
+
169
+ // Tentar autenticar
170
+ const user = await guard.attempt({
171
+ email: body.email,
172
+ password: body.password,
173
+ })
174
+
175
+ if (!user) {
176
+ // Registrar tentativa falha
177
+ await rateLimiter.hit(throttleKey, decaySeconds)
178
+ set.status = 401
179
+ return {
180
+ success: false as const,
181
+ error: 'InvalidCredentials',
182
+ message: 'Invalid email or password.',
183
+ }
184
+ }
185
+
186
+ // Sucesso: limpar rate limit
187
+ await rateLimiter.clear(throttleKey)
188
+
189
+ // Montar response
190
+ const response: any = {
191
+ success: true as const,
192
+ user: user.toJSON(),
193
+ }
194
+
195
+ // Se for token guard, incluir token na response
196
+ if ('getLastGeneratedToken' in guard) {
197
+ const token = (guard as any).getLastGeneratedToken()
198
+ if (token) {
199
+ response.token = token
200
+ }
201
+ }
202
+
203
+ return response
204
+ } catch (error: any) {
205
+ set.status = 500
206
+ return {
207
+ success: false as const,
208
+ error: 'LoginFailed',
209
+ message: error.message ?? 'An unexpected error occurred.',
210
+ }
211
+ }
212
+ }, {
213
+ body: LoginBodySchema,
214
+ response: {
215
+ 200: LoginResponseSchema,
216
+ 401: AuthErrorResponseSchema,
217
+ 429: AuthErrorResponseSchema,
218
+ 500: AuthErrorResponseSchema,
219
+ },
220
+ detail: {
221
+ summary: 'Login',
222
+ description: 'Authenticate with email and password. Returns user data and session cookie (or token).',
223
+ },
224
+ })
225
+
226
+ // ───── Logout ─────
227
+ .use(auth())
228
+ .post('/logout', async (ctx) => {
229
+ const { auth: guard } = ctx as any
230
+
231
+ try {
232
+ if (guard) {
233
+ await guard.logout()
234
+ }
235
+
236
+ return {
237
+ success: true as const,
238
+ message: 'Logged out successfully.',
239
+ }
240
+ } catch (error: any) {
241
+ (ctx as any).set.status = 500
242
+ return {
243
+ success: false as const,
244
+ error: 'LogoutFailed',
245
+ message: error.message ?? 'Failed to logout.',
246
+ }
247
+ }
248
+ }, {
249
+ response: {
250
+ 200: t.Object({
251
+ success: t.Literal(true),
252
+ message: t.String(),
253
+ }),
254
+ 500: AuthErrorResponseSchema,
255
+ },
256
+ detail: {
257
+ summary: 'Logout',
258
+ description: 'Destroy the current session and clear the cookie.',
259
+ },
260
+ })
261
+
262
+ // ───── Me (current user) ─────
263
+ .get('/me', async (ctx) => {
264
+ const { user } = ctx as any
265
+
266
+ return {
267
+ success: true as const,
268
+ user: user.toJSON(),
269
+ }
270
+ }, {
271
+ response: {
272
+ 200: AuthUserResponseSchema,
273
+ },
274
+ detail: {
275
+ summary: 'Current User',
276
+ description: 'Returns the currently authenticated user.',
277
+ },
278
+ })
@@ -1,6 +1,7 @@
1
1
  import { Elysia, t } from "elysia"
2
2
  import { usersRoutes } from "./users.routes"
3
3
  import { roomRoutes } from "./room.routes"
4
+ import { authRoutes } from "./auth.routes"
4
5
 
5
6
  export const apiRoutes = new Elysia({ prefix: "/api" })
6
7
  .get("/", () => ({ message: "🔥 Hot Reload funcionando! FluxStack API v1.4.0 ⚡" }), {
@@ -34,5 +35,6 @@ export const apiRoutes = new Elysia({ prefix: "/api" })
34
35
  }
35
36
  })
36
37
  // Register routes
38
+ .use(authRoutes)
37
39
  .use(usersRoutes)
38
40
  .use(roomRoutes)
package/config/index.ts CHANGED
@@ -40,6 +40,8 @@ export { appRuntimeConfig } from './system/runtime.config'
40
40
  export { systemConfig, systemRuntimeInfo } from './system/system.config'
41
41
  export { databaseConfig } from './system/database.config'
42
42
  export { servicesConfig } from './system/services.config'
43
+ export { authConfig } from './system/auth.config'
44
+ export { sessionConfig } from './system/session.config'
43
45
 
44
46
  // Main FluxStack config (composed)
45
47
  export { fluxStackConfig, config as fluxConfig, type FluxStackConfig } from './fluxstack.config'
@@ -80,6 +82,8 @@ export type { SystemConfig, SystemRuntimeInfo } from './system/system.config'
80
82
  export type { AppRuntimeConfig } from './system/runtime.config'
81
83
  export type { DatabaseConfig } from './system/database.config'
82
84
  export type { ServicesConfig } from './system/services.config'
85
+ export type { AuthConfig } from './system/auth.config'
86
+ export type { SessionConfig } from './system/session.config'
83
87
 
84
88
  // Plugin types
85
89
  export type { CryptoAuthConfig } from '../plugins/crypto-auth/config'
@@ -99,6 +103,8 @@ import { appRuntimeConfig } from './system/runtime.config'
99
103
  import { systemConfig, systemRuntimeInfo } from './system/system.config'
100
104
  import { databaseConfig } from './system/database.config'
101
105
  import { servicesConfig } from './system/services.config'
106
+ import { authConfig } from './system/auth.config'
107
+ import { sessionConfig } from './system/session.config'
102
108
  import { cryptoAuthConfig } from '../plugins/crypto-auth/config'
103
109
 
104
110
  /**
@@ -122,6 +128,8 @@ export const config = {
122
128
  systemRuntime: systemRuntimeInfo,
123
129
  database: databaseConfig,
124
130
  services: servicesConfig,
131
+ auth: authConfig,
132
+ session: sessionConfig,
125
133
 
126
134
  // Plugin configs
127
135
  cryptoAuth: cryptoAuthConfig
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Auth Configuration
3
+ *
4
+ * Configuração do sistema de autenticação.
5
+ * Inspirado no config/auth.php do Laravel.
6
+ *
7
+ * Guards: COMO autenticar (session, token, jwt...)
8
+ * Providers: ONDE buscar usuários (memory, database...)
9
+ */
10
+
11
+ import { defineConfig, defineNestedConfig, config } from '@core/utils/config-schema'
12
+
13
+ // ===== Defaults =====
14
+
15
+ const defaultsSchema = {
16
+ guard: config.enum('AUTH_DEFAULT_GUARD', ['session', 'token'] as const, 'session'),
17
+ provider: config.enum('AUTH_DEFAULT_PROVIDER', ['memory', 'database'] as const, 'memory'),
18
+ } as const
19
+
20
+ // ===== Password Hashing =====
21
+
22
+ const passwordsSchema = {
23
+ hashAlgorithm: config.enum('AUTH_HASH_ALGORITHM', ['bcrypt', 'argon2id'] as const, 'bcrypt'),
24
+ bcryptRounds: config.number('AUTH_BCRYPT_ROUNDS', 10),
25
+ } as const
26
+
27
+ // ===== Rate Limiting =====
28
+
29
+ const rateLimitSchema = {
30
+ maxAttempts: config.number('AUTH_RATE_LIMIT_MAX_ATTEMPTS', 5),
31
+ decaySeconds: config.number('AUTH_RATE_LIMIT_DECAY_SECONDS', 60),
32
+ } as const
33
+
34
+ // ===== Token Guard =====
35
+
36
+ const tokenSchema = {
37
+ ttl: config.number('AUTH_TOKEN_TTL', 86400),
38
+ } as const
39
+
40
+ export const authConfig = defineNestedConfig({
41
+ defaults: defaultsSchema,
42
+ passwords: passwordsSchema,
43
+ rateLimit: rateLimitSchema,
44
+ token: tokenSchema,
45
+ })
46
+
47
+ export type AuthConfig = typeof authConfig
48
+
49
+ export default authConfig
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Session Configuration
3
+ *
4
+ * Configuração do sistema de sessões.
5
+ * Inspirado no config/session.php do Laravel.
6
+ */
7
+
8
+ import { defineConfig, config } from '@core/utils/config-schema'
9
+
10
+ const sessionConfigSchema = {
11
+ /** Driver de sessão (memory = processo, extensível para redis, database) */
12
+ driver: config.enum('SESSION_DRIVER', ['memory'] as const, 'memory'),
13
+ /** Tempo de vida da sessão em segundos (default: 7200 = 2 horas) */
14
+ lifetime: config.number('SESSION_LIFETIME', 7200),
15
+ /** Nome do cookie de sessão */
16
+ cookieName: config.string('SESSION_COOKIE', 'fluxstack_session'),
17
+ /** Cookie httpOnly (default: true) */
18
+ httpOnly: config.boolean('SESSION_HTTP_ONLY', true),
19
+ /** Cookie secure - true em produção (default: false) */
20
+ secure: config.boolean('SESSION_SECURE', false),
21
+ /** Cookie sameSite */
22
+ sameSite: config.enum('SESSION_SAME_SITE', ['strict', 'lax', 'none'] as const, 'lax'),
23
+ /** Cookie path */
24
+ path: config.string('SESSION_PATH', '/'),
25
+ /** Cookie domain (vazio = current domain) */
26
+ domain: config.string('SESSION_DOMAIN', ''),
27
+ } as const
28
+
29
+ export const sessionConfig = defineConfig(sessionConfigSchema)
30
+
31
+ export type SessionConfig = typeof sessionConfig
32
+
33
+ export default sessionConfig