create-fluxstack 1.0.21 → 1.1.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/app/server/live/register-components.ts +6 -26
- package/core/build/bundler.ts +53 -5
- package/core/build/flux-plugins-generator.ts +315 -0
- package/core/build/index.ts +1 -3
- package/core/build/live-components-generator.ts +231 -0
- package/core/build/optimizer.ts +2 -54
- package/core/cli/index.ts +2 -1
- package/core/config/env.ts +3 -1
- package/core/config/schema.ts +0 -3
- package/core/framework/server.ts +33 -1
- package/core/plugins/built-in/static/index.ts +24 -10
- package/core/plugins/built-in/vite/index.ts +92 -56
- package/core/plugins/manager.ts +51 -8
- package/core/utils/helpers.ts +9 -3
- package/fluxstack.config.ts +4 -10
- package/package.json +4 -3
- package/plugins/crypto-auth/README.md +238 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +325 -0
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +190 -0
- package/plugins/crypto-auth/client/components/LoginButton.tsx +155 -0
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +109 -0
- package/plugins/crypto-auth/client/components/SessionInfo.tsx +242 -0
- package/plugins/crypto-auth/client/components/index.ts +15 -0
- package/plugins/crypto-auth/client/index.ts +12 -0
- package/plugins/crypto-auth/index.ts +230 -0
- package/plugins/crypto-auth/package.json +65 -0
- package/plugins/crypto-auth/plugin.json +29 -0
- package/plugins/crypto-auth/server/AuthMiddleware.ts +237 -0
- package/plugins/crypto-auth/server/CryptoAuthService.ts +293 -0
- package/plugins/crypto-auth/server/index.ts +9 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serviço de Autenticação Criptográfica
|
|
3
|
+
* Implementa autenticação baseada em Ed25519
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
7
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
8
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
9
|
+
export interface Logger {
|
|
10
|
+
debug(message: string, meta?: any): void
|
|
11
|
+
info(message: string, meta?: any): void
|
|
12
|
+
warn(message: string, meta?: any): void
|
|
13
|
+
error(message: string, meta?: any): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SessionData {
|
|
17
|
+
sessionId: string
|
|
18
|
+
publicKey: string
|
|
19
|
+
createdAt: Date
|
|
20
|
+
lastUsed: Date
|
|
21
|
+
isAdmin: boolean
|
|
22
|
+
permissions: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AuthResult {
|
|
26
|
+
success: boolean
|
|
27
|
+
sessionId?: string
|
|
28
|
+
error?: string
|
|
29
|
+
user?: {
|
|
30
|
+
sessionId: string
|
|
31
|
+
isAdmin: boolean
|
|
32
|
+
isSuperAdmin: boolean
|
|
33
|
+
permissions: string[]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CryptoAuthConfig {
|
|
38
|
+
sessionTimeout: number
|
|
39
|
+
maxTimeDrift: number
|
|
40
|
+
adminKeys: string[]
|
|
41
|
+
logger?: Logger
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class CryptoAuthService {
|
|
45
|
+
private sessions: Map<string, SessionData> = new Map()
|
|
46
|
+
private config: CryptoAuthConfig
|
|
47
|
+
private logger?: Logger
|
|
48
|
+
|
|
49
|
+
constructor(config: CryptoAuthConfig) {
|
|
50
|
+
this.config = config
|
|
51
|
+
this.logger = config.logger
|
|
52
|
+
|
|
53
|
+
// Limpar sessões expiradas a cada 5 minutos
|
|
54
|
+
setInterval(() => {
|
|
55
|
+
this.cleanupExpiredSessions()
|
|
56
|
+
}, 5 * 60 * 1000)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inicializar uma nova sessão
|
|
61
|
+
*/
|
|
62
|
+
async initializeSession(data: { publicKey?: string }): Promise<AuthResult> {
|
|
63
|
+
try {
|
|
64
|
+
let publicKey: string
|
|
65
|
+
|
|
66
|
+
if (data.publicKey) {
|
|
67
|
+
// Validar chave pública fornecida
|
|
68
|
+
if (!this.isValidPublicKey(data.publicKey)) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: "Chave pública inválida"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
publicKey = data.publicKey
|
|
75
|
+
} else {
|
|
76
|
+
// Gerar novo par de chaves
|
|
77
|
+
const privateKey = ed25519.utils.randomPrivateKey()
|
|
78
|
+
publicKey = bytesToHex(ed25519.getPublicKey(privateKey))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sessionId = publicKey
|
|
82
|
+
const isAdmin = this.config.adminKeys.includes(publicKey)
|
|
83
|
+
|
|
84
|
+
const sessionData: SessionData = {
|
|
85
|
+
sessionId,
|
|
86
|
+
publicKey,
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
lastUsed: new Date(),
|
|
89
|
+
isAdmin,
|
|
90
|
+
permissions: isAdmin ? ['admin', 'read', 'write'] : ['read']
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.sessions.set(sessionId, sessionData)
|
|
94
|
+
|
|
95
|
+
this.logger?.info("Nova sessão inicializada", {
|
|
96
|
+
sessionId: sessionId.substring(0, 8) + "...",
|
|
97
|
+
isAdmin,
|
|
98
|
+
permissions: sessionData.permissions
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
sessionId,
|
|
104
|
+
user: {
|
|
105
|
+
sessionId,
|
|
106
|
+
isAdmin,
|
|
107
|
+
isSuperAdmin: isAdmin,
|
|
108
|
+
permissions: sessionData.permissions
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.logger?.error("Erro ao inicializar sessão", { error })
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: "Erro interno ao inicializar sessão"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validar uma sessão com assinatura
|
|
122
|
+
*/
|
|
123
|
+
async validateSession(data: {
|
|
124
|
+
sessionId: string
|
|
125
|
+
timestamp: number
|
|
126
|
+
nonce: string
|
|
127
|
+
signature: string
|
|
128
|
+
message?: string
|
|
129
|
+
}): Promise<AuthResult> {
|
|
130
|
+
try {
|
|
131
|
+
const { sessionId, timestamp, nonce, signature, message = "" } = data
|
|
132
|
+
|
|
133
|
+
// Verificar se a sessão existe
|
|
134
|
+
const session = this.sessions.get(sessionId)
|
|
135
|
+
if (!session) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
error: "Sessão não encontrada"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Verificar se a sessão não expirou
|
|
143
|
+
const now = Date.now()
|
|
144
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
145
|
+
if (sessionAge > this.config.sessionTimeout) {
|
|
146
|
+
this.sessions.delete(sessionId)
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: "Sessão expirada"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Verificar drift de tempo
|
|
154
|
+
const timeDrift = Math.abs(now - timestamp)
|
|
155
|
+
if (timeDrift > this.config.maxTimeDrift) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error: "Timestamp inválido"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Construir mensagem para verificação
|
|
163
|
+
const messageToVerify = `${sessionId}:${timestamp}:${nonce}:${message}`
|
|
164
|
+
const messageHash = sha256(new TextEncoder().encode(messageToVerify))
|
|
165
|
+
|
|
166
|
+
// Verificar assinatura
|
|
167
|
+
const publicKeyBytes = hexToBytes(session.publicKey)
|
|
168
|
+
const signatureBytes = hexToBytes(signature)
|
|
169
|
+
|
|
170
|
+
const isValidSignature = ed25519.verify(signatureBytes, messageHash, publicKeyBytes)
|
|
171
|
+
|
|
172
|
+
if (!isValidSignature) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: "Assinatura inválida"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Atualizar último uso da sessão
|
|
180
|
+
session.lastUsed = new Date()
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
sessionId,
|
|
185
|
+
user: {
|
|
186
|
+
sessionId,
|
|
187
|
+
isAdmin: session.isAdmin,
|
|
188
|
+
isSuperAdmin: session.isAdmin,
|
|
189
|
+
permissions: session.permissions
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger?.error("Erro ao validar sessão", { error })
|
|
194
|
+
return {
|
|
195
|
+
success: false,
|
|
196
|
+
error: "Erro interno ao validar sessão"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Obter informações da sessão
|
|
203
|
+
*/
|
|
204
|
+
async getSessionInfo(sessionId: string): Promise<SessionData | null> {
|
|
205
|
+
const session = this.sessions.get(sessionId)
|
|
206
|
+
if (!session) {
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Verificar se não expirou
|
|
211
|
+
const now = Date.now()
|
|
212
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
213
|
+
if (sessionAge > this.config.sessionTimeout) {
|
|
214
|
+
this.sessions.delete(sessionId)
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { ...session }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Destruir uma sessão
|
|
223
|
+
*/
|
|
224
|
+
async destroySession(sessionId: string): Promise<void> {
|
|
225
|
+
this.sessions.delete(sessionId)
|
|
226
|
+
this.logger?.info("Sessão destruída", {
|
|
227
|
+
sessionId: sessionId.substring(0, 8) + "..."
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Verificar se uma chave pública é válida
|
|
233
|
+
*/
|
|
234
|
+
private isValidPublicKey(publicKey: string): boolean {
|
|
235
|
+
try {
|
|
236
|
+
if (publicKey.length !== 64) {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const bytes = hexToBytes(publicKey)
|
|
241
|
+
return bytes.length === 32
|
|
242
|
+
} catch {
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Limpar sessões expiradas
|
|
249
|
+
*/
|
|
250
|
+
private cleanupExpiredSessions(): void {
|
|
251
|
+
const now = Date.now()
|
|
252
|
+
let cleanedCount = 0
|
|
253
|
+
|
|
254
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
255
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
256
|
+
if (sessionAge > this.config.sessionTimeout) {
|
|
257
|
+
this.sessions.delete(sessionId)
|
|
258
|
+
cleanedCount++
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (cleanedCount > 0) {
|
|
263
|
+
this.logger?.debug(`Limpeza de sessões: ${cleanedCount} sessões expiradas removidas`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Obter estatísticas das sessões
|
|
269
|
+
*/
|
|
270
|
+
getStats() {
|
|
271
|
+
const now = Date.now()
|
|
272
|
+
let activeSessions = 0
|
|
273
|
+
let adminSessions = 0
|
|
274
|
+
|
|
275
|
+
for (const session of this.sessions.values()) {
|
|
276
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
277
|
+
if (sessionAge <= this.config.sessionTimeout) {
|
|
278
|
+
activeSessions++
|
|
279
|
+
if (session.isAdmin) {
|
|
280
|
+
adminSessions++
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
totalSessions: this.sessions.size,
|
|
287
|
+
activeSessions,
|
|
288
|
+
adminSessions,
|
|
289
|
+
sessionTimeout: this.config.sessionTimeout,
|
|
290
|
+
adminKeys: this.config.adminKeys.length
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exportações do servidor de autenticação
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { CryptoAuthService } from './CryptoAuthService'
|
|
6
|
+
export type { SessionData, AuthResult, CryptoAuthConfig } from './CryptoAuthService'
|
|
7
|
+
|
|
8
|
+
export { AuthMiddleware } from './AuthMiddleware'
|
|
9
|
+
export type { AuthMiddlewareConfig, AuthMiddlewareResult } from './AuthMiddleware'
|
package/vite.config.ts
CHANGED
|
@@ -23,6 +23,22 @@ export default defineConfig({
|
|
|
23
23
|
},
|
|
24
24
|
})
|
|
25
25
|
],
|
|
26
|
+
define: {
|
|
27
|
+
__DEFINES__: JSON.stringify({}),
|
|
28
|
+
global: 'globalThis',
|
|
29
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
|
30
|
+
__HMR_CONFIG_NAME__: JSON.stringify('vite.config.ts'),
|
|
31
|
+
__BASE__: JSON.stringify('/'),
|
|
32
|
+
__SERVER_HOST__: JSON.stringify('localhost'),
|
|
33
|
+
__HMR_PROTOCOL__: JSON.stringify('ws'),
|
|
34
|
+
__HMR_PORT__: JSON.stringify(5173),
|
|
35
|
+
__HMR_HOSTNAME__: JSON.stringify('localhost'),
|
|
36
|
+
__HMR_BASE__: JSON.stringify('/'),
|
|
37
|
+
__HMR_DIRECT_TARGET__: JSON.stringify('localhost:5173'),
|
|
38
|
+
__HMR_ENABLE_OVERLAY__: JSON.stringify(true),
|
|
39
|
+
__HMR_TIMEOUT__: JSON.stringify(30000),
|
|
40
|
+
__WS_TOKEN__: JSON.stringify(''),
|
|
41
|
+
},
|
|
26
42
|
root: 'app/client',
|
|
27
43
|
server: {
|
|
28
44
|
port: 5173,
|