create-fluxstack 1.5.0 → 1.5.2

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 (50) hide show
  1. package/.env.example +1 -8
  2. package/app/client/src/App.tsx +1 -4
  3. package/app/server/index.ts +0 -4
  4. package/app/server/routes/index.ts +1 -5
  5. package/config/index.ts +1 -9
  6. package/core/cli/generators/plugin.ts +34 -324
  7. package/core/cli/generators/template-engine.ts +0 -5
  8. package/core/cli/plugin-discovery.ts +12 -33
  9. package/core/framework/server.ts +0 -10
  10. package/core/plugins/dependency-manager.ts +22 -89
  11. package/core/plugins/index.ts +0 -4
  12. package/core/plugins/manager.ts +2 -3
  13. package/core/plugins/registry.ts +1 -28
  14. package/core/utils/logger/index.ts +0 -4
  15. package/core/utils/version.ts +1 -1
  16. package/fluxstack.config.ts +114 -253
  17. package/package.json +117 -117
  18. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +0 -475
  19. package/CRYPTO-AUTH-MIDDLEWARES.md +0 -473
  20. package/CRYPTO-AUTH-USAGE.md +0 -491
  21. package/EXEMPLO-ROTA-PROTEGIDA.md +0 -347
  22. package/QUICK-START-CRYPTO-AUTH.md +0 -221
  23. package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
  24. package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
  25. package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
  26. package/app/server/routes/exemplo-posts.routes.ts +0 -161
  27. package/core/plugins/module-resolver.ts +0 -216
  28. package/plugins/crypto-auth/README.md +0 -788
  29. package/plugins/crypto-auth/ai-context.md +0 -1282
  30. package/plugins/crypto-auth/cli/make-protected-route.command.ts +0 -383
  31. package/plugins/crypto-auth/client/CryptoAuthClient.ts +0 -302
  32. package/plugins/crypto-auth/client/components/AuthProvider.tsx +0 -131
  33. package/plugins/crypto-auth/client/components/LoginButton.tsx +0 -138
  34. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +0 -89
  35. package/plugins/crypto-auth/client/components/index.ts +0 -12
  36. package/plugins/crypto-auth/client/index.ts +0 -12
  37. package/plugins/crypto-auth/config/index.ts +0 -34
  38. package/plugins/crypto-auth/index.ts +0 -162
  39. package/plugins/crypto-auth/package.json +0 -66
  40. package/plugins/crypto-auth/server/AuthMiddleware.ts +0 -181
  41. package/plugins/crypto-auth/server/CryptoAuthService.ts +0 -186
  42. package/plugins/crypto-auth/server/index.ts +0 -22
  43. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +0 -65
  44. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +0 -26
  45. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +0 -76
  46. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +0 -45
  47. package/plugins/crypto-auth/server/middlewares/helpers.ts +0 -140
  48. package/plugins/crypto-auth/server/middlewares/index.ts +0 -22
  49. package/plugins/crypto-auth/server/middlewares.ts +0 -19
  50. package/test-crypto-auth.ts +0 -101
@@ -1,235 +0,0 @@
1
- /**
2
- * Exemplo de uso dos middlewares crypto-auth
3
- * Demonstra como proteger rotas com autenticação criptográfica
4
- */
5
-
6
- import { Elysia, t } from 'elysia'
7
- import {
8
- cryptoAuthRequired,
9
- cryptoAuthAdmin,
10
- cryptoAuthPermissions,
11
- cryptoAuthOptional,
12
- getCryptoAuthUser,
13
- isCryptoAuthAdmin
14
- } from '@/plugins/crypto-auth/server'
15
-
16
- // ========================================
17
- // 1️⃣ ROTAS QUE REQUEREM AUTENTICAÇÃO
18
- // ========================================
19
-
20
- export const protectedRoutes = new Elysia()
21
- // ✅ Aplica middleware a TODAS as rotas deste grupo
22
- .use(cryptoAuthRequired())
23
-
24
- // Agora TODAS as rotas abaixo requerem autenticação
25
- .get('/users/me', ({ request }) => {
26
- const user = getCryptoAuthUser(request)!
27
-
28
- return {
29
- profile: {
30
- publicKey: user.publicKey,
31
- isAdmin: user.isAdmin,
32
- permissions: user.permissions
33
- }
34
- }
35
- })
36
-
37
- .get('/users', ({ request }) => {
38
- const user = getCryptoAuthUser(request)!
39
-
40
- return {
41
- users: [
42
- { id: 1, name: 'João' },
43
- { id: 2, name: 'Maria' }
44
- ],
45
- requestedBy: user.publicKey.substring(0, 16) + '...'
46
- }
47
- })
48
-
49
- .post('/posts', ({ request, body }) => {
50
- const user = getCryptoAuthUser(request)!
51
- const { title, content } = body as { title: string; content: string }
52
-
53
- return {
54
- success: true,
55
- post: {
56
- title,
57
- content,
58
- author: user.publicKey,
59
- createdAt: new Date()
60
- }
61
- }
62
- }, {
63
- body: t.Object({
64
- title: t.String(),
65
- content: t.String()
66
- })
67
- })
68
-
69
- // ========================================
70
- // 2️⃣ ROTAS QUE REQUEREM ADMIN
71
- // ========================================
72
-
73
- export const adminRoutes = new Elysia()
74
- // ✅ Apenas admins podem acessar
75
- .use(cryptoAuthAdmin())
76
-
77
- .get('/admin/stats', () => ({
78
- totalUsers: 100,
79
- totalPosts: 500,
80
- systemHealth: 'optimal'
81
- }))
82
-
83
- .delete('/admin/users/:id', ({ params, request }) => {
84
- const user = getCryptoAuthUser(request)!
85
-
86
- return {
87
- success: true,
88
- message: `Usuário ${params.id} deletado`,
89
- deletedBy: user.publicKey
90
- }
91
- })
92
-
93
- .post('/admin/broadcast', ({ body }) => ({
94
- success: true,
95
- message: 'Mensagem enviada para todos os usuários',
96
- content: body
97
- }), {
98
- body: t.Object({
99
- message: t.String()
100
- })
101
- })
102
-
103
- // ========================================
104
- // 3️⃣ ROTAS COM PERMISSÕES ESPECÍFICAS
105
- // ========================================
106
-
107
- export const writeRoutes = new Elysia()
108
- // ✅ Requer permissão 'write'
109
- .use(cryptoAuthPermissions(['write']))
110
-
111
- .put('/posts/:id', ({ params, body }) => ({
112
- success: true,
113
- message: `Post ${params.id} atualizado`,
114
- data: body
115
- }), {
116
- body: t.Object({
117
- title: t.Optional(t.String()),
118
- content: t.Optional(t.String())
119
- })
120
- })
121
-
122
- .patch('/posts/:id/publish', ({ params }) => ({
123
- success: true,
124
- message: `Post ${params.id} publicado`
125
- }))
126
-
127
- // ========================================
128
- // 4️⃣ ROTAS MISTAS (Opcional)
129
- // ========================================
130
-
131
- export const mixedRoutes = new Elysia()
132
- // ✅ Autenticação OPCIONAL - adiciona user se autenticado
133
- .use(cryptoAuthOptional())
134
-
135
- // Comportamento diferente se autenticado
136
- .get('/posts/:id', ({ request, params }) => {
137
- const user = getCryptoAuthUser(request)
138
- const isAdmin = isCryptoAuthAdmin(request)
139
-
140
- return {
141
- post: {
142
- id: params.id,
143
- title: 'Título do Post',
144
- // Mostra conteúdo completo apenas se autenticado
145
- content: user ? 'Conteúdo completo do post...' : 'Prévia...',
146
- author: 'João'
147
- },
148
- viewer: user ? {
149
- publicKey: user.publicKey.substring(0, 16) + '...',
150
- canEdit: isAdmin,
151
- canComment: true
152
- } : {
153
- canEdit: false,
154
- canComment: false
155
- }
156
- }
157
- })
158
-
159
- .get('/posts', ({ request }) => {
160
- const user = getCryptoAuthUser(request)
161
-
162
- return {
163
- posts: [
164
- { id: 1, title: 'Post 1' },
165
- { id: 2, title: 'Post 2' }
166
- ],
167
- authenticated: !!user
168
- }
169
- })
170
-
171
- // ========================================
172
- // 5️⃣ ROTAS COM VERIFICAÇÃO MANUAL
173
- // ========================================
174
-
175
- export const customRoutes = new Elysia()
176
- .use(cryptoAuthRequired())
177
-
178
- .get('/posts/:id/edit', ({ request, params, set }) => {
179
- const user = getCryptoAuthUser(request)!
180
-
181
- // Verificação customizada - apenas autor ou admin
182
- const post = { id: params.id, authorKey: 'abc123...' } // Buscar do DB
183
-
184
- const canEdit = user.isAdmin || user.publicKey === post.authorKey
185
-
186
- if (!canEdit) {
187
- set.status = 403
188
- return {
189
- error: 'Apenas o autor ou admin podem editar este post'
190
- }
191
- }
192
-
193
- return {
194
- post,
195
- canEdit: true
196
- }
197
- })
198
-
199
- // ========================================
200
- // 6️⃣ COMBINANDO MÚLTIPLOS MIDDLEWARES
201
- // ========================================
202
-
203
- export const combinedRoutes = new Elysia({ prefix: '/api/v1' })
204
- // Primeiro grupo - rotas públicas
205
- .get('/health', () => ({ status: 'ok' }))
206
-
207
- .get('/posts', () => ({
208
- posts: [/* ... */]
209
- }))
210
-
211
- // Segundo grupo - rotas protegidas
212
- .group('/users', (app) => app
213
- .use(cryptoAuthRequired())
214
- .get('/', () => ({ users: [] }))
215
- .post('/', ({ body }) => ({ created: body }))
216
- )
217
-
218
- // Terceiro grupo - rotas admin
219
- .group('/admin', (app) => app
220
- .use(cryptoAuthAdmin())
221
- .get('/stats', () => ({ stats: {} }))
222
- .delete('/users/:id', ({ params }) => ({ deleted: params.id }))
223
- )
224
-
225
- // ========================================
226
- // 7️⃣ EXPORTAR TODAS AS ROTAS
227
- // ========================================
228
-
229
- export const allExampleRoutes = new Elysia()
230
- .use(protectedRoutes)
231
- .use(adminRoutes)
232
- .use(writeRoutes)
233
- .use(mixedRoutes)
234
- .use(customRoutes)
235
- .use(combinedRoutes)
@@ -1,161 +0,0 @@
1
- /**
2
- * 🎓 EXEMPLO PRÁTICO: Como criar rotas com Crypto-Auth
3
- *
4
- * Este arquivo demonstra como um desenvolvedor cria rotas usando
5
- * o sistema de autenticação crypto-auth do FluxStack.
6
- */
7
-
8
- import { Elysia, t } from 'elysia'
9
- import {
10
- cryptoAuthRequired,
11
- cryptoAuthAdmin,
12
- cryptoAuthOptional,
13
- getCryptoAuthUser
14
- } from '@/plugins/crypto-auth/server'
15
-
16
- // Mock database (simulação)
17
- const posts = [
18
- { id: 1, title: 'Post Público 1', content: 'Conteúdo aberto', public: true },
19
- { id: 2, title: 'Post Público 2', content: 'Conteúdo aberto', public: true }
20
- ]
21
-
22
- export const exemploPostsRoutes = new Elysia({ prefix: '/exemplo-posts' })
23
-
24
- // ========================================
25
- // 🌐 ROTA PÚBLICA - Qualquer um acessa
26
- // ========================================
27
- .get('/', () => {
28
- return {
29
- success: true,
30
- message: 'Lista pública de posts',
31
- posts: posts.filter(p => p.public)
32
- }
33
- })
34
-
35
- // ========================================
36
- // 🌓 ROTA COM AUTH OPCIONAL
37
- // Funciona com ou sem autenticação
38
- // ========================================
39
- .guard({}, (app) =>
40
- app.use(cryptoAuthOptional())
41
-
42
- .get('/:id', ({ request, params }) => {
43
- const user = getCryptoAuthUser(request)
44
- const isAuthenticated = !!user
45
- const post = posts.find(p => p.id === parseInt(params.id))
46
-
47
- if (!post) {
48
- return { success: false, error: 'Post não encontrado' }
49
- }
50
-
51
- return {
52
- success: true,
53
- post: {
54
- ...post,
55
- // ✅ Conteúdo extra apenas para autenticados
56
- premiumContent: isAuthenticated
57
- ? 'Conteúdo premium exclusivo para usuários autenticados!'
58
- : null,
59
- viewer: isAuthenticated
60
- ? `Autenticado: ${user.publicKey.substring(0, 8)}...`
61
- : 'Visitante anônimo'
62
- }
63
- }
64
- })
65
- )
66
-
67
- // ========================================
68
- // 🔒 ROTAS PROTEGIDAS - Requer autenticação
69
- // ========================================
70
- .guard({}, (app) =>
71
- app.use(cryptoAuthRequired())
72
-
73
- // GET /api/exemplo-posts/meus-posts
74
- .get('/meus-posts', ({ request }) => {
75
- const user = getCryptoAuthUser(request)!
76
-
77
- return {
78
- success: true,
79
- message: `Posts criados por você`,
80
- user: {
81
- publicKey: user.publicKey.substring(0, 16) + '...',
82
- isAdmin: user.isAdmin
83
- },
84
- posts: [
85
- {
86
- id: 999,
87
- title: 'Meu Post Privado',
88
- content: 'Só eu posso ver',
89
- author: user.publicKey
90
- }
91
- ]
92
- }
93
- })
94
-
95
- // POST /api/exemplo-posts/criar
96
- .post('/criar', ({ request, body }) => {
97
- const user = getCryptoAuthUser(request)!
98
- const { title, content } = body as { title: string; content: string }
99
-
100
- const newPost = {
101
- id: Date.now(),
102
- title,
103
- content,
104
- author: user.publicKey,
105
- createdAt: new Date().toISOString(),
106
- public: false
107
- }
108
-
109
- posts.push(newPost)
110
-
111
- return {
112
- success: true,
113
- message: 'Post criado com sucesso!',
114
- post: newPost
115
- }
116
- }, {
117
- body: t.Object({
118
- title: t.String({ minLength: 3 }),
119
- content: t.String({ minLength: 10 })
120
- })
121
- })
122
- )
123
-
124
- // ========================================
125
- // 👑 ROTAS ADMIN - Apenas administradores
126
- // ========================================
127
- .guard({}, (app) =>
128
- app.use(cryptoAuthAdmin())
129
-
130
- // GET /api/exemplo-posts/admin/todos
131
- .get('/admin/todos', ({ request }) => {
132
- const user = getCryptoAuthUser(request)!
133
-
134
- return {
135
- success: true,
136
- message: 'Painel administrativo',
137
- admin: user.publicKey.substring(0, 8) + '...',
138
- totalPosts: posts.length,
139
- posts: posts // Admin vê tudo
140
- }
141
- })
142
-
143
- // DELETE /api/exemplo-posts/admin/:id
144
- .delete('/admin/:id', ({ request, params }) => {
145
- const user = getCryptoAuthUser(request)!
146
- const postIndex = posts.findIndex(p => p.id === parseInt(params.id))
147
-
148
- if (postIndex === -1) {
149
- return { success: false, error: 'Post não encontrado' }
150
- }
151
-
152
- const deletedPost = posts.splice(postIndex, 1)[0]
153
-
154
- return {
155
- success: true,
156
- message: `Post "${deletedPost.title}" deletado pelo admin`,
157
- deletedBy: user.publicKey.substring(0, 8) + '...',
158
- deletedPost
159
- }
160
- })
161
- )
@@ -1,216 +0,0 @@
1
- /**
2
- * Module Resolver para Plugins
3
- * Implementa resolução em cascata: plugin local → projeto principal
4
- */
5
-
6
- import { existsSync } from 'fs'
7
- import { join, resolve } from 'path'
8
- import type { Logger } from '../utils/logger'
9
-
10
- export interface ModuleResolverConfig {
11
- projectRoot: string
12
- logger?: Logger
13
- }
14
-
15
- export class PluginModuleResolver {
16
- private config: ModuleResolverConfig
17
- private logger?: Logger
18
- private resolveCache: Map<string, string> = new Map()
19
-
20
- constructor(config: ModuleResolverConfig) {
21
- this.config = config
22
- this.logger = config.logger
23
- }
24
-
25
- /**
26
- * Resolve um módulo com estratégia em cascata:
27
- * 1. node_modules local do plugin
28
- * 2. node_modules do projeto principal
29
- */
30
- resolveModule(moduleName: string, pluginPath: string): string | null {
31
- const cacheKey = `${pluginPath}::${moduleName}`
32
-
33
- // Verificar cache
34
- if (this.resolveCache.has(cacheKey)) {
35
- return this.resolveCache.get(cacheKey)!
36
- }
37
-
38
- this.logger?.debug(`Resolvendo módulo '${moduleName}' para plugin em '${pluginPath}'`)
39
-
40
- // 1. Tentar no node_modules local do plugin
41
- const localPath = this.tryResolveLocal(moduleName, pluginPath)
42
- if (localPath) {
43
- this.logger?.debug(`✅ Módulo '${moduleName}' encontrado localmente: ${localPath}`)
44
- this.resolveCache.set(cacheKey, localPath)
45
- return localPath
46
- }
47
-
48
- // 2. Tentar no node_modules do projeto principal
49
- const projectPath = this.tryResolveProject(moduleName)
50
- if (projectPath) {
51
- this.logger?.debug(`✅ Módulo '${moduleName}' encontrado no projeto: ${projectPath}`)
52
- this.resolveCache.set(cacheKey, projectPath)
53
- return projectPath
54
- }
55
-
56
- this.logger?.warn(`❌ Módulo '${moduleName}' não encontrado em nenhum contexto`)
57
- return null
58
- }
59
-
60
- /**
61
- * Tenta resolver no node_modules local do plugin
62
- */
63
- private tryResolveLocal(moduleName: string, pluginPath: string): string | null {
64
- const pluginDir = resolve(pluginPath)
65
- const localNodeModules = join(pluginDir, 'node_modules', moduleName)
66
-
67
- if (existsSync(localNodeModules)) {
68
- // Verificar se tem package.json para pegar o entry point
69
- const packageJsonPath = join(localNodeModules, 'package.json')
70
- if (existsSync(packageJsonPath)) {
71
- try {
72
- const pkg = require(packageJsonPath)
73
- const entry = pkg.module || pkg.main || 'index.js'
74
- const entryPath = join(localNodeModules, entry)
75
-
76
- if (existsSync(entryPath)) {
77
- return entryPath
78
- }
79
- } catch (error) {
80
- this.logger?.debug(`Erro ao ler package.json de '${moduleName}'`, { error })
81
- }
82
- }
83
-
84
- // Fallback: tentar index.js/index.ts
85
- const indexJs = join(localNodeModules, 'index.js')
86
- const indexTs = join(localNodeModules, 'index.ts')
87
-
88
- if (existsSync(indexJs)) return indexJs
89
- if (existsSync(indexTs)) return indexTs
90
-
91
- return localNodeModules
92
- }
93
-
94
- return null
95
- }
96
-
97
- /**
98
- * Tenta resolver no node_modules do projeto principal
99
- */
100
- private tryResolveProject(moduleName: string): string | null {
101
- const projectNodeModules = join(this.config.projectRoot, 'node_modules', moduleName)
102
-
103
- if (existsSync(projectNodeModules)) {
104
- // Verificar se tem package.json para pegar o entry point
105
- const packageJsonPath = join(projectNodeModules, 'package.json')
106
- if (existsSync(packageJsonPath)) {
107
- try {
108
- const pkg = require(packageJsonPath)
109
- const entry = pkg.module || pkg.main || 'index.js'
110
- const entryPath = join(projectNodeModules, entry)
111
-
112
- if (existsSync(entryPath)) {
113
- return entryPath
114
- }
115
- } catch (error) {
116
- this.logger?.debug(`Erro ao ler package.json de '${moduleName}'`, { error })
117
- }
118
- }
119
-
120
- // Fallback: tentar index.js/index.ts
121
- const indexJs = join(projectNodeModules, 'index.js')
122
- const indexTs = join(projectNodeModules, 'index.ts')
123
-
124
- if (existsSync(indexJs)) return indexJs
125
- if (existsSync(indexTs)) return indexTs
126
-
127
- return projectNodeModules
128
- }
129
-
130
- return null
131
- }
132
-
133
- /**
134
- * Resolve sub-paths (ex: @noble/curves/ed25519)
135
- */
136
- resolveSubpath(moduleName: string, subpath: string, pluginPath: string): string | null {
137
- const fullModule = `${moduleName}/${subpath}`
138
- const cacheKey = `${pluginPath}::${fullModule}`
139
-
140
- // Verificar cache
141
- if (this.resolveCache.has(cacheKey)) {
142
- return this.resolveCache.get(cacheKey)!
143
- }
144
-
145
- this.logger?.debug(`Resolvendo subpath '${fullModule}' para plugin em '${pluginPath}'`)
146
-
147
- // 1. Tentar no node_modules local do plugin
148
- const pluginDir = resolve(pluginPath)
149
- const localPath = join(pluginDir, 'node_modules', fullModule)
150
-
151
- if (this.existsWithExtension(localPath)) {
152
- const resolvedLocal = this.findFileWithExtension(localPath)
153
- if (resolvedLocal) {
154
- this.logger?.debug(`✅ Subpath '${fullModule}' encontrado localmente: ${resolvedLocal}`)
155
- this.resolveCache.set(cacheKey, resolvedLocal)
156
- return resolvedLocal
157
- }
158
- }
159
-
160
- // 2. Tentar no node_modules do projeto principal
161
- const projectPath = join(this.config.projectRoot, 'node_modules', fullModule)
162
-
163
- if (this.existsWithExtension(projectPath)) {
164
- const resolvedProject = this.findFileWithExtension(projectPath)
165
- if (resolvedProject) {
166
- this.logger?.debug(`✅ Subpath '${fullModule}' encontrado no projeto: ${resolvedProject}`)
167
- this.resolveCache.set(cacheKey, resolvedProject)
168
- return resolvedProject
169
- }
170
- }
171
-
172
- this.logger?.warn(`❌ Subpath '${fullModule}' não encontrado em nenhum contexto`)
173
- return null
174
- }
175
-
176
- /**
177
- * Verifica se arquivo existe com alguma extensão comum
178
- */
179
- private existsWithExtension(basePath: string): boolean {
180
- const extensions = ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '/index.js', '/index.ts']
181
- return extensions.some(ext => existsSync(basePath + ext))
182
- }
183
-
184
- /**
185
- * Encontra arquivo com extensão
186
- */
187
- private findFileWithExtension(basePath: string): string | null {
188
- const extensions = ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '/index.js', '/index.ts']
189
-
190
- for (const ext of extensions) {
191
- const fullPath = basePath + ext
192
- if (existsSync(fullPath)) {
193
- return fullPath
194
- }
195
- }
196
-
197
- return null
198
- }
199
-
200
- /**
201
- * Limpar cache
202
- */
203
- clearCache(): void {
204
- this.resolveCache.clear()
205
- }
206
-
207
- /**
208
- * Obter estatísticas
209
- */
210
- getStats() {
211
- return {
212
- cachedModules: this.resolveCache.size,
213
- projectRoot: this.config.projectRoot
214
- }
215
- }
216
- }