create-fluxstack 1.4.1 → 1.5.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/.env.example +8 -1
- package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +475 -0
- package/CRYPTO-AUTH-MIDDLEWARES.md +473 -0
- package/CRYPTO-AUTH-USAGE.md +491 -0
- package/EXEMPLO-ROTA-PROTEGIDA.md +347 -0
- package/QUICK-START-CRYPTO-AUTH.md +221 -0
- package/app/client/src/App.tsx +4 -1
- package/app/client/src/pages/CryptoAuthPage.tsx +394 -0
- package/app/server/index.ts +4 -0
- package/app/server/routes/crypto-auth-demo.routes.ts +167 -0
- package/app/server/routes/example-with-crypto-auth.routes.ts +235 -0
- package/app/server/routes/exemplo-posts.routes.ts +161 -0
- package/app/server/routes/index.ts +5 -1
- package/config/index.ts +9 -1
- package/core/cli/generators/plugin.ts +324 -34
- package/core/cli/generators/template-engine.ts +5 -0
- package/core/cli/plugin-discovery.ts +33 -12
- package/core/framework/server.ts +10 -0
- package/core/plugins/dependency-manager.ts +89 -22
- package/core/plugins/index.ts +4 -0
- package/core/plugins/manager.ts +3 -2
- package/core/plugins/module-resolver.ts +216 -0
- package/core/plugins/registry.ts +28 -1
- package/core/utils/logger/index.ts +4 -0
- package/fluxstack.config.ts +253 -114
- package/package.json +117 -117
- package/plugins/crypto-auth/README.md +722 -172
- package/plugins/crypto-auth/ai-context.md +1282 -0
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
- package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
- package/plugins/crypto-auth/client/components/index.ts +1 -4
- package/plugins/crypto-auth/client/index.ts +1 -1
- package/plugins/crypto-auth/config/index.ts +34 -0
- package/plugins/crypto-auth/index.ts +84 -152
- package/plugins/crypto-auth/package.json +65 -64
- package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
- package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
- package/plugins/crypto-auth/server/index.ts +15 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
- package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
- package/plugins/crypto-auth/server/middlewares.ts +19 -0
- package/test-crypto-auth.ts +101 -0
- package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
- package/plugins/crypto-auth/plugin.json +0 -29
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Command: make:protected-route
|
|
3
|
+
* Gera rotas protegidas automaticamente
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CliCommand, CliContext } from '@/core/plugins/types'
|
|
7
|
+
import { writeFileSync, existsSync, mkdirSync } from 'fs'
|
|
8
|
+
import { join } from 'path'
|
|
9
|
+
|
|
10
|
+
const ROUTE_TEMPLATES = {
|
|
11
|
+
required: (name: string, pascalName: string) => `/**
|
|
12
|
+
* ${pascalName} Routes
|
|
13
|
+
* 🔒 Autenticação obrigatória
|
|
14
|
+
* Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth required
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Elysia, t } from 'elysia'
|
|
18
|
+
import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
19
|
+
|
|
20
|
+
export const ${name}Routes = new Elysia({ prefix: '/${name}' })
|
|
21
|
+
|
|
22
|
+
// ========================================
|
|
23
|
+
// 🔒 ROTAS PROTEGIDAS (autenticação obrigatória)
|
|
24
|
+
// ========================================
|
|
25
|
+
.guard({}, (app) =>
|
|
26
|
+
app.use(cryptoAuthRequired())
|
|
27
|
+
|
|
28
|
+
// GET /api/${name}
|
|
29
|
+
.get('/', ({ request }) => {
|
|
30
|
+
const user = getCryptoAuthUser(request)!
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
message: 'Lista de ${name}',
|
|
35
|
+
user: {
|
|
36
|
+
publicKey: user.publicKey.substring(0, 16) + '...',
|
|
37
|
+
isAdmin: user.isAdmin
|
|
38
|
+
},
|
|
39
|
+
data: []
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// GET /api/${name}/:id
|
|
44
|
+
.get('/:id', ({ request, params }) => {
|
|
45
|
+
const user = getCryptoAuthUser(request)!
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
message: 'Detalhes de ${name}',
|
|
50
|
+
id: params.id,
|
|
51
|
+
user: user.publicKey.substring(0, 8) + '...'
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// POST /api/${name}
|
|
56
|
+
.post('/', ({ request, body }) => {
|
|
57
|
+
const user = getCryptoAuthUser(request)!
|
|
58
|
+
const data = body as any
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
message: '${pascalName} criado com sucesso',
|
|
63
|
+
createdBy: user.publicKey.substring(0, 8) + '...',
|
|
64
|
+
data
|
|
65
|
+
}
|
|
66
|
+
}, {
|
|
67
|
+
body: t.Object({
|
|
68
|
+
// Adicione seus campos aqui
|
|
69
|
+
name: t.String({ minLength: 3 })
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// PUT /api/${name}/:id
|
|
74
|
+
.put('/:id', ({ request, params, body }) => {
|
|
75
|
+
const user = getCryptoAuthUser(request)!
|
|
76
|
+
const data = body as any
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
message: '${pascalName} atualizado',
|
|
81
|
+
id: params.id,
|
|
82
|
+
updatedBy: user.publicKey.substring(0, 8) + '...',
|
|
83
|
+
data
|
|
84
|
+
}
|
|
85
|
+
}, {
|
|
86
|
+
body: t.Object({
|
|
87
|
+
name: t.String({ minLength: 3 })
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// DELETE /api/${name}/:id
|
|
92
|
+
.delete('/:id', ({ request, params }) => {
|
|
93
|
+
const user = getCryptoAuthUser(request)!
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
message: '${pascalName} deletado',
|
|
98
|
+
id: params.id,
|
|
99
|
+
deletedBy: user.publicKey.substring(0, 8) + '...'
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
`,
|
|
104
|
+
|
|
105
|
+
admin: (name: string, pascalName: string) => `/**
|
|
106
|
+
* ${pascalName} Routes
|
|
107
|
+
* 👑 Apenas administradores
|
|
108
|
+
* Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth admin
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
import { Elysia, t } from 'elysia'
|
|
112
|
+
import { cryptoAuthAdmin, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
113
|
+
|
|
114
|
+
export const ${name}Routes = new Elysia({ prefix: '/${name}' })
|
|
115
|
+
|
|
116
|
+
// ========================================
|
|
117
|
+
// 👑 ROTAS ADMIN (apenas administradores)
|
|
118
|
+
// ========================================
|
|
119
|
+
.guard({}, (app) =>
|
|
120
|
+
app.use(cryptoAuthAdmin())
|
|
121
|
+
|
|
122
|
+
// GET /api/${name}
|
|
123
|
+
.get('/', ({ request }) => {
|
|
124
|
+
const user = getCryptoAuthUser(request)!
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
message: 'Painel administrativo de ${name}',
|
|
129
|
+
admin: user.publicKey.substring(0, 8) + '...',
|
|
130
|
+
data: []
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// POST /api/${name}
|
|
135
|
+
.post('/', ({ request, body }) => {
|
|
136
|
+
const user = getCryptoAuthUser(request)!
|
|
137
|
+
const data = body as any
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
message: '${pascalName} criado pelo admin',
|
|
142
|
+
admin: user.publicKey.substring(0, 8) + '...',
|
|
143
|
+
data
|
|
144
|
+
}
|
|
145
|
+
}, {
|
|
146
|
+
body: t.Object({
|
|
147
|
+
name: t.String({ minLength: 3 })
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// DELETE /api/${name}/:id
|
|
152
|
+
.delete('/:id', ({ request, params }) => {
|
|
153
|
+
const user = getCryptoAuthUser(request)!
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
message: '${pascalName} deletado pelo admin',
|
|
158
|
+
id: params.id,
|
|
159
|
+
admin: user.publicKey.substring(0, 8) + '...'
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
`,
|
|
164
|
+
|
|
165
|
+
optional: (name: string, pascalName: string) => `/**
|
|
166
|
+
* ${pascalName} Routes
|
|
167
|
+
* 🌓 Autenticação opcional
|
|
168
|
+
* Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth optional
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
import { Elysia } from 'elysia'
|
|
172
|
+
import { cryptoAuthOptional, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
173
|
+
|
|
174
|
+
export const ${name}Routes = new Elysia({ prefix: '/${name}' })
|
|
175
|
+
|
|
176
|
+
// ========================================
|
|
177
|
+
// 🌐 ROTA PÚBLICA
|
|
178
|
+
// ========================================
|
|
179
|
+
.get('/', () => ({
|
|
180
|
+
success: true,
|
|
181
|
+
message: 'Lista pública de ${name}',
|
|
182
|
+
data: []
|
|
183
|
+
}))
|
|
184
|
+
|
|
185
|
+
// ========================================
|
|
186
|
+
// 🌓 ROTAS COM AUTH OPCIONAL
|
|
187
|
+
// ========================================
|
|
188
|
+
.guard({}, (app) =>
|
|
189
|
+
app.use(cryptoAuthOptional())
|
|
190
|
+
|
|
191
|
+
// GET /api/${name}/:id
|
|
192
|
+
.get('/:id', ({ request, params }) => {
|
|
193
|
+
const user = getCryptoAuthUser(request)
|
|
194
|
+
const isAuthenticated = !!user
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
id: params.id,
|
|
199
|
+
message: isAuthenticated
|
|
200
|
+
? \`${pascalName} personalizado para \${user.publicKey.substring(0, 8)}...\`
|
|
201
|
+
: 'Visualização pública de ${name}',
|
|
202
|
+
// Conteúdo extra apenas para autenticados
|
|
203
|
+
premiumContent: isAuthenticated ? 'Conteúdo exclusivo' : null,
|
|
204
|
+
viewer: isAuthenticated
|
|
205
|
+
? user.publicKey.substring(0, 8) + '...'
|
|
206
|
+
: 'Visitante anônimo'
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
)
|
|
210
|
+
`,
|
|
211
|
+
|
|
212
|
+
public: (name: string, pascalName: string) => `/**
|
|
213
|
+
* ${pascalName} Routes
|
|
214
|
+
* 🌐 Totalmente público
|
|
215
|
+
* Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth public
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
import { Elysia } from 'elysia'
|
|
219
|
+
|
|
220
|
+
export const ${name}Routes = new Elysia({ prefix: '/${name}' })
|
|
221
|
+
|
|
222
|
+
// ========================================
|
|
223
|
+
// 🌐 ROTAS PÚBLICAS
|
|
224
|
+
// ========================================
|
|
225
|
+
|
|
226
|
+
// GET /api/${name}
|
|
227
|
+
.get('/', () => ({
|
|
228
|
+
success: true,
|
|
229
|
+
message: 'Lista de ${name}',
|
|
230
|
+
data: []
|
|
231
|
+
}))
|
|
232
|
+
|
|
233
|
+
// GET /api/${name}/:id
|
|
234
|
+
.get('/:id', ({ params }) => ({
|
|
235
|
+
success: true,
|
|
236
|
+
id: params.id,
|
|
237
|
+
message: 'Detalhes de ${name}'
|
|
238
|
+
}))
|
|
239
|
+
`
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function toPascalCase(str: string): string {
|
|
243
|
+
return str
|
|
244
|
+
.split(/[-_]/)
|
|
245
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
246
|
+
.join('')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const makeProtectedRouteCommand: CliCommand = {
|
|
250
|
+
name: 'crypto-auth:make:route',
|
|
251
|
+
description: 'Gera um arquivo de rotas com proteção crypto-auth',
|
|
252
|
+
category: 'Crypto Auth',
|
|
253
|
+
aliases: ['crypto-auth:generate:route'],
|
|
254
|
+
|
|
255
|
+
arguments: [
|
|
256
|
+
{
|
|
257
|
+
name: 'name',
|
|
258
|
+
description: 'Nome da rota (ex: posts, users, admin)',
|
|
259
|
+
required: true,
|
|
260
|
+
type: 'string'
|
|
261
|
+
}
|
|
262
|
+
],
|
|
263
|
+
|
|
264
|
+
options: [
|
|
265
|
+
{
|
|
266
|
+
name: 'auth',
|
|
267
|
+
short: 'a',
|
|
268
|
+
description: 'Tipo de autenticação (required, admin, optional, public)',
|
|
269
|
+
type: 'string',
|
|
270
|
+
default: 'required',
|
|
271
|
+
choices: ['required', 'admin', 'optional', 'public']
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: 'output',
|
|
275
|
+
short: 'o',
|
|
276
|
+
description: 'Diretório de saída (padrão: app/server/routes)',
|
|
277
|
+
type: 'string',
|
|
278
|
+
default: 'app/server/routes'
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'force',
|
|
282
|
+
short: 'f',
|
|
283
|
+
description: 'Sobrescrever arquivo existente',
|
|
284
|
+
type: 'boolean',
|
|
285
|
+
default: false
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
|
|
289
|
+
examples: [
|
|
290
|
+
'flux crypto-auth:make:route posts',
|
|
291
|
+
'flux crypto-auth:make:route admin --auth admin',
|
|
292
|
+
'flux crypto-auth:make:route feed --auth optional',
|
|
293
|
+
'flux crypto-auth:make:route articles --auth required --force'
|
|
294
|
+
],
|
|
295
|
+
|
|
296
|
+
handler: async (args, options, context) => {
|
|
297
|
+
const [name] = args as [string]
|
|
298
|
+
const { auth, output, force } = options as {
|
|
299
|
+
auth: 'required' | 'admin' | 'optional' | 'public'
|
|
300
|
+
output: string
|
|
301
|
+
force: boolean
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Validar nome
|
|
305
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
306
|
+
console.error('❌ Nome inválido. Use apenas letras minúsculas, números e hífens.')
|
|
307
|
+
console.error(' Exemplos válidos: posts, my-posts, user-settings')
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const pascalName = toPascalCase(name)
|
|
312
|
+
const fileName = `${name}.routes.ts`
|
|
313
|
+
const outputDir = join(context.workingDir, output)
|
|
314
|
+
const filePath = join(outputDir, fileName)
|
|
315
|
+
|
|
316
|
+
// Verificar se arquivo existe
|
|
317
|
+
if (existsSync(filePath) && !force) {
|
|
318
|
+
console.error(`❌ Arquivo já existe: ${filePath}`)
|
|
319
|
+
console.error(' Use --force para sobrescrever')
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Criar diretório se não existir
|
|
324
|
+
if (!existsSync(outputDir)) {
|
|
325
|
+
mkdirSync(outputDir, { recursive: true })
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Gerar código
|
|
329
|
+
const template = ROUTE_TEMPLATES[auth]
|
|
330
|
+
const code = template(name, pascalName)
|
|
331
|
+
|
|
332
|
+
// Escrever arquivo
|
|
333
|
+
writeFileSync(filePath, code, 'utf-8')
|
|
334
|
+
|
|
335
|
+
console.log(`\n✅ Rota criada com sucesso!`)
|
|
336
|
+
console.log(`📁 Arquivo: ${filePath}`)
|
|
337
|
+
console.log(`🔐 Tipo de auth: ${auth}`)
|
|
338
|
+
|
|
339
|
+
// Instruções de uso
|
|
340
|
+
console.log(`\n📋 Próximos passos:`)
|
|
341
|
+
console.log(`\n1. Importar a rota em app/server/routes/index.ts:`)
|
|
342
|
+
console.log(` import { ${name}Routes } from './${name}.routes'`)
|
|
343
|
+
console.log(`\n2. Registrar no apiRoutes:`)
|
|
344
|
+
console.log(` export const apiRoutes = new Elysia({ prefix: '/api' })`)
|
|
345
|
+
console.log(` .use(${name}Routes)`)
|
|
346
|
+
console.log(`\n3. Rotas disponíveis:`)
|
|
347
|
+
|
|
348
|
+
const routes = {
|
|
349
|
+
required: [
|
|
350
|
+
`GET /api/${name}`,
|
|
351
|
+
`GET /api/${name}/:id`,
|
|
352
|
+
`POST /api/${name}`,
|
|
353
|
+
`PUT /api/${name}/:id`,
|
|
354
|
+
`DELETE /api/${name}/:id`
|
|
355
|
+
],
|
|
356
|
+
admin: [
|
|
357
|
+
`GET /api/${name}`,
|
|
358
|
+
`POST /api/${name}`,
|
|
359
|
+
`DELETE /api/${name}/:id`
|
|
360
|
+
],
|
|
361
|
+
optional: [
|
|
362
|
+
`GET /api/${name}`,
|
|
363
|
+
`GET /api/${name}/:id`
|
|
364
|
+
],
|
|
365
|
+
public: [
|
|
366
|
+
`GET /api/${name}`,
|
|
367
|
+
`GET /api/${name}/:id`
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
routes[auth].forEach(route => console.log(` ${route}`))
|
|
372
|
+
|
|
373
|
+
console.log(`\n4. Testar (sem auth):`)
|
|
374
|
+
console.log(` curl http://localhost:3000/api/${name}`)
|
|
375
|
+
|
|
376
|
+
if (auth !== 'public') {
|
|
377
|
+
const expectedStatus = auth === 'optional' ? '200 (sem conteúdo premium)' : '401'
|
|
378
|
+
console.log(` Esperado: ${expectedStatus}`)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
console.log(`\n🚀 Pronto! Inicie o servidor com: bun run dev`)
|
|
382
|
+
}
|
|
383
|
+
}
|