create-fluxstack 1.12.0 → 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 (82) 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/client/src/live/RoomChatDemo.tsx +24 -105
  14. package/app/server/auth/AuthManager.ts +213 -0
  15. package/app/server/auth/DevAuthProvider.ts +66 -0
  16. package/app/server/auth/HashManager.ts +123 -0
  17. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  18. package/app/server/auth/RateLimiter.ts +106 -0
  19. package/app/server/auth/contracts.ts +192 -0
  20. package/app/server/auth/guards/SessionGuard.ts +167 -0
  21. package/app/server/auth/guards/TokenGuard.ts +202 -0
  22. package/app/server/auth/index.ts +174 -0
  23. package/app/server/auth/middleware.ts +163 -0
  24. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  25. package/app/server/auth/sessions/SessionManager.ts +164 -0
  26. package/app/server/cache/CacheManager.ts +81 -0
  27. package/app/server/cache/MemoryDriver.ts +112 -0
  28. package/app/server/cache/contracts.ts +49 -0
  29. package/app/server/cache/index.ts +42 -0
  30. package/app/server/index.ts +14 -0
  31. package/app/server/live/LiveAdminPanel.ts +173 -0
  32. package/app/server/live/LiveCounter.ts +1 -0
  33. package/app/server/live/LiveLocalCounter.ts +13 -8
  34. package/app/server/live/LiveProtectedChat.ts +150 -0
  35. package/app/server/live/LiveRoomChat.ts +45 -203
  36. package/app/server/routes/auth.routes.ts +278 -0
  37. package/app/server/routes/index.ts +2 -0
  38. package/config/index.ts +8 -0
  39. package/config/system/auth.config.ts +49 -0
  40. package/config/system/session.config.ts +33 -0
  41. package/core/client/LiveComponentsProvider.tsx +76 -5
  42. package/core/client/components/Live.tsx +2 -1
  43. package/core/client/hooks/useLiveComponent.ts +47 -4
  44. package/core/client/index.ts +2 -1
  45. package/core/framework/server.ts +36 -4
  46. package/core/plugins/built-in/live-components/commands/create-live-component.ts +15 -8
  47. package/core/plugins/built-in/monitoring/index.ts +10 -3
  48. package/core/plugins/built-in/vite/index.ts +95 -18
  49. package/core/plugins/config.ts +5 -4
  50. package/core/plugins/discovery.ts +11 -2
  51. package/core/plugins/manager.ts +11 -5
  52. package/core/plugins/module-resolver.ts +1 -1
  53. package/core/plugins/registry.ts +53 -25
  54. package/core/server/live/ComponentRegistry.ts +79 -24
  55. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  56. package/core/server/live/LiveLogger.ts +111 -0
  57. package/core/server/live/LiveRoomManager.ts +5 -4
  58. package/core/server/live/StateSignature.ts +644 -643
  59. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  60. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  61. package/core/server/live/auth/index.ts +19 -0
  62. package/core/server/live/auth/types.ts +179 -0
  63. package/core/server/live/auto-generated-components.ts +8 -2
  64. package/core/server/live/index.ts +16 -0
  65. package/core/server/live/websocket-plugin.ts +92 -16
  66. package/core/templates/create-project.ts +0 -3
  67. package/core/types/types.ts +133 -13
  68. package/core/utils/index.ts +17 -17
  69. package/core/utils/logger/index.ts +5 -2
  70. package/core/utils/version.ts +1 -1
  71. package/package.json +1 -8
  72. package/plugins/crypto-auth/index.ts +6 -0
  73. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  74. package/plugins/crypto-auth/server/index.ts +24 -21
  75. package/rest-tests/README.md +57 -0
  76. package/rest-tests/auth-token.http +113 -0
  77. package/rest-tests/auth.http +112 -0
  78. package/rest-tests/rooms-token.http +69 -0
  79. package/rest-tests/users-token.http +62 -0
  80. package/.dockerignore +0 -81
  81. package/Dockerfile +0 -70
  82. package/LIVE_COMPONENTS_REVIEW.md +0 -781
@@ -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
+ }
@@ -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
+ }