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.
- package/LLMD/INDEX.md +8 -1
- package/LLMD/agent.md +867 -0
- package/LLMD/config/environment-vars.md +30 -0
- package/LLMD/resources/live-auth.md +447 -0
- package/LLMD/resources/live-components.md +79 -21
- package/LLMD/resources/live-logging.md +158 -0
- package/LLMD/resources/live-upload.md +1 -1
- package/LLMD/resources/rest-auth.md +290 -0
- package/README.md +520 -340
- package/app/client/src/App.tsx +11 -0
- package/app/client/src/components/AppLayout.tsx +1 -0
- package/app/client/src/live/AuthDemo.tsx +332 -0
- package/app/server/auth/AuthManager.ts +213 -0
- package/app/server/auth/DevAuthProvider.ts +66 -0
- package/app/server/auth/HashManager.ts +123 -0
- package/app/server/auth/JWTAuthProvider.example.ts +101 -0
- package/app/server/auth/RateLimiter.ts +106 -0
- package/app/server/auth/contracts.ts +192 -0
- package/app/server/auth/guards/SessionGuard.ts +167 -0
- package/app/server/auth/guards/TokenGuard.ts +202 -0
- package/app/server/auth/index.ts +174 -0
- package/app/server/auth/middleware.ts +163 -0
- package/app/server/auth/providers/InMemoryProvider.ts +162 -0
- package/app/server/auth/sessions/SessionManager.ts +164 -0
- package/app/server/cache/CacheManager.ts +81 -0
- package/app/server/cache/MemoryDriver.ts +112 -0
- package/app/server/cache/contracts.ts +49 -0
- package/app/server/cache/index.ts +42 -0
- package/app/server/index.ts +14 -0
- package/app/server/live/LiveAdminPanel.ts +173 -0
- package/app/server/live/LiveCounter.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +13 -8
- package/app/server/live/LiveProtectedChat.ts +150 -0
- package/app/server/routes/auth.routes.ts +278 -0
- package/app/server/routes/index.ts +2 -0
- package/config/index.ts +8 -0
- package/config/system/auth.config.ts +49 -0
- package/config/system/session.config.ts +33 -0
- package/core/client/LiveComponentsProvider.tsx +76 -5
- package/core/client/components/Live.tsx +2 -1
- package/core/client/hooks/useLiveComponent.ts +47 -4
- package/core/client/index.ts +2 -1
- package/core/framework/server.ts +36 -4
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +15 -8
- package/core/plugins/built-in/monitoring/index.ts +10 -3
- package/core/plugins/built-in/vite/index.ts +95 -18
- package/core/plugins/config.ts +5 -4
- package/core/plugins/discovery.ts +11 -2
- package/core/plugins/manager.ts +11 -5
- package/core/plugins/module-resolver.ts +1 -1
- package/core/plugins/registry.ts +53 -25
- package/core/server/live/ComponentRegistry.ts +79 -24
- package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
- package/core/server/live/LiveLogger.ts +111 -0
- package/core/server/live/LiveRoomManager.ts +5 -4
- package/core/server/live/StateSignature.ts +644 -643
- package/core/server/live/auth/LiveAuthContext.ts +71 -0
- package/core/server/live/auth/LiveAuthManager.ts +304 -0
- package/core/server/live/auth/index.ts +19 -0
- package/core/server/live/auth/types.ts +179 -0
- package/core/server/live/auto-generated-components.ts +8 -2
- package/core/server/live/index.ts +16 -0
- package/core/server/live/websocket-plugin.ts +92 -16
- package/core/templates/create-project.ts +0 -3
- package/core/types/types.ts +133 -13
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/index.ts +5 -2
- package/core/utils/version.ts +1 -1
- package/package.json +1 -8
- package/plugins/crypto-auth/index.ts +6 -0
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
- package/plugins/crypto-auth/server/index.ts +24 -21
- package/rest-tests/README.md +57 -0
- package/rest-tests/auth-token.http +113 -0
- package/rest-tests/auth.http +112 -0
- package/rest-tests/rooms-token.http +69 -0
- package/rest-tests/users-token.http +62 -0
- package/.dockerignore +0 -81
- package/Dockerfile +0 -70
- package/LIVE_COMPONENTS_REVIEW.md +0 -781
|
@@ -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
|