create-fluxstack 1.4.0 → 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/index.ts +5 -2
- package/core/cli/generators/plugin.ts +580 -0
- package/core/cli/generators/template-engine.ts +5 -0
- package/core/cli/index.ts +88 -3
- 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/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +117 -8
- 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
|
@@ -1,238 +1,788 @@
|
|
|
1
|
-
# FluxStack Crypto Auth Plugin
|
|
1
|
+
# 🔐 FluxStack Crypto Auth Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sistema de autenticação baseado em criptografia **Ed25519** para FluxStack. Autenticação stateless sem sessões, usando assinaturas criptográficas.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 📋 Índice
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- [O Que É](#-o-que-é)
|
|
8
|
+
- [Como Funciona](#-como-funciona)
|
|
9
|
+
- [Instalação](#-instalação)
|
|
10
|
+
- [Configuração](#️-configuração)
|
|
11
|
+
- [Uso Básico](#-uso-básico)
|
|
12
|
+
- [CLI Commands](#-cli-commands)
|
|
13
|
+
- [Middlewares Disponíveis](#-middlewares-disponíveis)
|
|
14
|
+
- [Helpers e Utilitários](#-helpers-e-utilitários)
|
|
15
|
+
- [Fluxo de Autenticação](#-fluxo-de-autenticação)
|
|
16
|
+
- [Segurança](#-segurança)
|
|
17
|
+
- [Troubleshooting](#-troubleshooting)
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
---
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
## 🎯 O Que É
|
|
22
|
+
|
|
23
|
+
**Crypto Auth** é um plugin de autenticação que usa **assinaturas digitais Ed25519** ao invés de sessões tradicionais.
|
|
24
|
+
|
|
25
|
+
### ✨ Principais Características
|
|
26
|
+
|
|
27
|
+
- ✅ **Stateless**: Sem sessões, sem armazenamento de tokens
|
|
28
|
+
- ✅ **Zero Trust**: Cada requisição é validada independentemente
|
|
29
|
+
- ✅ **Ed25519**: Criptografia de curva elíptica (rápida e segura)
|
|
30
|
+
- ✅ **Anti-Replay**: Proteção contra replay attacks com timestamps e nonces
|
|
31
|
+
- ✅ **Admin Support**: Sistema de permissões com chaves administrativas
|
|
32
|
+
- ✅ **TypeScript**: Totalmente tipado
|
|
33
|
+
- ✅ **CLI Integration**: Geração automática de rotas protegidas
|
|
34
|
+
|
|
35
|
+
### 🔄 Diferenças vs. Auth Tradicional
|
|
36
|
+
|
|
37
|
+
| Característica | Auth Tradicional | Crypto Auth |
|
|
38
|
+
|----------------|------------------|-------------|
|
|
39
|
+
| **Armazenamento** | Sessões no servidor | Nenhum |
|
|
40
|
+
| **Escalabilidade** | Limitada (sessões) | Infinita (stateless) |
|
|
41
|
+
| **Segurança** | Token JWT ou session | Assinatura Ed25519 |
|
|
42
|
+
| **Chave privada** | Armazenada no servidor | **NUNCA** sai do cliente |
|
|
43
|
+
| **Performance** | Depende do DB/cache | Ultra-rápida (validação local) |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🔬 Como Funciona
|
|
48
|
+
|
|
49
|
+
### 1. **Cliente Gera Par de Chaves (Uma Vez)**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// No navegador (usando TweetNaCl ou similar)
|
|
53
|
+
const keypair = nacl.sign.keyPair()
|
|
54
|
+
|
|
55
|
+
// Armazenar no localStorage (chave privada NUNCA sai do navegador)
|
|
56
|
+
localStorage.setItem('privateKey', toHex(keypair.secretKey))
|
|
57
|
+
localStorage.setItem('publicKey', toHex(keypair.publicKey))
|
|
19
58
|
```
|
|
20
59
|
|
|
21
|
-
|
|
60
|
+
### 2. **Cliente Assina Cada Requisição**
|
|
22
61
|
|
|
23
|
-
|
|
62
|
+
```typescript
|
|
63
|
+
// Para cada request
|
|
64
|
+
const timestamp = Date.now()
|
|
65
|
+
const nonce = generateRandomNonce()
|
|
66
|
+
const message = `${timestamp}:${nonce}:${requestBody}`
|
|
67
|
+
|
|
68
|
+
// Assinar com chave privada
|
|
69
|
+
const signature = nacl.sign.detached(message, privateKey)
|
|
70
|
+
|
|
71
|
+
// Enviar headers
|
|
72
|
+
headers = {
|
|
73
|
+
'x-public-key': publicKeyHex,
|
|
74
|
+
'x-timestamp': timestamp,
|
|
75
|
+
'x-nonce': nonce,
|
|
76
|
+
'x-signature': toHex(signature)
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. **Servidor Valida Assinatura**
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Plugin valida automaticamente
|
|
84
|
+
const isValid = nacl.sign.detached.verify(
|
|
85
|
+
message,
|
|
86
|
+
signature,
|
|
87
|
+
publicKey
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if (!isValid) {
|
|
91
|
+
throw new Error('Invalid signature')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Verificar timestamp (previne replay attacks)
|
|
95
|
+
if (Math.abs(Date.now() - timestamp) > maxTimeDrift) {
|
|
96
|
+
throw new Error('Timestamp expired')
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. **Sem Armazenamento de Estado**
|
|
101
|
+
|
|
102
|
+
- ✅ Servidor valida usando **apenas** a chave pública enviada
|
|
103
|
+
- ✅ Nenhuma sessão ou token armazenado
|
|
104
|
+
- ✅ Cada requisição é independente
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📦 Instalação
|
|
109
|
+
|
|
110
|
+
O plugin já vem incluído no FluxStack. Para habilitá-lo:
|
|
111
|
+
|
|
112
|
+
### 1. **Adicionar ao `fluxstack.config.ts`**
|
|
24
113
|
|
|
25
114
|
```typescript
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
115
|
+
import { cryptoAuthPlugin } from './plugins/crypto-auth'
|
|
116
|
+
|
|
117
|
+
export default defineConfig({
|
|
29
118
|
plugins: {
|
|
30
|
-
enabled: [
|
|
119
|
+
enabled: [
|
|
120
|
+
cryptoAuthPlugin
|
|
121
|
+
],
|
|
31
122
|
config: {
|
|
32
123
|
'crypto-auth': {
|
|
33
124
|
enabled: true,
|
|
34
|
-
sessionTimeout: 1800000, // 30 minutos
|
|
35
125
|
maxTimeDrift: 300000, // 5 minutos
|
|
36
126
|
adminKeys: [
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
protectedRoutes: [
|
|
40
|
-
'/api/admin/*',
|
|
41
|
-
'/api/protected/*'
|
|
42
|
-
],
|
|
43
|
-
publicRoutes: [
|
|
44
|
-
'/api/auth/*',
|
|
45
|
-
'/api/health',
|
|
46
|
-
'/api/docs'
|
|
127
|
+
'a1b2c3d4e5f6...', // Chaves públicas de admins (hex 64 chars)
|
|
128
|
+
'f6e5d4c3b2a1...'
|
|
47
129
|
],
|
|
48
130
|
enableMetrics: true
|
|
49
131
|
}
|
|
50
132
|
}
|
|
51
133
|
}
|
|
52
|
-
}
|
|
134
|
+
})
|
|
53
135
|
```
|
|
54
136
|
|
|
55
|
-
|
|
137
|
+
### 2. **Variáveis de Ambiente (Opcional)**
|
|
56
138
|
|
|
57
|
-
|
|
139
|
+
```bash
|
|
140
|
+
# .env
|
|
141
|
+
CRYPTO_AUTH_ENABLED=true
|
|
142
|
+
CRYPTO_AUTH_MAX_TIME_DRIFT=300000
|
|
143
|
+
CRYPTO_AUTH_ADMIN_KEYS=a1b2c3d4e5f6...,f6e5d4c3b2a1...
|
|
144
|
+
CRYPTO_AUTH_ENABLE_METRICS=true
|
|
145
|
+
```
|
|
58
146
|
|
|
59
|
-
|
|
60
|
-
import React from 'react'
|
|
61
|
-
import { AuthProvider } from '@/plugins/crypto-auth/client'
|
|
147
|
+
---
|
|
62
148
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
>
|
|
74
|
-
<YourApp />
|
|
75
|
-
</AuthProvider>
|
|
76
|
-
)
|
|
149
|
+
## ⚙️ Configuração
|
|
150
|
+
|
|
151
|
+
### Schema de Configuração
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
{
|
|
155
|
+
enabled: boolean // Habilitar/desabilitar plugin
|
|
156
|
+
maxTimeDrift: number // Máximo drift de tempo (ms) - previne replay
|
|
157
|
+
adminKeys: string[] // Chaves públicas de administradores
|
|
158
|
+
enableMetrics: boolean // Habilitar logs de métricas
|
|
77
159
|
}
|
|
78
160
|
```
|
|
79
161
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
import React from 'react'
|
|
84
|
-
import { useAuth } from '@/plugins/crypto-auth/client'
|
|
85
|
-
|
|
86
|
-
function Dashboard() {
|
|
87
|
-
const {
|
|
88
|
-
isAuthenticated,
|
|
89
|
-
isAdmin,
|
|
90
|
-
permissions,
|
|
91
|
-
login,
|
|
92
|
-
logout,
|
|
93
|
-
client
|
|
94
|
-
} = useAuth()
|
|
95
|
-
|
|
96
|
-
const handleApiCall = async () => {
|
|
97
|
-
try {
|
|
98
|
-
const response = await client.fetch('/api/protected/data')
|
|
99
|
-
const data = await response.json()
|
|
100
|
-
console.log(data)
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error('Erro na API:', error)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
162
|
+
### Valores Padrão
|
|
105
163
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<h1>Dashboard {isAdmin && '(Admin)'}</h1>
|
|
113
|
-
<p>Permissões: {permissions.join(', ')}</p>
|
|
114
|
-
<button onClick={handleApiCall}>Chamar API</button>
|
|
115
|
-
<button onClick={logout}>Sair</button>
|
|
116
|
-
</div>
|
|
117
|
-
)
|
|
164
|
+
```typescript
|
|
165
|
+
{
|
|
166
|
+
enabled: true,
|
|
167
|
+
maxTimeDrift: 300000, // 5 minutos
|
|
168
|
+
adminKeys: [],
|
|
169
|
+
enableMetrics: true
|
|
118
170
|
}
|
|
119
171
|
```
|
|
120
172
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 🚀 Uso Básico
|
|
176
|
+
|
|
177
|
+
### Opção 1: CLI (Recomendado)
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Criar rota com auth obrigatória
|
|
181
|
+
bun flux crypto-auth:make:route users
|
|
182
|
+
|
|
183
|
+
# Criar rota admin-only
|
|
184
|
+
bun flux crypto-auth:make:route admin-panel --auth admin
|
|
185
|
+
|
|
186
|
+
# Criar rota com auth opcional
|
|
187
|
+
bun flux crypto-auth:make:route blog --auth optional
|
|
188
|
+
|
|
189
|
+
# Criar rota pública
|
|
190
|
+
bun flux crypto-auth:make:route public-api --auth public
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Opção 2: Manual
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// app/server/routes/users.routes.ts
|
|
197
|
+
import { Elysia, t } from 'elysia'
|
|
198
|
+
import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
199
|
+
|
|
200
|
+
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
201
|
+
|
|
202
|
+
// ========================================
|
|
203
|
+
// 🔒 ROTAS PROTEGIDAS
|
|
204
|
+
// ========================================
|
|
205
|
+
.guard({}, (app) =>
|
|
206
|
+
app.use(cryptoAuthRequired())
|
|
207
|
+
|
|
208
|
+
.get('/', ({ request }) => {
|
|
209
|
+
const user = getCryptoAuthUser(request)!
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
message: 'Lista de usuários',
|
|
213
|
+
authenticatedAs: user.publicKey.substring(0, 8) + '...',
|
|
214
|
+
isAdmin: user.isAdmin
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
.post('/', ({ request, body }) => {
|
|
219
|
+
const user = getCryptoAuthUser(request)!
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
message: 'Usuário criado',
|
|
223
|
+
createdBy: user.publicKey.substring(0, 8) + '...'
|
|
224
|
+
}
|
|
225
|
+
}, {
|
|
226
|
+
body: t.Object({
|
|
227
|
+
name: t.String(),
|
|
228
|
+
email: t.String()
|
|
229
|
+
})
|
|
230
|
+
})
|
|
152
231
|
)
|
|
153
|
-
}
|
|
154
232
|
```
|
|
155
233
|
|
|
156
|
-
###
|
|
234
|
+
### Registrar Rotas
|
|
157
235
|
|
|
158
|
-
```
|
|
159
|
-
|
|
236
|
+
```typescript
|
|
237
|
+
// app/server/routes/index.ts
|
|
238
|
+
import { usersRoutes } from './users.routes'
|
|
160
239
|
|
|
161
|
-
const
|
|
240
|
+
export const apiRoutes = new Elysia({ prefix: '/api' })
|
|
241
|
+
.use(usersRoutes)
|
|
242
|
+
```
|
|
162
243
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 🎛️ CLI Commands
|
|
247
|
+
|
|
248
|
+
### `crypto-auth:make:route`
|
|
249
|
+
|
|
250
|
+
Gera arquivos de rotas com proteção crypto-auth automaticamente.
|
|
251
|
+
|
|
252
|
+
#### **Sintaxe**
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
bun flux crypto-auth:make:route <name> [options]
|
|
168
256
|
```
|
|
169
257
|
|
|
170
|
-
|
|
258
|
+
#### **Argumentos**
|
|
171
259
|
|
|
172
|
-
|
|
260
|
+
- `name` - Nome da rota (ex: posts, users, admin)
|
|
173
261
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
262
|
+
#### **Opções**
|
|
263
|
+
|
|
264
|
+
- `--auth, -a` - Tipo de autenticação (required, admin, optional, public)
|
|
265
|
+
- `--output, -o` - Diretório de saída (padrão: app/server/routes)
|
|
266
|
+
- `--force, -f` - Sobrescrever arquivo existente
|
|
267
|
+
|
|
268
|
+
#### **Exemplos**
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Rota com auth obrigatória
|
|
272
|
+
bun flux crypto-auth:make:route posts
|
|
273
|
+
|
|
274
|
+
# Rota admin-only com output customizado
|
|
275
|
+
bun flux crypto-auth:make:route admin --auth admin --output src/routes
|
|
276
|
+
|
|
277
|
+
# Forçar sobrescrita
|
|
278
|
+
bun flux crypto-auth:make:route users --force
|
|
279
|
+
```
|
|
178
280
|
|
|
179
|
-
|
|
281
|
+
#### **Templates Gerados**
|
|
180
282
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
283
|
+
| Tipo | Descrição | Rotas Geradas |
|
|
284
|
+
|------|-----------|---------------|
|
|
285
|
+
| `required` | Auth obrigatória | GET, POST, PUT, DELETE (CRUD completo) |
|
|
286
|
+
| `admin` | Apenas admins | GET, POST, DELETE |
|
|
287
|
+
| `optional` | Auth opcional | GET (lista), GET (detalhes com conteúdo extra) |
|
|
288
|
+
| `public` | Sem auth | GET (lista), GET (detalhes) |
|
|
185
289
|
|
|
186
|
-
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 🛡️ Middlewares Disponíveis
|
|
293
|
+
|
|
294
|
+
### 1. `cryptoAuthRequired()`
|
|
295
|
+
|
|
296
|
+
Autenticação **obrigatória**. Bloqueia requisições não autenticadas.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
300
|
+
|
|
301
|
+
.guard({}, (app) =>
|
|
302
|
+
app.use(cryptoAuthRequired())
|
|
303
|
+
.get('/protected', ({ request }) => {
|
|
304
|
+
const user = getCryptoAuthUser(request)! // ✅ Sempre existe
|
|
305
|
+
return { user }
|
|
306
|
+
})
|
|
307
|
+
)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Comportamento:**
|
|
311
|
+
- ✅ Requisição autenticada → Prossegue
|
|
312
|
+
- ❌ Requisição não autenticada → `401 Unauthorized`
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### 2. `cryptoAuthAdmin()`
|
|
317
|
+
|
|
318
|
+
Apenas **administradores**. Valida se a chave pública está em `adminKeys`.
|
|
187
319
|
|
|
188
320
|
```typescript
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
321
|
+
import { cryptoAuthAdmin, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
322
|
+
|
|
323
|
+
.guard({}, (app) =>
|
|
324
|
+
app.use(cryptoAuthAdmin())
|
|
325
|
+
.delete('/delete/:id', ({ request }) => {
|
|
326
|
+
const user = getCryptoAuthUser(request)! // ✅ Sempre admin
|
|
327
|
+
return { message: 'Deletado' }
|
|
328
|
+
})
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Comportamento:**
|
|
333
|
+
- ✅ Chave pública está em `adminKeys` → Prossegue
|
|
334
|
+
- ❌ Não é admin → `403 Forbidden`
|
|
335
|
+
- ❌ Não autenticado → `401 Unauthorized`
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### 3. `cryptoAuthOptional()`
|
|
340
|
+
|
|
341
|
+
Autenticação **opcional**. Não bloqueia, mas identifica usuários autenticados.
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { cryptoAuthOptional, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
345
|
+
|
|
346
|
+
.guard({}, (app) =>
|
|
347
|
+
app.use(cryptoAuthOptional())
|
|
348
|
+
.get('/feed', ({ request }) => {
|
|
349
|
+
const user = getCryptoAuthUser(request) // ⚠️ Pode ser null
|
|
350
|
+
|
|
351
|
+
if (user) {
|
|
352
|
+
return {
|
|
353
|
+
message: 'Feed personalizado',
|
|
354
|
+
recommendations: [...],
|
|
355
|
+
user: user.publicKey.substring(0, 8) + '...'
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
message: 'Feed público',
|
|
361
|
+
trending: [...]
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Comportamento:**
|
|
368
|
+
- ✅ Requisição autenticada → `user` disponível
|
|
369
|
+
- ✅ Requisição não autenticada → `user = null`, requisição prossegue
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### 4. `cryptoAuthPermissions(permissions: string[])`
|
|
374
|
+
|
|
375
|
+
Valida **permissões customizadas**.
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { cryptoAuthPermissions, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
379
|
+
|
|
380
|
+
.guard({}, (app) =>
|
|
381
|
+
app.use(cryptoAuthPermissions(['write', 'delete']))
|
|
382
|
+
.put('/edit/:id', ({ request }) => {
|
|
383
|
+
const user = getCryptoAuthUser(request)! // ✅ Tem as permissões
|
|
384
|
+
return { message: 'Editado' }
|
|
385
|
+
})
|
|
386
|
+
)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Comportamento:**
|
|
390
|
+
- ✅ Usuário tem todas as permissões → Prossegue
|
|
391
|
+
- ❌ Falta alguma permissão → `403 Forbidden`
|
|
392
|
+
|
|
393
|
+
> **Nota**: Sistema de permissões requer extensão customizada. Por padrão, apenas `isAdmin` é verificado.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 🔧 Helpers e Utilitários
|
|
398
|
+
|
|
399
|
+
### `getCryptoAuthUser(request)`
|
|
400
|
+
|
|
401
|
+
Retorna o usuário autenticado ou `null`.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import { getCryptoAuthUser } from '@/plugins/crypto-auth/server'
|
|
405
|
+
|
|
406
|
+
.get('/profile', ({ request }) => {
|
|
407
|
+
const user = getCryptoAuthUser(request)
|
|
408
|
+
|
|
192
409
|
if (!user) {
|
|
193
|
-
return { error: '
|
|
410
|
+
return { error: 'Not authenticated' }
|
|
194
411
|
}
|
|
195
|
-
|
|
412
|
+
|
|
196
413
|
return {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
sessionId: user.sessionId,
|
|
200
|
-
isAdmin: user.isAdmin,
|
|
201
|
-
permissions: user.permissions
|
|
202
|
-
}
|
|
414
|
+
publicKey: user.publicKey,
|
|
415
|
+
isAdmin: user.isAdmin
|
|
203
416
|
}
|
|
204
417
|
})
|
|
205
418
|
```
|
|
206
419
|
|
|
207
|
-
|
|
420
|
+
**Retorno:**
|
|
421
|
+
```typescript
|
|
422
|
+
{
|
|
423
|
+
publicKey: string // Chave pública do usuário (hex)
|
|
424
|
+
isAdmin: boolean // Se é administrador
|
|
425
|
+
} | null
|
|
426
|
+
```
|
|
208
427
|
|
|
209
|
-
|
|
210
|
-
2. **Session ID** = chave pública (64 chars hex)
|
|
211
|
-
3. **Todas as requisições** são assinadas com a chave privada
|
|
212
|
-
4. **Backend valida** a assinatura Ed25519 em cada request
|
|
213
|
-
5. **Admin access** via chaves públicas autorizadas
|
|
428
|
+
---
|
|
214
429
|
|
|
215
|
-
|
|
430
|
+
### `isCryptoAuthAuthenticated(request)`
|
|
216
431
|
|
|
217
|
-
|
|
218
|
-
- ✅ Nonce para prevenir replay attacks
|
|
219
|
-
- ✅ Validação de timestamp (5 min máximo)
|
|
220
|
-
- ✅ Timeout de sessões (30 min padrão)
|
|
221
|
-
- ✅ Chaves privadas nunca saem do cliente
|
|
222
|
-
- ✅ Stateless - sem dependência de banco de dados
|
|
432
|
+
Verifica se a requisição está autenticada.
|
|
223
433
|
|
|
224
|
-
|
|
434
|
+
```typescript
|
|
435
|
+
import { isCryptoAuthAuthenticated } from '@/plugins/crypto-auth/server'
|
|
436
|
+
|
|
437
|
+
.get('/status', ({ request }) => {
|
|
438
|
+
const isAuth = isCryptoAuthAuthenticated(request)
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
authenticated: isAuth,
|
|
442
|
+
message: isAuth ? 'Você está logado' : 'Você não está logado'
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Retorno:** `boolean`
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### `isCryptoAuthAdmin(request)`
|
|
452
|
+
|
|
453
|
+
Verifica se o usuário é administrador.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { isCryptoAuthAdmin } from '@/plugins/crypto-auth/server'
|
|
457
|
+
|
|
458
|
+
.get('/admin-check', ({ request }) => {
|
|
459
|
+
const isAdmin = isCryptoAuthAdmin(request)
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
isAdmin,
|
|
463
|
+
access: isAdmin ? 'granted' : 'denied'
|
|
464
|
+
}
|
|
465
|
+
})
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Retorno:** `boolean`
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### `hasCryptoAuthPermission(request, permission)`
|
|
473
|
+
|
|
474
|
+
Verifica se o usuário tem uma permissão específica.
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
import { hasCryptoAuthPermission } from '@/plugins/crypto-auth/server'
|
|
478
|
+
|
|
479
|
+
.get('/can-delete', ({ request }) => {
|
|
480
|
+
const canDelete = hasCryptoAuthPermission(request, 'delete')
|
|
481
|
+
|
|
482
|
+
return { canDelete }
|
|
483
|
+
})
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Retorno:** `boolean`
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## 🔄 Fluxo de Autenticação
|
|
491
|
+
|
|
492
|
+
### Diagrama Completo
|
|
493
|
+
|
|
494
|
+
```
|
|
495
|
+
┌─────────────┐ ┌─────────────┐
|
|
496
|
+
│ Cliente │ │ Servidor │
|
|
497
|
+
│ (Browser) │ │ (Elysia) │
|
|
498
|
+
└──────┬──────┘ └──────┬──────┘
|
|
499
|
+
│ │
|
|
500
|
+
│ 1. Gera par de chaves Ed25519 (uma vez) │
|
|
501
|
+
│ privateKey, publicKey │
|
|
502
|
+
│ localStorage.setItem(...) │
|
|
503
|
+
│ │
|
|
504
|
+
│ 2. Para cada request: │
|
|
505
|
+
│ - timestamp = Date.now() │
|
|
506
|
+
│ - nonce = random() │
|
|
507
|
+
│ - message = `${timestamp}:${nonce}:${body}` │
|
|
508
|
+
│ - signature = sign(message, privateKey) │
|
|
509
|
+
│ │
|
|
510
|
+
│ 3. Envia request com headers │
|
|
511
|
+
│────────────────────────────────────────────────>│
|
|
512
|
+
│ x-public-key: <publicKey> │
|
|
513
|
+
│ x-timestamp: <timestamp> │
|
|
514
|
+
│ x-nonce: <nonce> │
|
|
515
|
+
│ x-signature: <signature> │
|
|
516
|
+
│ │
|
|
517
|
+
│ │ 4. Middleware valida:
|
|
518
|
+
│ │ - Reconstrói message
|
|
519
|
+
│ │ - verify(message, signature, publicKey)
|
|
520
|
+
│ │ - Verifica timestamp
|
|
521
|
+
│ │ - Verifica se é admin (se necessário)
|
|
522
|
+
│ │
|
|
523
|
+
│ 5a. ✅ Válido │
|
|
524
|
+
│<────────────────────────────────────────────────│
|
|
525
|
+
│ 200 OK { data: ... } │
|
|
526
|
+
│ │
|
|
527
|
+
│ 5b. ❌ Inválido │
|
|
528
|
+
│<────────────────────────────────────────────────│
|
|
529
|
+
│ 401 Unauthorized { error: ... } │
|
|
530
|
+
│ │
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## 🔒 Segurança
|
|
536
|
+
|
|
537
|
+
### ✅ Proteções Implementadas
|
|
538
|
+
|
|
539
|
+
1. **Anti-Replay Attacks**
|
|
540
|
+
- Timestamp validation (maxTimeDrift)
|
|
541
|
+
- Nonce único por requisição
|
|
542
|
+
- Assinatura inclui timestamp + nonce
|
|
543
|
+
|
|
544
|
+
2. **Stateless Security**
|
|
545
|
+
- Sem sessões (não há o que roubar)
|
|
546
|
+
- Chave privada **NUNCA** sai do cliente
|
|
547
|
+
- Validação criptográfica a cada request
|
|
548
|
+
|
|
549
|
+
3. **Admin Protection**
|
|
550
|
+
- Lista whitelist de chaves públicas administrativas
|
|
551
|
+
- Validação dupla (auth + isAdmin)
|
|
552
|
+
|
|
553
|
+
4. **Type Safety**
|
|
554
|
+
- TypeScript completo
|
|
555
|
+
- Validação de schemas com TypeBox
|
|
556
|
+
|
|
557
|
+
### ⚠️ Considerações de Segurança
|
|
558
|
+
|
|
559
|
+
1. **HTTPS Obrigatório**
|
|
560
|
+
```typescript
|
|
561
|
+
// Sempre use HTTPS em produção
|
|
562
|
+
if (process.env.NODE_ENV === 'production' && !request.url.startsWith('https')) {
|
|
563
|
+
throw new Error('HTTPS required')
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
2. **Rotação de Chaves**
|
|
568
|
+
```typescript
|
|
569
|
+
// Cliente deve permitir rotação de chaves
|
|
570
|
+
function rotateKeys() {
|
|
571
|
+
const newKeypair = nacl.sign.keyPair()
|
|
572
|
+
// Migrar dados assinados com chave antiga
|
|
573
|
+
// Atualizar localStorage
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
3. **Rate Limiting**
|
|
578
|
+
```typescript
|
|
579
|
+
// Adicionar rate limiting para prevenir brute force
|
|
580
|
+
import { rateLimit } from '@/plugins/rate-limit'
|
|
581
|
+
|
|
582
|
+
.use(rateLimit({ max: 100, window: '15m' }))
|
|
583
|
+
.use(cryptoAuthRequired())
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
4. **Admin Keys em Ambiente**
|
|
587
|
+
```bash
|
|
588
|
+
# .env
|
|
589
|
+
CRYPTO_AUTH_ADMIN_KEYS=key1,key2,key3
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## 🧪 Troubleshooting
|
|
595
|
+
|
|
596
|
+
### ❌ Erro: "Authentication required"
|
|
597
|
+
|
|
598
|
+
**Problema**: Requisição sem headers de autenticação.
|
|
599
|
+
|
|
600
|
+
**Solução**:
|
|
601
|
+
```typescript
|
|
602
|
+
// Cliente deve enviar headers
|
|
603
|
+
headers: {
|
|
604
|
+
'x-public-key': publicKeyHex,
|
|
605
|
+
'x-timestamp': Date.now().toString(),
|
|
606
|
+
'x-nonce': generateNonce(),
|
|
607
|
+
'x-signature': signatureHex
|
|
608
|
+
}
|
|
609
|
+
```
|
|
225
610
|
|
|
226
|
-
|
|
611
|
+
---
|
|
227
612
|
|
|
613
|
+
### ❌ Erro: "Invalid signature"
|
|
614
|
+
|
|
615
|
+
**Problema**: Assinatura não corresponde à mensagem.
|
|
616
|
+
|
|
617
|
+
**Causas comuns**:
|
|
618
|
+
1. Chave privada incorreta
|
|
619
|
+
2. Mensagem reconstruída diferente
|
|
620
|
+
3. Ordem dos campos alterada
|
|
621
|
+
|
|
622
|
+
**Solução**:
|
|
623
|
+
```typescript
|
|
624
|
+
// Garantir ordem exata dos campos
|
|
625
|
+
const message = `${timestamp}:${nonce}:${JSON.stringify(body)}`
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
### ❌ Erro: "Timestamp drift too large"
|
|
631
|
+
|
|
632
|
+
**Problema**: Diferença entre timestamp do cliente e servidor excede `maxTimeDrift`.
|
|
633
|
+
|
|
634
|
+
**Solução**:
|
|
635
|
+
```typescript
|
|
636
|
+
// Sincronizar relógio do cliente com servidor
|
|
637
|
+
const serverTime = await fetch('/api/time').then(r => r.json())
|
|
638
|
+
const timeDrift = Date.now() - serverTime.timestamp
|
|
639
|
+
// Ajustar timestamps futuros
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
### ❌ Erro: "Admin access required"
|
|
645
|
+
|
|
646
|
+
**Problema**: Usuário não está na lista de `adminKeys`.
|
|
647
|
+
|
|
648
|
+
**Solução**:
|
|
649
|
+
```typescript
|
|
650
|
+
// Adicionar chave pública ao config
|
|
651
|
+
{
|
|
652
|
+
'crypto-auth': {
|
|
653
|
+
adminKeys: [
|
|
654
|
+
'a1b2c3d4e5f6...', // Sua chave pública
|
|
655
|
+
]
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
### 🔍 Debug Mode
|
|
663
|
+
|
|
664
|
+
Habilitar logs detalhados:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
// fluxstack.config.ts
|
|
668
|
+
{
|
|
669
|
+
'crypto-auth': {
|
|
670
|
+
enableMetrics: true
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
Verificar logs:
|
|
228
676
|
```bash
|
|
229
|
-
#
|
|
230
|
-
|
|
677
|
+
# Requisições autenticadas
|
|
678
|
+
Requisição autenticada {
|
|
679
|
+
publicKey: "a1b2c3d4...",
|
|
680
|
+
isAdmin: false,
|
|
681
|
+
path: "/api/users",
|
|
682
|
+
method: "GET"
|
|
683
|
+
}
|
|
231
684
|
|
|
232
|
-
#
|
|
233
|
-
|
|
685
|
+
# Falhas de autenticação
|
|
686
|
+
Falha na autenticação {
|
|
687
|
+
error: "Invalid signature",
|
|
688
|
+
path: "/api/users",
|
|
689
|
+
method: "POST"
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## 📚 Recursos Adicionais
|
|
696
|
+
|
|
697
|
+
### Documentação Relacionada
|
|
698
|
+
|
|
699
|
+
- [`QUICK-START-CRYPTO-AUTH.md`](../../QUICK-START-CRYPTO-AUTH.md) - Início rápido em 5 minutos
|
|
700
|
+
- [`EXEMPLO-ROTA-PROTEGIDA.md`](../../EXEMPLO-ROTA-PROTEGIDA.md) - Tutorial passo-a-passo
|
|
701
|
+
- [`CRYPTO-AUTH-MIDDLEWARE-GUIDE.md`](../../CRYPTO-AUTH-MIDDLEWARE-GUIDE.md) - Referência de middlewares
|
|
702
|
+
|
|
703
|
+
### Bibliotecas Cliente Recomendadas
|
|
704
|
+
|
|
705
|
+
- **JavaScript/TypeScript**: [TweetNaCl.js](https://github.com/dchest/tweetnacl-js)
|
|
706
|
+
- **React**: [@stablelib/ed25519](https://github.com/StableLib/stablelib)
|
|
707
|
+
|
|
708
|
+
### Exemplo de Cliente
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
// client-auth.ts
|
|
712
|
+
import nacl from 'tweetnacl'
|
|
713
|
+
import { encodeHex, decodeHex } from 'tweetnacl-util'
|
|
714
|
+
|
|
715
|
+
export class CryptoAuthClient {
|
|
716
|
+
private privateKey: Uint8Array
|
|
717
|
+
private publicKey: Uint8Array
|
|
718
|
+
|
|
719
|
+
constructor() {
|
|
720
|
+
// Carregar ou gerar chaves
|
|
721
|
+
const stored = localStorage.getItem('cryptoAuthKeys')
|
|
722
|
+
|
|
723
|
+
if (stored) {
|
|
724
|
+
const keys = JSON.parse(stored)
|
|
725
|
+
this.privateKey = decodeHex(keys.privateKey)
|
|
726
|
+
this.publicKey = decodeHex(keys.publicKey)
|
|
727
|
+
} else {
|
|
728
|
+
const keypair = nacl.sign.keyPair()
|
|
729
|
+
this.privateKey = keypair.secretKey
|
|
730
|
+
this.publicKey = keypair.publicKey
|
|
731
|
+
|
|
732
|
+
localStorage.setItem('cryptoAuthKeys', JSON.stringify({
|
|
733
|
+
privateKey: encodeHex(keypair.secretKey),
|
|
734
|
+
publicKey: encodeHex(keypair.publicKey)
|
|
735
|
+
}))
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async fetch(url: string, options: RequestInit = {}) {
|
|
740
|
+
const timestamp = Date.now().toString()
|
|
741
|
+
const nonce = encodeHex(nacl.randomBytes(16))
|
|
742
|
+
const body = options.body || ''
|
|
743
|
+
|
|
744
|
+
const message = `${timestamp}:${nonce}:${body}`
|
|
745
|
+
const signature = nacl.sign.detached(
|
|
746
|
+
new TextEncoder().encode(message),
|
|
747
|
+
this.privateKey
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
const headers = {
|
|
751
|
+
...options.headers,
|
|
752
|
+
'x-public-key': encodeHex(this.publicKey),
|
|
753
|
+
'x-timestamp': timestamp,
|
|
754
|
+
'x-nonce': nonce,
|
|
755
|
+
'x-signature': encodeHex(signature)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return fetch(url, { ...options, headers })
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
getPublicKey() {
|
|
762
|
+
return encodeHex(this.publicKey)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Uso
|
|
767
|
+
const authClient = new CryptoAuthClient()
|
|
768
|
+
const response = await authClient.fetch('/api/users', {
|
|
769
|
+
method: 'POST',
|
|
770
|
+
body: JSON.stringify({ name: 'João' })
|
|
771
|
+
})
|
|
234
772
|
```
|
|
235
773
|
|
|
236
|
-
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## 🤝 Contribuindo
|
|
777
|
+
|
|
778
|
+
Para reportar bugs ou sugerir melhorias, abra uma issue no repositório do FluxStack.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## 📄 Licença
|
|
783
|
+
|
|
784
|
+
Este plugin é parte do FluxStack e segue a mesma licença do framework.
|
|
785
|
+
|
|
786
|
+
---
|
|
237
787
|
|
|
238
|
-
|
|
788
|
+
**Desenvolvido com ❤️ pela FluxStack Team**
|