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.
Files changed (51) hide show
  1. package/.env.example +8 -1
  2. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +475 -0
  3. package/CRYPTO-AUTH-MIDDLEWARES.md +473 -0
  4. package/CRYPTO-AUTH-USAGE.md +491 -0
  5. package/EXEMPLO-ROTA-PROTEGIDA.md +347 -0
  6. package/QUICK-START-CRYPTO-AUTH.md +221 -0
  7. package/app/client/src/App.tsx +4 -1
  8. package/app/client/src/pages/CryptoAuthPage.tsx +394 -0
  9. package/app/server/index.ts +4 -0
  10. package/app/server/routes/crypto-auth-demo.routes.ts +167 -0
  11. package/app/server/routes/example-with-crypto-auth.routes.ts +235 -0
  12. package/app/server/routes/exemplo-posts.routes.ts +161 -0
  13. package/app/server/routes/index.ts +5 -1
  14. package/config/index.ts +9 -1
  15. package/core/cli/generators/plugin.ts +324 -34
  16. package/core/cli/generators/template-engine.ts +5 -0
  17. package/core/cli/plugin-discovery.ts +33 -12
  18. package/core/framework/server.ts +10 -0
  19. package/core/plugins/dependency-manager.ts +89 -22
  20. package/core/plugins/index.ts +4 -0
  21. package/core/plugins/manager.ts +3 -2
  22. package/core/plugins/module-resolver.ts +216 -0
  23. package/core/plugins/registry.ts +28 -1
  24. package/core/utils/logger/index.ts +4 -0
  25. package/fluxstack.config.ts +253 -114
  26. package/package.json +117 -117
  27. package/plugins/crypto-auth/README.md +722 -172
  28. package/plugins/crypto-auth/ai-context.md +1282 -0
  29. package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
  30. package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
  31. package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
  32. package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
  33. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
  34. package/plugins/crypto-auth/client/components/index.ts +1 -4
  35. package/plugins/crypto-auth/client/index.ts +1 -1
  36. package/plugins/crypto-auth/config/index.ts +34 -0
  37. package/plugins/crypto-auth/index.ts +84 -152
  38. package/plugins/crypto-auth/package.json +65 -64
  39. package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
  40. package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
  41. package/plugins/crypto-auth/server/index.ts +15 -2
  42. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
  43. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
  44. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
  45. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
  46. package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
  47. package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
  48. package/plugins/crypto-auth/server/middlewares.ts +19 -0
  49. package/test-crypto-auth.ts +101 -0
  50. package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
  51. package/plugins/crypto-auth/plugin.json +0 -29
@@ -3,7 +3,20 @@
3
3
  */
4
4
 
5
5
  export { CryptoAuthService } from './CryptoAuthService'
6
- export type { SessionData, AuthResult, CryptoAuthConfig } from './CryptoAuthService'
6
+ export type { AuthResult, CryptoAuthConfig } from './CryptoAuthService'
7
7
 
8
8
  export { AuthMiddleware } from './AuthMiddleware'
9
- export type { AuthMiddlewareConfig, AuthMiddlewareResult } from './AuthMiddleware'
9
+ export type { AuthMiddlewareConfig, AuthMiddlewareResult } from './AuthMiddleware'
10
+
11
+ // Middlewares Elysia
12
+ export {
13
+ cryptoAuthRequired,
14
+ cryptoAuthAdmin,
15
+ cryptoAuthPermissions,
16
+ cryptoAuthOptional,
17
+ getCryptoAuthUser,
18
+ isCryptoAuthAuthenticated,
19
+ isCryptoAuthAdmin,
20
+ hasCryptoAuthPermission
21
+ } from './middlewares'
22
+ export type { CryptoAuthUser, CryptoAuthMiddlewareOptions } from './middlewares'
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Middleware que REQUER privilégios de administrador
3
+ * Bloqueia requisições não autenticadas (401) ou sem privilégios admin (403)
4
+ */
5
+
6
+ import { Elysia } from 'elysia'
7
+ import { createGuard } from '@/core/server/middleware/elysia-helpers'
8
+ import type { Logger } from '@/core/utils/logger'
9
+ import { validateAuthSync, type CryptoAuthUser } from './helpers'
10
+
11
+ export interface CryptoAuthMiddlewareOptions {
12
+ logger?: Logger
13
+ }
14
+
15
+ export const cryptoAuthAdmin = (options: CryptoAuthMiddlewareOptions = {}) => {
16
+ return new Elysia({ name: 'crypto-auth-admin' })
17
+ .derive(async ({ request }) => {
18
+ const result = await validateAuthSync(request as Request, options.logger)
19
+
20
+ if (result.success && result.user) {
21
+ ;(request as any).user = result.user
22
+ }
23
+
24
+ return {}
25
+ })
26
+ .use(
27
+ createGuard({
28
+ name: 'crypto-auth-admin-check',
29
+ check: ({ request }) => {
30
+ const user = (request as any).user as CryptoAuthUser | undefined
31
+ return user && user.isAdmin
32
+ },
33
+ onFail: (set, { request }) => {
34
+ const user = (request as any).user as CryptoAuthUser | undefined
35
+
36
+ if (!user) {
37
+ set.status = 401
38
+ return {
39
+ error: {
40
+ message: 'Authentication required',
41
+ code: 'CRYPTO_AUTH_REQUIRED',
42
+ statusCode: 401
43
+ }
44
+ }
45
+ }
46
+
47
+ options.logger?.warn('Admin access denied', {
48
+ publicKey: user.publicKey.substring(0, 8) + '...',
49
+ permissions: user.permissions
50
+ })
51
+
52
+ set.status = 403
53
+ return {
54
+ error: {
55
+ message: 'Admin privileges required',
56
+ code: 'ADMIN_REQUIRED',
57
+ statusCode: 403,
58
+ yourPermissions: user.permissions
59
+ }
60
+ }
61
+ }
62
+ })
63
+ )
64
+ .as('plugin')
65
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Middleware OPCIONAL - adiciona user se autenticado, mas não requer
3
+ * Não bloqueia requisições não autenticadas - permite acesso público
4
+ */
5
+
6
+ import { Elysia } from 'elysia'
7
+ import type { Logger } from '@/core/utils/logger'
8
+ import { validateAuthSync } from './helpers'
9
+
10
+ export interface CryptoAuthMiddlewareOptions {
11
+ logger?: Logger
12
+ }
13
+
14
+ export const cryptoAuthOptional = (options: CryptoAuthMiddlewareOptions = {}) => {
15
+ return new Elysia({ name: 'crypto-auth-optional' })
16
+ .derive(async ({ request }) => {
17
+ const result = await validateAuthSync(request as Request, options.logger)
18
+
19
+ if (result.success && result.user) {
20
+ ;(request as any).user = result.user
21
+ }
22
+
23
+ return {}
24
+ })
25
+ .as('plugin')
26
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Middleware que REQUER permissões específicas
3
+ * Bloqueia requisições sem as permissões necessárias (403)
4
+ */
5
+
6
+ import { Elysia } from 'elysia'
7
+ import { createGuard } from '@/core/server/middleware/elysia-helpers'
8
+ import type { Logger } from '@/core/utils/logger'
9
+ import { validateAuthSync, type CryptoAuthUser } from './helpers'
10
+
11
+ export interface CryptoAuthMiddlewareOptions {
12
+ logger?: Logger
13
+ }
14
+
15
+ export const cryptoAuthPermissions = (
16
+ requiredPermissions: string[],
17
+ options: CryptoAuthMiddlewareOptions = {}
18
+ ) => {
19
+ return new Elysia({ name: 'crypto-auth-permissions' })
20
+ .derive(async ({ request }) => {
21
+ const result = await validateAuthSync(request as Request, options.logger)
22
+
23
+ if (result.success && result.user) {
24
+ ;(request as any).user = result.user
25
+ }
26
+
27
+ return {}
28
+ })
29
+ .use(
30
+ createGuard({
31
+ name: 'crypto-auth-permissions-check',
32
+ check: ({ request }) => {
33
+ const user = (request as any).user as CryptoAuthUser | undefined
34
+
35
+ if (!user) return false
36
+
37
+ const userPermissions = user.permissions
38
+ return requiredPermissions.every(
39
+ perm => userPermissions.includes(perm) || userPermissions.includes('admin')
40
+ )
41
+ },
42
+ onFail: (set, { request }) => {
43
+ const user = (request as any).user as CryptoAuthUser | undefined
44
+
45
+ if (!user) {
46
+ set.status = 401
47
+ return {
48
+ error: {
49
+ message: 'Authentication required',
50
+ code: 'CRYPTO_AUTH_REQUIRED',
51
+ statusCode: 401
52
+ }
53
+ }
54
+ }
55
+
56
+ options.logger?.warn('Permission denied', {
57
+ publicKey: user.publicKey.substring(0, 8) + '...',
58
+ required: requiredPermissions,
59
+ has: user.permissions
60
+ })
61
+
62
+ set.status = 403
63
+ return {
64
+ error: {
65
+ message: 'Insufficient permissions',
66
+ code: 'PERMISSION_DENIED',
67
+ statusCode: 403,
68
+ required: requiredPermissions,
69
+ yours: user.permissions
70
+ }
71
+ }
72
+ }
73
+ })
74
+ )
75
+ .as('plugin')
76
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Middleware que REQUER autenticação
3
+ * Bloqueia requisições não autenticadas com 401
4
+ */
5
+
6
+ import { Elysia } from 'elysia'
7
+ import { createGuard } from '@/core/server/middleware/elysia-helpers'
8
+ import type { Logger } from '@/core/utils/logger'
9
+ import { validateAuthSync } from './helpers'
10
+
11
+ export interface CryptoAuthMiddlewareOptions {
12
+ logger?: Logger
13
+ }
14
+
15
+ export const cryptoAuthRequired = (options: CryptoAuthMiddlewareOptions = {}) => {
16
+ return new Elysia({ name: 'crypto-auth-required' })
17
+ .derive(async ({ request }) => {
18
+ const result = await validateAuthSync(request as Request, options.logger)
19
+
20
+ if (result.success && result.user) {
21
+ ;(request as any).user = result.user
22
+ }
23
+
24
+ return {}
25
+ })
26
+ .use(
27
+ createGuard({
28
+ name: 'crypto-auth-check',
29
+ check: ({ request }) => {
30
+ return !!(request as any).user
31
+ },
32
+ onFail: (set) => {
33
+ set.status = 401
34
+ return {
35
+ error: {
36
+ message: 'Authentication required',
37
+ code: 'CRYPTO_AUTH_REQUIRED',
38
+ statusCode: 401
39
+ }
40
+ }
41
+ }
42
+ })
43
+ )
44
+ .as('plugin')
45
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Crypto Auth Middleware Helpers
3
+ * Funções compartilhadas para validação de autenticação
4
+ */
5
+
6
+ import type { Logger } from '@/core/utils/logger'
7
+
8
+ export interface CryptoAuthUser {
9
+ publicKey: string
10
+ isAdmin: boolean
11
+ permissions: string[]
12
+ }
13
+
14
+ /**
15
+ * Get auth service from global
16
+ */
17
+ export function getAuthService() {
18
+ const service = (global as any).cryptoAuthService
19
+ if (!service) {
20
+ throw new Error('CryptoAuthService not initialized. Make sure crypto-auth plugin is loaded.')
21
+ }
22
+ return service
23
+ }
24
+
25
+ /**
26
+ * Get auth middleware from global
27
+ */
28
+ export function getAuthMiddleware() {
29
+ const middleware = (global as any).cryptoAuthMiddleware
30
+ if (!middleware) {
31
+ throw new Error('AuthMiddleware not initialized. Make sure crypto-auth plugin is loaded.')
32
+ }
33
+ return middleware
34
+ }
35
+
36
+ /**
37
+ * Extract and validate authentication from request
38
+ * Versão SÍNCRONA para evitar problemas com Elysia
39
+ */
40
+ export function extractAuthHeaders(request: Request): {
41
+ publicKey: string
42
+ timestamp: number
43
+ nonce: string
44
+ signature: string
45
+ } | null {
46
+ const headers = request.headers
47
+ const publicKey = headers.get('x-public-key')
48
+ const timestampStr = headers.get('x-timestamp')
49
+ const nonce = headers.get('x-nonce')
50
+ const signature = headers.get('x-signature')
51
+
52
+ if (!publicKey || !timestampStr || !nonce || !signature) {
53
+ return null
54
+ }
55
+
56
+ const timestamp = parseInt(timestampStr, 10)
57
+ if (isNaN(timestamp)) {
58
+ return null
59
+ }
60
+
61
+ return { publicKey, timestamp, nonce, signature }
62
+ }
63
+
64
+ /**
65
+ * Build message for signature verification
66
+ */
67
+ export function buildMessage(request: Request): string {
68
+ const url = new URL(request.url)
69
+ return `${request.method}:${url.pathname}`
70
+ }
71
+
72
+ /**
73
+ * Validate authentication synchronously
74
+ */
75
+ export async function validateAuthSync(request: Request, logger?: Logger): Promise<{
76
+ success: boolean
77
+ user?: CryptoAuthUser
78
+ error?: string
79
+ }> {
80
+ try {
81
+ const authHeaders = extractAuthHeaders(request)
82
+
83
+ if (!authHeaders) {
84
+ return {
85
+ success: false,
86
+ error: 'Missing authentication headers'
87
+ }
88
+ }
89
+
90
+ const authService = getAuthService()
91
+ const message = buildMessage(request)
92
+
93
+ const result = await authService.validateRequest({
94
+ publicKey: authHeaders.publicKey,
95
+ timestamp: authHeaders.timestamp,
96
+ nonce: authHeaders.nonce,
97
+ signature: authHeaders.signature,
98
+ message
99
+ })
100
+
101
+ return result
102
+ } catch (error) {
103
+ logger?.error('Auth validation error', { error })
104
+ return {
105
+ success: false,
106
+ error: error instanceof Error ? error.message : 'Unknown error'
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Helper: Obter usuário autenticado do request
113
+ */
114
+ export function getCryptoAuthUser(request: Request): CryptoAuthUser | null {
115
+ return (request as any).user || null
116
+ }
117
+
118
+ /**
119
+ * Helper: Verificar se request está autenticado
120
+ */
121
+ export function isCryptoAuthAuthenticated(request: Request): boolean {
122
+ return !!(request as any).user
123
+ }
124
+
125
+ /**
126
+ * Helper: Verificar se usuário é admin
127
+ */
128
+ export function isCryptoAuthAdmin(request: Request): boolean {
129
+ const user = getCryptoAuthUser(request)
130
+ return user?.isAdmin || false
131
+ }
132
+
133
+ /**
134
+ * Helper: Verificar se usuário tem permissão específica
135
+ */
136
+ export function hasCryptoAuthPermission(request: Request, permission: string): boolean {
137
+ const user = getCryptoAuthUser(request)
138
+ if (!user) return false
139
+ return user.permissions.includes(permission) || user.permissions.includes('admin')
140
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Crypto Auth Middlewares
3
+ * Exports centralizados de todos os middlewares
4
+ */
5
+
6
+ // Middlewares
7
+ export { cryptoAuthRequired } from './cryptoAuthRequired'
8
+ export { cryptoAuthAdmin } from './cryptoAuthAdmin'
9
+ export { cryptoAuthOptional } from './cryptoAuthOptional'
10
+ export { cryptoAuthPermissions } from './cryptoAuthPermissions'
11
+
12
+ // Helpers
13
+ export {
14
+ getCryptoAuthUser,
15
+ isCryptoAuthAuthenticated,
16
+ isCryptoAuthAdmin,
17
+ hasCryptoAuthPermission,
18
+ type CryptoAuthUser
19
+ } from './helpers'
20
+
21
+ // Types
22
+ export type { CryptoAuthMiddlewareOptions } from './cryptoAuthRequired'
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Crypto Auth Middlewares
3
+ * Middlewares Elysia para autenticação criptográfica
4
+ *
5
+ * Uso:
6
+ * ```typescript
7
+ * import { cryptoAuthRequired, cryptoAuthAdmin } from '@/plugins/crypto-auth/server'
8
+ *
9
+ * export const myRoutes = new Elysia()
10
+ * .use(cryptoAuthRequired())
11
+ * .get('/protected', ({ request }) => {
12
+ * const user = getCryptoAuthUser(request)
13
+ * return { user }
14
+ * })
15
+ * ```
16
+ */
17
+
18
+ // Re-export tudo do módulo middlewares
19
+ export * from './middlewares/index'
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Script de teste para validar autenticação criptográfica
3
+ */
4
+
5
+ import { ed25519 } from '@noble/curves/ed25519'
6
+ import { sha256 } from '@noble/hashes/sha256'
7
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
8
+
9
+ async function testCryptoAuth() {
10
+ console.log('🔐 Testando Autenticação Criptográfica Ed25519\n')
11
+
12
+ // 1. Gerar par de chaves
13
+ console.log('1️⃣ Gerando par de chaves Ed25519...')
14
+ const privateKey = ed25519.utils.randomPrivateKey()
15
+ const publicKeyBytes = ed25519.getPublicKey(privateKey)
16
+ const publicKey = bytesToHex(publicKeyBytes)
17
+ const privateKeyHex = bytesToHex(privateKey)
18
+
19
+ console.log(` ✅ Chave pública: ${publicKey.substring(0, 16)}...`)
20
+ console.log(` ✅ Chave privada: ${privateKeyHex.substring(0, 16)}... (NUNCA enviar ao servidor!)\n`)
21
+
22
+ // 2. Preparar requisição
23
+ console.log('2️⃣ Preparando requisição assinada...')
24
+ const url = '/api/crypto-auth/protected'
25
+ const method = 'GET'
26
+ const timestamp = Date.now()
27
+ const nonce = generateNonce()
28
+
29
+ // 3. Construir mensagem para assinar
30
+ const message = `${method}:${url}`
31
+ const fullMessage = `${publicKey}:${timestamp}:${nonce}:${message}`
32
+
33
+ console.log(` 📝 Mensagem: ${fullMessage.substring(0, 50)}...\n`)
34
+
35
+ // 4. Assinar mensagem
36
+ console.log('3️⃣ Assinando mensagem com chave privada...')
37
+ const messageHash = sha256(new TextEncoder().encode(fullMessage))
38
+ const signatureBytes = ed25519.sign(messageHash, privateKey)
39
+ const signature = bytesToHex(signatureBytes)
40
+
41
+ console.log(` ✅ Assinatura: ${signature.substring(0, 32)}...\n`)
42
+
43
+ // 5. Fazer requisição ao servidor
44
+ console.log('4️⃣ Enviando requisição ao servidor...')
45
+ const response = await fetch('http://localhost:3000/api/crypto-auth/protected', {
46
+ method: 'GET',
47
+ headers: {
48
+ 'x-public-key': publicKey,
49
+ 'x-timestamp': timestamp.toString(),
50
+ 'x-nonce': nonce,
51
+ 'x-signature': signature
52
+ }
53
+ })
54
+
55
+ const data = await response.json()
56
+
57
+ console.log(` 📡 Status: ${response.status}`)
58
+ console.log(` 📦 Resposta:`, JSON.stringify(data, null, 2))
59
+
60
+ if (data.success) {
61
+ console.log('\n✅ SUCESSO! Assinatura validada corretamente pelo servidor!')
62
+ console.log(` 👤 Dados protegidos recebidos: ${data.data?.secretInfo}`)
63
+ } else {
64
+ console.log('\n❌ ERRO! Assinatura rejeitada pelo servidor')
65
+ console.log(` ⚠️ Erro: ${data.error}`)
66
+ }
67
+
68
+ // 6. Testar replay attack (reutilizar mesma assinatura)
69
+ console.log('\n5️⃣ Testando proteção contra replay attack...')
70
+ const replayResponse = await fetch('http://localhost:3000/api/crypto-auth/protected', {
71
+ method: 'GET',
72
+ headers: {
73
+ 'x-public-key': publicKey,
74
+ 'x-timestamp': timestamp.toString(),
75
+ 'x-nonce': nonce, // Mesmo nonce
76
+ 'x-signature': signature // Mesma assinatura
77
+ }
78
+ })
79
+
80
+ const replayData = await replayResponse.json()
81
+
82
+ console.log(` 📡 Replay Status: ${replayResponse.status}`)
83
+ console.log(` 📦 Replay Response:`, JSON.stringify(replayData, null, 2))
84
+
85
+ if (!replayData.success && replayData.error?.includes('nonce')) {
86
+ console.log(' ✅ Proteção funcionando! Replay attack bloqueado.')
87
+ } else if (replayResponse.status === 401) {
88
+ console.log(' ✅ Proteção funcionando! Replay attack bloqueado (status 401).')
89
+ } else {
90
+ console.log(' ⚠️ ATENÇÃO: Replay attack NÃO foi bloqueado!')
91
+ }
92
+ }
93
+
94
+ function generateNonce(): string {
95
+ const bytes = new Uint8Array(16)
96
+ crypto.getRandomValues(bytes)
97
+ return bytesToHex(bytes)
98
+ }
99
+
100
+ // Executar teste
101
+ testCryptoAuth().catch(console.error)