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.
- package/.env.example +1 -8
- package/app/client/src/App.tsx +1 -4
- package/app/server/index.ts +0 -4
- package/app/server/routes/index.ts +1 -5
- package/config/index.ts +1 -9
- package/core/cli/generators/plugin.ts +34 -324
- package/core/cli/generators/template-engine.ts +0 -5
- package/core/cli/plugin-discovery.ts +12 -33
- package/core/framework/server.ts +0 -10
- package/core/plugins/dependency-manager.ts +22 -89
- package/core/plugins/index.ts +0 -4
- package/core/plugins/manager.ts +2 -3
- package/core/plugins/registry.ts +1 -28
- package/core/utils/logger/index.ts +0 -4
- package/core/utils/version.ts +1 -1
- package/fluxstack.config.ts +114 -253
- package/package.json +117 -117
- package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +0 -475
- package/CRYPTO-AUTH-MIDDLEWARES.md +0 -473
- package/CRYPTO-AUTH-USAGE.md +0 -491
- package/EXEMPLO-ROTA-PROTEGIDA.md +0 -347
- package/QUICK-START-CRYPTO-AUTH.md +0 -221
- package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
- package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
- package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
- package/app/server/routes/exemplo-posts.routes.ts +0 -161
- package/core/plugins/module-resolver.ts +0 -216
- package/plugins/crypto-auth/README.md +0 -788
- package/plugins/crypto-auth/ai-context.md +0 -1282
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +0 -383
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +0 -302
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +0 -131
- package/plugins/crypto-auth/client/components/LoginButton.tsx +0 -138
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +0 -89
- package/plugins/crypto-auth/client/components/index.ts +0 -12
- package/plugins/crypto-auth/client/index.ts +0 -12
- package/plugins/crypto-auth/config/index.ts +0 -34
- package/plugins/crypto-auth/index.ts +0 -162
- package/plugins/crypto-auth/package.json +0 -66
- package/plugins/crypto-auth/server/AuthMiddleware.ts +0 -181
- package/plugins/crypto-auth/server/CryptoAuthService.ts +0 -186
- package/plugins/crypto-auth/server/index.ts +0 -22
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +0 -65
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +0 -26
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +0 -76
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +0 -45
- package/plugins/crypto-auth/server/middlewares/helpers.ts +0 -140
- package/plugins/crypto-auth/server/middlewares/index.ts +0 -22
- package/plugins/crypto-auth/server/middlewares.ts +0 -19
- package/test-crypto-auth.ts +0 -101
|
@@ -1,1282 +0,0 @@
|
|
|
1
|
-
# 🔐 Crypto Auth Plugin - AI Context Documentation
|
|
2
|
-
|
|
3
|
-
> **Plugin de Autenticação Criptográfica FluxStack**
|
|
4
|
-
> Sistema de autenticação **STATELESS** baseado em assinaturas Ed25519
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 📖 Índice Rápido
|
|
9
|
-
|
|
10
|
-
1. [Overview e Conceitos](#overview-e-conceitos)
|
|
11
|
-
2. [Arquitetura do Sistema](#arquitetura-do-sistema)
|
|
12
|
-
3. [Fluxo de Autenticação](#fluxo-de-autenticação)
|
|
13
|
-
4. [Componentes Principais](#componentes-principais)
|
|
14
|
-
5. [Padrões e Boas Práticas](#padrões-e-boas-práticas)
|
|
15
|
-
6. [Troubleshooting](#troubleshooting)
|
|
16
|
-
7. [Exemplos de Uso](#exemplos-de-uso)
|
|
17
|
-
8. [Segurança](#segurança)
|
|
18
|
-
9. [Testes](#testes)
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 🎯 Overview e Conceitos
|
|
23
|
-
|
|
24
|
-
### O que é este plugin?
|
|
25
|
-
|
|
26
|
-
Sistema de autenticação **SEM SESSÕES** que usa criptografia Ed25519 para validar requisições.
|
|
27
|
-
|
|
28
|
-
### Conceitos-chave
|
|
29
|
-
|
|
30
|
-
**🚫 NÃO HÁ SESSÕES NO SERVIDOR**
|
|
31
|
-
- Servidor NÃO armazena estado de autenticação
|
|
32
|
-
- Cada requisição é validada independentemente
|
|
33
|
-
- Chave pública identifica o usuário
|
|
34
|
-
|
|
35
|
-
**🔑 Par de Chaves Ed25519**
|
|
36
|
-
- **Chave Privada**: NUNCA sai do navegador, armazenada em localStorage
|
|
37
|
-
- **Chave Pública**: Enviada em cada requisição, identifica o usuário
|
|
38
|
-
|
|
39
|
-
**✍️ Assinatura Digital**
|
|
40
|
-
- Cliente assina cada requisição com chave privada
|
|
41
|
-
- Servidor valida assinatura usando chave pública recebida
|
|
42
|
-
- Assinatura inclui: `publicKey:timestamp:nonce:message`
|
|
43
|
-
|
|
44
|
-
**🛡️ Proteções**
|
|
45
|
-
- **Replay Attack**: Nonces únicos impedem reutilização de assinaturas
|
|
46
|
-
- **Time Drift**: Timestamps impedem requisições muito antigas (5 min)
|
|
47
|
-
- **Man-in-the-Middle**: Assinaturas são únicas por requisição
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 🏗️ Arquitetura do Sistema
|
|
52
|
-
|
|
53
|
-
### Estrutura de Arquivos
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
plugins/crypto-auth/
|
|
57
|
-
├── index.ts # Plugin principal e hooks
|
|
58
|
-
├── ai-context.md # Esta documentação
|
|
59
|
-
├── server/
|
|
60
|
-
│ ├── index.ts # Exports do servidor
|
|
61
|
-
│ ├── CryptoAuthService.ts # Validação de assinaturas
|
|
62
|
-
│ └── AuthMiddleware.ts # Middleware de autenticação
|
|
63
|
-
├── client/
|
|
64
|
-
│ ├── index.ts # Exports do cliente
|
|
65
|
-
│ ├── CryptoAuthClient.ts # Cliente de autenticação
|
|
66
|
-
│ └── components/
|
|
67
|
-
│ └── AuthProvider.tsx # React Context Provider
|
|
68
|
-
└── README.md # Documentação do usuário
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Fluxo de Dados
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
75
|
-
│ CLIENTE │
|
|
76
|
-
├─────────────────────────────────────────────────────────────┤
|
|
77
|
-
│ 1. Gera par de chaves Ed25519 (local) │
|
|
78
|
-
│ privateKey → NUNCA enviada │
|
|
79
|
-
│ publicKey → Enviada em cada request │
|
|
80
|
-
│ │
|
|
81
|
-
│ 2. Para cada requisição: │
|
|
82
|
-
│ - timestamp = Date.now() │
|
|
83
|
-
│ - nonce = crypto.randomBytes(16) │
|
|
84
|
-
│ - message = "GET:/api/users" │
|
|
85
|
-
│ - fullMessage = publicKey:timestamp:nonce:message │
|
|
86
|
-
│ - signature = sign(fullMessage, privateKey) │
|
|
87
|
-
│ │
|
|
88
|
-
│ 3. Headers enviados: │
|
|
89
|
-
│ x-public-key: <publicKey> │
|
|
90
|
-
│ x-timestamp: <timestamp> │
|
|
91
|
-
│ x-nonce: <nonce> │
|
|
92
|
-
│ x-signature: <signature> │
|
|
93
|
-
└─────────────────────────────────────────────────────────────┘
|
|
94
|
-
↓
|
|
95
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
96
|
-
│ SERVIDOR │
|
|
97
|
-
├─────────────────────────────────────────────────────────────┤
|
|
98
|
-
│ 1. Plugin Hook onRequest │
|
|
99
|
-
│ - AuthMiddleware.authenticate(context) │
|
|
100
|
-
│ │
|
|
101
|
-
│ 2. AuthMiddleware │
|
|
102
|
-
│ - Extrai headers (public-key, timestamp, nonce, sig) │
|
|
103
|
-
│ - Chama CryptoAuthService.validateRequest() │
|
|
104
|
-
│ │
|
|
105
|
-
│ 3. CryptoAuthService.validateRequest() │
|
|
106
|
-
│ ✓ Valida formato da chave pública │
|
|
107
|
-
│ ✓ Verifica time drift (< 5 min) │
|
|
108
|
-
│ ✓ Verifica se nonce já foi usado │
|
|
109
|
-
│ ✓ Reconstrói mensagem: publicKey:timestamp:nonce:message │
|
|
110
|
-
│ ✓ Verifica assinatura: verify(signature, message, publicKey) │
|
|
111
|
-
│ ✓ Marca nonce como usado │
|
|
112
|
-
│ ✓ Retorna user: { publicKey, isAdmin, permissions } │
|
|
113
|
-
│ │
|
|
114
|
-
│ 4. Se válido: │
|
|
115
|
-
│ - context.request.user = user │
|
|
116
|
-
│ - Processa rota normalmente │
|
|
117
|
-
│ │
|
|
118
|
-
│ 5. Se inválido: │
|
|
119
|
-
│ - context.handled = true │
|
|
120
|
-
│ - context.response = 401 Unauthorized │
|
|
121
|
-
│ - Rota NÃO é executada │
|
|
122
|
-
└─────────────────────────────────────────────────────────────┘
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## 🔄 Fluxo de Autenticação
|
|
128
|
-
|
|
129
|
-
### 1. Inicialização do Cliente
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// Cliente inicializa automaticamente ou manualmente
|
|
133
|
-
const client = new CryptoAuthClient({
|
|
134
|
-
autoInit: true, // Gera chaves automaticamente
|
|
135
|
-
storage: 'localStorage' // Onde armazenar chaves
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// Se autoInit: true
|
|
139
|
-
// → Verifica se já existem chaves no localStorage
|
|
140
|
-
// → Se sim: carrega chaves existentes
|
|
141
|
-
// → Se não: gera novo par de chaves
|
|
142
|
-
|
|
143
|
-
// Chaves armazenadas em: localStorage['fluxstack_crypto_keys']
|
|
144
|
-
// Formato: { publicKey, privateKey, createdAt }
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### 2. Requisição Assinada
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// Método automático (recomendado)
|
|
151
|
-
const response = await client.fetch('/api/users')
|
|
152
|
-
|
|
153
|
-
// O que acontece internamente:
|
|
154
|
-
// 1. timestamp = Date.now()
|
|
155
|
-
// 2. nonce = generateNonce() // 16 bytes aleatórios
|
|
156
|
-
// 3. message = buildMessage('GET', '/api/users', null)
|
|
157
|
-
// → "GET:/api/users"
|
|
158
|
-
// 4. fullMessage = `${publicKey}:${timestamp}:${nonce}:${message}`
|
|
159
|
-
// 5. messageHash = sha256(fullMessage)
|
|
160
|
-
// 6. signature = ed25519.sign(messageHash, privateKey)
|
|
161
|
-
// 7. Headers adicionados automaticamente
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### 3. Validação no Servidor
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
// Plugin Hook onRequest (automático)
|
|
168
|
-
onRequest: async (context) => {
|
|
169
|
-
const authResult = await authMiddleware.authenticate(context)
|
|
170
|
-
|
|
171
|
-
if (authResult.success) {
|
|
172
|
-
// ✅ Usuário autenticado
|
|
173
|
-
context.request.user = authResult.user
|
|
174
|
-
// Rota é executada normalmente
|
|
175
|
-
} else if (authResult.required) {
|
|
176
|
-
// ❌ Falha na autenticação
|
|
177
|
-
context.handled = true
|
|
178
|
-
context.response = new Response(JSON.stringify({
|
|
179
|
-
success: false,
|
|
180
|
-
error: authResult.error
|
|
181
|
-
}), { status: 401 })
|
|
182
|
-
// Rota NÃO é executada
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### 4. Acesso nas Rotas
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
// Rotas protegidas podem acessar user
|
|
191
|
-
.get('/api/users', ({ request }) => {
|
|
192
|
-
const user = (request as any).user
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
user: {
|
|
196
|
-
publicKey: user.publicKey,
|
|
197
|
-
isAdmin: user.isAdmin,
|
|
198
|
-
permissions: user.permissions
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## 🧩 Componentes Principais
|
|
207
|
-
|
|
208
|
-
### 1. CryptoAuthService (Backend)
|
|
209
|
-
|
|
210
|
-
**Localização**: `plugins/crypto-auth/server/CryptoAuthService.ts`
|
|
211
|
-
|
|
212
|
-
**Responsabilidades**:
|
|
213
|
-
- Validar assinaturas Ed25519
|
|
214
|
-
- Gerenciar nonces (prevenir replay attacks)
|
|
215
|
-
- Verificar drift de tempo
|
|
216
|
-
- Identificar usuários admin
|
|
217
|
-
|
|
218
|
-
**Métodos Principais**:
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
class CryptoAuthService {
|
|
222
|
-
// Validar uma requisição assinada
|
|
223
|
-
async validateRequest(data: {
|
|
224
|
-
publicKey: string
|
|
225
|
-
timestamp: number
|
|
226
|
-
nonce: string
|
|
227
|
-
signature: string
|
|
228
|
-
message?: string
|
|
229
|
-
}): Promise<AuthResult>
|
|
230
|
-
|
|
231
|
-
// Verificar se chave pública é válida (64 hex chars)
|
|
232
|
-
private isValidPublicKey(publicKey: string): boolean
|
|
233
|
-
|
|
234
|
-
// Limpar nonces antigos (executado a cada 5 min)
|
|
235
|
-
private cleanupOldNonces(): void
|
|
236
|
-
|
|
237
|
-
// Retornar estatísticas
|
|
238
|
-
getStats(): { usedNonces: number; adminKeys: number }
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
**Estado Interno**:
|
|
243
|
-
```typescript
|
|
244
|
-
private usedNonces: Map<string, number> // `${publicKey}:${nonce}` → timestamp
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**Importante**:
|
|
248
|
-
- `usedNonces` é limpo automaticamente a cada 5 minutos
|
|
249
|
-
- Nonces mais antigos que `maxTimeDrift * 2` são removidos
|
|
250
|
-
- NÃO HÁ armazenamento de sessões!
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
### 2. AuthMiddleware (Backend)
|
|
255
|
-
|
|
256
|
-
**Localização**: `plugins/crypto-auth/server/AuthMiddleware.ts`
|
|
257
|
-
|
|
258
|
-
**Responsabilidades**:
|
|
259
|
-
- Verificar se rota requer autenticação
|
|
260
|
-
- Extrair headers de autenticação
|
|
261
|
-
- Chamar CryptoAuthService para validar
|
|
262
|
-
- Decidir se permite acesso
|
|
263
|
-
|
|
264
|
-
**Métodos Principais**:
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
class AuthMiddleware {
|
|
268
|
-
// Autenticar uma requisição
|
|
269
|
-
async authenticate(context: RequestContext): Promise<{
|
|
270
|
-
success: boolean
|
|
271
|
-
required: boolean // Se autenticação é obrigatória
|
|
272
|
-
error?: string
|
|
273
|
-
user?: User
|
|
274
|
-
}>
|
|
275
|
-
|
|
276
|
-
// Verificar se rota está protegida
|
|
277
|
-
private isProtectedRoute(path: string): boolean
|
|
278
|
-
|
|
279
|
-
// Verificar se rota é pública
|
|
280
|
-
private isPublicRoute(path: string): boolean
|
|
281
|
-
|
|
282
|
-
// Extrair headers de auth
|
|
283
|
-
private extractAuthHeaders(headers): AuthHeaders | null
|
|
284
|
-
|
|
285
|
-
// Construir mensagem para validação
|
|
286
|
-
private buildMessage(context: RequestContext): string
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
**Lógica de Decisão**:
|
|
291
|
-
```typescript
|
|
292
|
-
// 1. Se rota pública → success: true, required: false
|
|
293
|
-
// 2. Se rota protegida sem headers → success: false, required: true
|
|
294
|
-
// 3. Se rota protegida com headers → valida assinatura
|
|
295
|
-
// - Se válida → success: true, required: true, user: {...}
|
|
296
|
-
// - Se inválida → success: false, required: true, error: "..."
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
### 3. CryptoAuthClient (Frontend)
|
|
302
|
-
|
|
303
|
-
**Localização**: `plugins/crypto-auth/client/CryptoAuthClient.ts`
|
|
304
|
-
|
|
305
|
-
**Responsabilidades**:
|
|
306
|
-
- Gerar e gerenciar par de chaves
|
|
307
|
-
- Assinar requisições automaticamente
|
|
308
|
-
- Armazenar chaves em localStorage
|
|
309
|
-
|
|
310
|
-
**Métodos Públicos**:
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
class CryptoAuthClient {
|
|
314
|
-
// Inicializar (gerar ou carregar chaves)
|
|
315
|
-
initialize(): KeyPair
|
|
316
|
-
|
|
317
|
-
// Criar novo par de chaves
|
|
318
|
-
createNewKeys(): KeyPair
|
|
319
|
-
|
|
320
|
-
// Fazer requisição autenticada
|
|
321
|
-
async fetch(url: string, options?: RequestInit): Promise<Response>
|
|
322
|
-
|
|
323
|
-
// Obter chaves atuais
|
|
324
|
-
getKeys(): KeyPair | null
|
|
325
|
-
|
|
326
|
-
// Verificar se está inicializado
|
|
327
|
-
isInitialized(): boolean
|
|
328
|
-
|
|
329
|
-
// Limpar chaves (logout)
|
|
330
|
-
clearKeys(): void
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Métodos Privados**:
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
// Assinar mensagem
|
|
338
|
-
private signMessage(message: string, timestamp: number, nonce: string): string
|
|
339
|
-
|
|
340
|
-
// Construir mensagem para assinar
|
|
341
|
-
private buildMessage(method: string, url: string, body?: any): string
|
|
342
|
-
|
|
343
|
-
// Gerar nonce aleatório
|
|
344
|
-
private generateNonce(): string
|
|
345
|
-
|
|
346
|
-
// Carregar chaves do storage
|
|
347
|
-
private loadKeys(): KeyPair | null
|
|
348
|
-
|
|
349
|
-
// Salvar chaves no storage
|
|
350
|
-
private saveKeys(keys: KeyPair): void
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**Formato de Mensagem**:
|
|
354
|
-
```typescript
|
|
355
|
-
// Para GET /api/users
|
|
356
|
-
message = "GET:/api/users"
|
|
357
|
-
|
|
358
|
-
// Para POST /api/users com body
|
|
359
|
-
message = "POST:/api/users:{\"name\":\"João\"}"
|
|
360
|
-
|
|
361
|
-
// Mensagem completa assinada
|
|
362
|
-
fullMessage = `${publicKey}:${timestamp}:${nonce}:${message}`
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
### 4. AuthProvider (React Component)
|
|
368
|
-
|
|
369
|
-
**Localização**: `plugins/crypto-auth/client/components/AuthProvider.tsx`
|
|
370
|
-
|
|
371
|
-
**Responsabilidades**:
|
|
372
|
-
- Prover contexto de autenticação via React Context
|
|
373
|
-
- Gerenciar estado de chaves
|
|
374
|
-
- Callbacks para eventos (onKeysChange, onError)
|
|
375
|
-
|
|
376
|
-
**Interface**:
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
export interface AuthContextValue {
|
|
380
|
-
client: CryptoAuthClient
|
|
381
|
-
keys: KeyPair | null
|
|
382
|
-
hasKeys: boolean
|
|
383
|
-
isLoading: boolean
|
|
384
|
-
error: string | null
|
|
385
|
-
createKeys: () => void
|
|
386
|
-
clearKeys: () => void
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Hook para usar o contexto
|
|
390
|
-
export const useAuth = (): AuthContextValue
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
**Uso**:
|
|
394
|
-
|
|
395
|
-
```tsx
|
|
396
|
-
// Wrapper da aplicação
|
|
397
|
-
<AuthProvider
|
|
398
|
-
config={{ storage: 'localStorage' }}
|
|
399
|
-
onKeysChange={(hasKeys, keys) => console.log('Keys changed')}
|
|
400
|
-
onError={(error) => console.error(error)}
|
|
401
|
-
>
|
|
402
|
-
<App />
|
|
403
|
-
</AuthProvider>
|
|
404
|
-
|
|
405
|
-
// Dentro de componentes
|
|
406
|
-
function MyComponent() {
|
|
407
|
-
const { keys, hasKeys, createKeys, clearKeys } = useAuth()
|
|
408
|
-
|
|
409
|
-
if (!hasKeys) {
|
|
410
|
-
return <button onClick={createKeys}>Login</button>
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return <button onClick={clearKeys}>Logout</button>
|
|
414
|
-
}
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
### 5. Plugin Principal (index.ts)
|
|
420
|
-
|
|
421
|
-
**Localização**: `plugins/crypto-auth/index.ts`
|
|
422
|
-
|
|
423
|
-
**Responsabilidades**:
|
|
424
|
-
- Definir schema de configuração
|
|
425
|
-
- Hooks do plugin (setup, onRequest, onResponse, onServerStart)
|
|
426
|
-
- Rotas de informação (/api/auth/info)
|
|
427
|
-
|
|
428
|
-
**Configuração**:
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
defaultConfig: {
|
|
432
|
-
enabled: true,
|
|
433
|
-
maxTimeDrift: 300000, // 5 minutos em ms
|
|
434
|
-
adminKeys: [], // Array de chaves públicas admin
|
|
435
|
-
protectedRoutes: [
|
|
436
|
-
"/api/admin/*",
|
|
437
|
-
"/api/crypto-auth/protected",
|
|
438
|
-
"/api/crypto-auth/admin"
|
|
439
|
-
],
|
|
440
|
-
publicRoutes: [
|
|
441
|
-
"/api/crypto-auth/public",
|
|
442
|
-
"/api/health",
|
|
443
|
-
"/api/docs",
|
|
444
|
-
"/swagger"
|
|
445
|
-
],
|
|
446
|
-
enableMetrics: true
|
|
447
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
**Hooks**:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
// 1. setup - Inicialização
|
|
454
|
-
setup: async (context) => {
|
|
455
|
-
const authService = new CryptoAuthService(...)
|
|
456
|
-
const authMiddleware = new AuthMiddleware(...)
|
|
457
|
-
|
|
458
|
-
// Armazenar no global para acesso nos hooks
|
|
459
|
-
(global as any).cryptoAuthService = authService
|
|
460
|
-
(global as any).cryptoAuthMiddleware = authMiddleware
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// 2. onRequest - Validar cada requisição
|
|
464
|
-
onRequest: async (context) => {
|
|
465
|
-
const authResult = await authMiddleware.authenticate(context)
|
|
466
|
-
|
|
467
|
-
if (authResult.success) {
|
|
468
|
-
context.request.user = authResult.user // ✅
|
|
469
|
-
} else if (authResult.required) {
|
|
470
|
-
context.handled = true // ❌
|
|
471
|
-
context.response = new Response(...)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// 3. onResponse - Métricas (opcional)
|
|
476
|
-
onResponse: async (context) => {
|
|
477
|
-
// Log de requisições autenticadas
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// 4. onServerStart - Log de status
|
|
481
|
-
onServerStart: async (context) => {
|
|
482
|
-
logger.info("Crypto Auth plugin ativo")
|
|
483
|
-
}
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## 📋 Padrões e Boas Práticas
|
|
489
|
-
|
|
490
|
-
### ✅ Sempre Fazer
|
|
491
|
-
|
|
492
|
-
1. **Usar cliente nativo para requisições protegidas**
|
|
493
|
-
```typescript
|
|
494
|
-
// ✅ Correto
|
|
495
|
-
const response = await authClient.fetch('/api/protected')
|
|
496
|
-
|
|
497
|
-
// ❌ Errado - não inclui assinatura
|
|
498
|
-
const response = await fetch('/api/protected')
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
2. **Verificar se usuário está autenticado nas rotas**
|
|
502
|
-
```typescript
|
|
503
|
-
// ✅ Correto
|
|
504
|
-
.get('/api/users', ({ request }) => {
|
|
505
|
-
const user = (request as any).user
|
|
506
|
-
if (!user) {
|
|
507
|
-
return { error: 'Unauthorized' }
|
|
508
|
-
}
|
|
509
|
-
// ...
|
|
510
|
-
})
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
3. **Adicionar novas rotas protegidas na config**
|
|
514
|
-
```typescript
|
|
515
|
-
// config/app.config.ts
|
|
516
|
-
plugins: {
|
|
517
|
-
config: {
|
|
518
|
-
'crypto-auth': {
|
|
519
|
-
protectedRoutes: [
|
|
520
|
-
"/api/admin/*",
|
|
521
|
-
"/api/crypto-auth/protected",
|
|
522
|
-
"/api/users/*" // ✅ Nova rota
|
|
523
|
-
]
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
4. **Tratar erros de autenticação no frontend**
|
|
530
|
-
```typescript
|
|
531
|
-
try {
|
|
532
|
-
const response = await authClient.fetch('/api/protected')
|
|
533
|
-
if (response.status === 401) {
|
|
534
|
-
// Chaves inválidas, criar novas
|
|
535
|
-
authClient.clearKeys()
|
|
536
|
-
authClient.createNewKeys()
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
console.error('Auth error:', error)
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
5. **Verificar permissões de admin quando necessário**
|
|
544
|
-
```typescript
|
|
545
|
-
.get('/api/admin/users', ({ request, set }) => {
|
|
546
|
-
const user = (request as any).user
|
|
547
|
-
|
|
548
|
-
if (!user?.isAdmin) {
|
|
549
|
-
set.status = 403
|
|
550
|
-
return { error: 'Admin access required' }
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Lógica admin
|
|
554
|
-
})
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
### ❌ Nunca Fazer
|
|
560
|
-
|
|
561
|
-
1. **NÃO enviar chave privada ao servidor**
|
|
562
|
-
```typescript
|
|
563
|
-
// ❌ NUNCA FAZER ISSO!
|
|
564
|
-
await fetch('/api/register', {
|
|
565
|
-
body: JSON.stringify({
|
|
566
|
-
privateKey: keys.privateKey // PERIGO!
|
|
567
|
-
})
|
|
568
|
-
})
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
2. **NÃO armazenar sessões no servidor**
|
|
572
|
-
```typescript
|
|
573
|
-
// ❌ Viola arquitetura stateless
|
|
574
|
-
const sessions = new Map()
|
|
575
|
-
sessions.set(publicKey, userData)
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
3. **NÃO confiar apenas na chave pública**
|
|
579
|
-
```typescript
|
|
580
|
-
// ❌ Permite spoofing
|
|
581
|
-
.get('/api/users', ({ headers }) => {
|
|
582
|
-
const publicKey = headers['x-public-key']
|
|
583
|
-
// FALTA: validar assinatura!
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
// ✅ Correto - middleware já validou
|
|
587
|
-
.get('/api/users', ({ request }) => {
|
|
588
|
-
const user = (request as any).user // Validado!
|
|
589
|
-
})
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
4. **NÃO permitir timestamp muito antigo/futuro**
|
|
593
|
-
```typescript
|
|
594
|
-
// ❌ Vulnerável a replay attack
|
|
595
|
-
const maxTimeDrift = 24 * 60 * 60 * 1000 // 24 horas - MUITO!
|
|
596
|
-
|
|
597
|
-
// ✅ Correto
|
|
598
|
-
const maxTimeDrift = 5 * 60 * 1000 // 5 minutos
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
5. **NÃO reutilizar nonces**
|
|
602
|
-
```typescript
|
|
603
|
-
// ❌ Cliente NÃO deve fazer isso
|
|
604
|
-
const nonce = "fixed-nonce" // Sempre igual!
|
|
605
|
-
|
|
606
|
-
// ✅ Correto
|
|
607
|
-
const nonce = generateNonce() // Aleatório sempre
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
---
|
|
611
|
-
|
|
612
|
-
## 🔧 Troubleshooting
|
|
613
|
-
|
|
614
|
-
### Problema 1: "Assinatura inválida"
|
|
615
|
-
|
|
616
|
-
**Sintomas**: Requisições retornam 401 com erro "Assinatura inválida"
|
|
617
|
-
|
|
618
|
-
**Causas Possíveis**:
|
|
619
|
-
1. Chaves públicas/privadas não correspondem
|
|
620
|
-
2. Mensagem construída incorretamente
|
|
621
|
-
3. Timestamp/nonce diferentes no cliente e servidor
|
|
622
|
-
4. Corpo da requisição não incluído na assinatura
|
|
623
|
-
|
|
624
|
-
**Debug**:
|
|
625
|
-
```typescript
|
|
626
|
-
// No cliente - log mensagem assinada
|
|
627
|
-
const fullMessage = `${publicKey}:${timestamp}:${nonce}:${message}`
|
|
628
|
-
console.log('Client message:', fullMessage)
|
|
629
|
-
console.log('Signature:', signature)
|
|
630
|
-
|
|
631
|
-
// No servidor - log mensagem reconstruída
|
|
632
|
-
const messageToVerify = `${publicKey}:${timestamp}:${nonce}:${message}`
|
|
633
|
-
console.log('Server message:', messageToVerify)
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
**Solução**:
|
|
637
|
-
- Verificar se cliente e servidor constroem mensagem idêntica
|
|
638
|
-
- Confirmar que timestamp e nonce estão sendo enviados corretamente
|
|
639
|
-
- Para POST/PUT, verificar se body está sendo incluído na mensagem
|
|
640
|
-
|
|
641
|
-
---
|
|
642
|
-
|
|
643
|
-
### Problema 2: "Nonce já utilizado"
|
|
644
|
-
|
|
645
|
-
**Sintomas**: Segunda requisição idêntica retorna 401
|
|
646
|
-
|
|
647
|
-
**Causa**: Replay attack protection funcionando (comportamento esperado!)
|
|
648
|
-
|
|
649
|
-
**Quando é bug**:
|
|
650
|
-
- Se acontece com requisições DIFERENTES
|
|
651
|
-
- Se nonce não está sendo gerado aleatoriamente
|
|
652
|
-
|
|
653
|
-
**Debug**:
|
|
654
|
-
```typescript
|
|
655
|
-
// Verificar geração de nonce
|
|
656
|
-
console.log('Nonce 1:', generateNonce())
|
|
657
|
-
console.log('Nonce 2:', generateNonce())
|
|
658
|
-
// Devem ser SEMPRE diferentes!
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
**Solução**:
|
|
662
|
-
- Se está testando manualmente, gerar novo nonce a cada tentativa
|
|
663
|
-
- Verificar que `crypto.randomBytes()` está funcionando
|
|
664
|
-
|
|
665
|
-
---
|
|
666
|
-
|
|
667
|
-
### Problema 3: "Timestamp inválido ou expirado"
|
|
668
|
-
|
|
669
|
-
**Sintomas**: Requisições retornam 401 com erro de timestamp
|
|
670
|
-
|
|
671
|
-
**Causas Possíveis**:
|
|
672
|
-
1. Relógio do cliente desincronizado
|
|
673
|
-
2. Requisição demorou muito para chegar ao servidor
|
|
674
|
-
3. `maxTimeDrift` configurado muito curto
|
|
675
|
-
|
|
676
|
-
**Debug**:
|
|
677
|
-
```typescript
|
|
678
|
-
const clientTime = Date.now()
|
|
679
|
-
const serverTime = Date.now() // No servidor
|
|
680
|
-
const drift = Math.abs(serverTime - clientTime)
|
|
681
|
-
console.log('Time drift:', drift, 'ms')
|
|
682
|
-
console.log('Max allowed:', maxTimeDrift, 'ms')
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
**Solução**:
|
|
686
|
-
- Sincronizar relógio do sistema
|
|
687
|
-
- Aumentar `maxTimeDrift` se necessário (mas não muito!)
|
|
688
|
-
- Verificar latência de rede
|
|
689
|
-
|
|
690
|
-
---
|
|
691
|
-
|
|
692
|
-
### Problema 4: "User undefined nas rotas"
|
|
693
|
-
|
|
694
|
-
**Sintomas**: `request.user` é `undefined` mesmo com autenticação válida
|
|
695
|
-
|
|
696
|
-
**Causa**: User não está sendo propagado corretamente do middleware
|
|
697
|
-
|
|
698
|
-
**Debug**:
|
|
699
|
-
```typescript
|
|
700
|
-
// No plugin index.ts - verificar hook onRequest
|
|
701
|
-
if (authResult.success && authResult.user) {
|
|
702
|
-
context.request.user = authResult.user // ✅ Deve estar aqui
|
|
703
|
-
console.log('User set:', authResult.user)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Na rota
|
|
707
|
-
console.log('User received:', (request as any).user)
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
**Solução**:
|
|
711
|
-
- Verificar que `context.request.user` está sendo definido no hook
|
|
712
|
-
- Confirmar que middleware está retornando `user` no authResult
|
|
713
|
-
|
|
714
|
-
---
|
|
715
|
-
|
|
716
|
-
### Problema 5: Rotas públicas retornando 401
|
|
717
|
-
|
|
718
|
-
**Sintomas**: Rotas que deveriam ser públicas exigem autenticação
|
|
719
|
-
|
|
720
|
-
**Causa**: Rota não está na lista de `publicRoutes`
|
|
721
|
-
|
|
722
|
-
**Debug**:
|
|
723
|
-
```typescript
|
|
724
|
-
// Verificar configuração
|
|
725
|
-
console.log('Public routes:', config.publicRoutes)
|
|
726
|
-
console.log('Request path:', context.path)
|
|
727
|
-
console.log('Is public?:', isPublicRoute(context.path))
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
**Solução**:
|
|
731
|
-
```typescript
|
|
732
|
-
// config/app.config.ts
|
|
733
|
-
plugins: {
|
|
734
|
-
config: {
|
|
735
|
-
'crypto-auth': {
|
|
736
|
-
publicRoutes: [
|
|
737
|
-
"/api/health",
|
|
738
|
-
"/api/docs",
|
|
739
|
-
"/api/crypto-auth/public", // Adicionar rota
|
|
740
|
-
"/swagger"
|
|
741
|
-
]
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
---
|
|
748
|
-
|
|
749
|
-
## 💡 Exemplos de Uso
|
|
750
|
-
|
|
751
|
-
### Exemplo 1: Requisição Simples
|
|
752
|
-
|
|
753
|
-
```typescript
|
|
754
|
-
// Cliente
|
|
755
|
-
import { CryptoAuthClient } from '@/plugins/crypto-auth/client'
|
|
756
|
-
|
|
757
|
-
const client = new CryptoAuthClient({ autoInit: true })
|
|
758
|
-
|
|
759
|
-
// Fazer requisição protegida
|
|
760
|
-
const response = await client.fetch('/api/users')
|
|
761
|
-
const data = await response.json()
|
|
762
|
-
|
|
763
|
-
console.log('Users:', data)
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
```typescript
|
|
767
|
-
// Servidor - Rota
|
|
768
|
-
.get('/api/users', ({ request }) => {
|
|
769
|
-
const user = (request as any).user
|
|
770
|
-
|
|
771
|
-
return {
|
|
772
|
-
success: true,
|
|
773
|
-
user: {
|
|
774
|
-
publicKey: user.publicKey,
|
|
775
|
-
isAdmin: user.isAdmin
|
|
776
|
-
},
|
|
777
|
-
users: [/* ... */]
|
|
778
|
-
}
|
|
779
|
-
})
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
---
|
|
783
|
-
|
|
784
|
-
### Exemplo 2: Requisição POST com Body
|
|
785
|
-
|
|
786
|
-
```typescript
|
|
787
|
-
// Cliente
|
|
788
|
-
const newUser = { name: 'João', email: 'joao@test.com' }
|
|
789
|
-
|
|
790
|
-
const response = await client.fetch('/api/users', {
|
|
791
|
-
method: 'POST',
|
|
792
|
-
body: JSON.stringify(newUser)
|
|
793
|
-
})
|
|
794
|
-
|
|
795
|
-
const data = await response.json()
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
// Servidor
|
|
800
|
-
.post('/api/users', async ({ request, body }) => {
|
|
801
|
-
const user = (request as any).user
|
|
802
|
-
|
|
803
|
-
// Body é assinado automaticamente
|
|
804
|
-
const newUser = await createUser(body)
|
|
805
|
-
|
|
806
|
-
return {
|
|
807
|
-
success: true,
|
|
808
|
-
user: newUser,
|
|
809
|
-
authenticatedBy: user.publicKey
|
|
810
|
-
}
|
|
811
|
-
})
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
---
|
|
815
|
-
|
|
816
|
-
### Exemplo 3: Rota Admin
|
|
817
|
-
|
|
818
|
-
```typescript
|
|
819
|
-
// Cliente
|
|
820
|
-
const response = await client.fetch('/api/admin/stats')
|
|
821
|
-
|
|
822
|
-
if (response.status === 403) {
|
|
823
|
-
console.error('Você não é admin!')
|
|
824
|
-
}
|
|
825
|
-
```
|
|
826
|
-
|
|
827
|
-
```typescript
|
|
828
|
-
// Servidor
|
|
829
|
-
.get('/api/admin/stats', ({ request, set }) => {
|
|
830
|
-
const user = (request as any).user
|
|
831
|
-
|
|
832
|
-
// Verificar permissões
|
|
833
|
-
if (!user?.isAdmin) {
|
|
834
|
-
set.status = 403
|
|
835
|
-
return {
|
|
836
|
-
success: false,
|
|
837
|
-
error: 'Admin access required',
|
|
838
|
-
yourPermissions: user?.permissions || []
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return {
|
|
843
|
-
success: true,
|
|
844
|
-
stats: {
|
|
845
|
-
totalUsers: 100,
|
|
846
|
-
activeUsers: 50
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
})
|
|
850
|
-
```
|
|
851
|
-
|
|
852
|
-
---
|
|
853
|
-
|
|
854
|
-
### Exemplo 4: React Component com AuthProvider
|
|
855
|
-
|
|
856
|
-
```tsx
|
|
857
|
-
import { useAuth } from '@/plugins/crypto-auth/client'
|
|
858
|
-
|
|
859
|
-
function LoginButton() {
|
|
860
|
-
const { keys, hasKeys, isLoading, createKeys, clearKeys } = useAuth()
|
|
861
|
-
|
|
862
|
-
if (isLoading) {
|
|
863
|
-
return <div>Carregando...</div>
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
if (!hasKeys) {
|
|
867
|
-
return (
|
|
868
|
-
<button onClick={createKeys}>
|
|
869
|
-
Gerar Chaves de Autenticação
|
|
870
|
-
</button>
|
|
871
|
-
)
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
return (
|
|
875
|
-
<div>
|
|
876
|
-
<p>Autenticado: {keys.publicKey.substring(0, 16)}...</p>
|
|
877
|
-
<button onClick={clearKeys}>Logout</button>
|
|
878
|
-
</div>
|
|
879
|
-
)
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
function ProtectedData() {
|
|
883
|
-
const { client, hasKeys } = useAuth()
|
|
884
|
-
const [data, setData] = useState(null)
|
|
885
|
-
|
|
886
|
-
useEffect(() => {
|
|
887
|
-
if (hasKeys) {
|
|
888
|
-
client.fetch('/api/protected')
|
|
889
|
-
.then(r => r.json())
|
|
890
|
-
.then(setData)
|
|
891
|
-
}
|
|
892
|
-
}, [hasKeys])
|
|
893
|
-
|
|
894
|
-
return <pre>{JSON.stringify(data, null, 2)}</pre>
|
|
895
|
-
}
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
---
|
|
899
|
-
|
|
900
|
-
### Exemplo 5: Adicionar Chave Admin
|
|
901
|
-
|
|
902
|
-
```typescript
|
|
903
|
-
// 1. Gerar chave pública de um usuário admin
|
|
904
|
-
const adminClient = new CryptoAuthClient()
|
|
905
|
-
const adminKeys = adminClient.createNewKeys()
|
|
906
|
-
console.log('Admin Public Key:', adminKeys.publicKey)
|
|
907
|
-
|
|
908
|
-
// 2. Adicionar na configuração
|
|
909
|
-
// config/app.config.ts
|
|
910
|
-
plugins: {
|
|
911
|
-
config: {
|
|
912
|
-
'crypto-auth': {
|
|
913
|
-
adminKeys: [
|
|
914
|
-
"7443b54b3c8e2f1a9d5c6e4b2f8a1d3c9e5b7a2f4d8c1e6b3a9d5c7e2f4b8a1d"
|
|
915
|
-
]
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// 3. Usuário com essa chave pública terá isAdmin: true
|
|
921
|
-
const response = await adminClient.fetch('/api/admin/users')
|
|
922
|
-
// ✅ Acesso permitido
|
|
923
|
-
```
|
|
924
|
-
|
|
925
|
-
---
|
|
926
|
-
|
|
927
|
-
## 🔒 Segurança
|
|
928
|
-
|
|
929
|
-
### Princípios de Segurança
|
|
930
|
-
|
|
931
|
-
1. **Zero Trust**
|
|
932
|
-
- Servidor NUNCA confia apenas na chave pública
|
|
933
|
-
- SEMPRE valida assinatura antes de processar requisição
|
|
934
|
-
|
|
935
|
-
2. **Defesa em Profundidade**
|
|
936
|
-
- Validação de formato de chave
|
|
937
|
-
- Verificação de timestamp
|
|
938
|
-
- Proteção contra replay (nonces)
|
|
939
|
-
- Assinatura criptográfica
|
|
940
|
-
|
|
941
|
-
3. **Least Privilege**
|
|
942
|
-
- Usuários normais: apenas `read` permission
|
|
943
|
-
- Admins: `admin`, `read`, `write`, `delete`
|
|
944
|
-
|
|
945
|
-
---
|
|
946
|
-
|
|
947
|
-
### Vetores de Ataque e Mitigações
|
|
948
|
-
|
|
949
|
-
#### 1. Man-in-the-Middle (MITM)
|
|
950
|
-
|
|
951
|
-
**Ataque**: Interceptar e modificar requisição
|
|
952
|
-
|
|
953
|
-
**Mitigação**:
|
|
954
|
-
- ✅ Assinatura detecta qualquer modificação
|
|
955
|
-
- ✅ HTTPS obrigatório em produção
|
|
956
|
-
- ✅ Chave privada nunca transmitida
|
|
957
|
-
|
|
958
|
-
```typescript
|
|
959
|
-
// Atacante modifica mensagem
|
|
960
|
-
Original: "GET:/api/users"
|
|
961
|
-
Modificado: "GET:/api/admin/users"
|
|
962
|
-
|
|
963
|
-
// Assinatura não corresponde → 401 Unauthorized
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
---
|
|
967
|
-
|
|
968
|
-
#### 2. Replay Attack
|
|
969
|
-
|
|
970
|
-
**Ataque**: Reutilizar requisição válida capturada
|
|
971
|
-
|
|
972
|
-
**Mitigação**:
|
|
973
|
-
- ✅ Nonces únicos por requisição
|
|
974
|
-
- ✅ Timestamp expira em 5 minutos
|
|
975
|
-
- ✅ Nonces armazenados até expiração
|
|
976
|
-
|
|
977
|
-
```typescript
|
|
978
|
-
// Atacante captura requisição válida
|
|
979
|
-
Request 1: nonce = "abc123" → ✅ 200 OK
|
|
980
|
-
|
|
981
|
-
// Tenta reutilizar
|
|
982
|
-
Request 2: nonce = "abc123" → ❌ 401 "Nonce já utilizado"
|
|
983
|
-
```
|
|
984
|
-
|
|
985
|
-
---
|
|
986
|
-
|
|
987
|
-
#### 3. Brute Force de Chave Privada
|
|
988
|
-
|
|
989
|
-
**Ataque**: Tentar adivinhar chave privada
|
|
990
|
-
|
|
991
|
-
**Mitigação**:
|
|
992
|
-
- ✅ Ed25519 com 256 bits de segurança
|
|
993
|
-
- ✅ 2^256 combinações possíveis
|
|
994
|
-
- ✅ Computacionalmente inviável
|
|
995
|
-
|
|
996
|
-
---
|
|
997
|
-
|
|
998
|
-
#### 4. Key Theft (Roubo de Chave)
|
|
999
|
-
|
|
1000
|
-
**Ataque**: Acessar localStorage e roubar chave privada
|
|
1001
|
-
|
|
1002
|
-
**Mitigação**:
|
|
1003
|
-
- ⚠️ Se atacante tem acesso ao localStorage, chave está comprometida
|
|
1004
|
-
- ✅ Usar sempre HTTPS
|
|
1005
|
-
- ✅ Implementar Content Security Policy
|
|
1006
|
-
- ✅ XSS protection (sanitize inputs)
|
|
1007
|
-
|
|
1008
|
-
**Procedimento de Resposta**:
|
|
1009
|
-
```typescript
|
|
1010
|
-
// 1. Usuário reporta suspeita de roubo
|
|
1011
|
-
// 2. Gerar novas chaves
|
|
1012
|
-
client.clearKeys()
|
|
1013
|
-
client.createNewKeys()
|
|
1014
|
-
|
|
1015
|
-
// 3. Revogar chave antiga (se houver blacklist)
|
|
1016
|
-
// 4. Notificar usuário
|
|
1017
|
-
```
|
|
1018
|
-
|
|
1019
|
-
---
|
|
1020
|
-
|
|
1021
|
-
#### 5. Time Manipulation
|
|
1022
|
-
|
|
1023
|
-
**Ataque**: Modificar relógio do sistema
|
|
1024
|
-
|
|
1025
|
-
**Mitigação**:
|
|
1026
|
-
- ✅ `maxTimeDrift` limita divergência a 5 minutos
|
|
1027
|
-
- ✅ Servidor usa seu próprio timestamp como referência
|
|
1028
|
-
|
|
1029
|
-
```typescript
|
|
1030
|
-
// Cliente com relógio 1 hora no futuro
|
|
1031
|
-
clientTime: 2025-01-01 14:00:00
|
|
1032
|
-
serverTime: 2025-01-01 13:00:00
|
|
1033
|
-
|
|
1034
|
-
drift = 3600000ms > maxTimeDrift (300000ms)
|
|
1035
|
-
→ 401 "Timestamp inválido ou expirado"
|
|
1036
|
-
```
|
|
1037
|
-
|
|
1038
|
-
---
|
|
1039
|
-
|
|
1040
|
-
### Boas Práticas de Segurança
|
|
1041
|
-
|
|
1042
|
-
1. **Sempre usar HTTPS em produção**
|
|
1043
|
-
```typescript
|
|
1044
|
-
// config/server.config.ts
|
|
1045
|
-
if (process.env.NODE_ENV === 'production') {
|
|
1046
|
-
config.enforceHTTPS = true
|
|
1047
|
-
}
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
2. **Rotacionar chaves periodicamente**
|
|
1051
|
-
```typescript
|
|
1052
|
-
// A cada 30 dias, sugerir nova chave
|
|
1053
|
-
const keyAge = Date.now() - keys.createdAt.getTime()
|
|
1054
|
-
if (keyAge > 30 * 24 * 60 * 60 * 1000) {
|
|
1055
|
-
showRotateKeyDialog()
|
|
1056
|
-
}
|
|
1057
|
-
```
|
|
1058
|
-
|
|
1059
|
-
3. **Rate limiting em rotas sensíveis**
|
|
1060
|
-
```typescript
|
|
1061
|
-
// Limitar tentativas de autenticação
|
|
1062
|
-
const rateLimit = new Map()
|
|
1063
|
-
|
|
1064
|
-
.post('/api/auth/verify', ({ headers }) => {
|
|
1065
|
-
const publicKey = headers['x-public-key']
|
|
1066
|
-
const attempts = rateLimit.get(publicKey) || 0
|
|
1067
|
-
|
|
1068
|
-
if (attempts > 10) {
|
|
1069
|
-
return { error: 'Too many attempts' }
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
rateLimit.set(publicKey, attempts + 1)
|
|
1073
|
-
})
|
|
1074
|
-
```
|
|
1075
|
-
|
|
1076
|
-
4. **Logging de eventos de segurança**
|
|
1077
|
-
```typescript
|
|
1078
|
-
// Log failed auth attempts
|
|
1079
|
-
if (!authResult.success) {
|
|
1080
|
-
logger.warn('Failed authentication', {
|
|
1081
|
-
publicKey: publicKey.substring(0, 8) + '...',
|
|
1082
|
-
error: authResult.error,
|
|
1083
|
-
ip: context.headers['x-forwarded-for'],
|
|
1084
|
-
timestamp: new Date()
|
|
1085
|
-
})
|
|
1086
|
-
}
|
|
1087
|
-
```
|
|
1088
|
-
|
|
1089
|
-
---
|
|
1090
|
-
|
|
1091
|
-
## 🧪 Testes
|
|
1092
|
-
|
|
1093
|
-
### Teste Automatizado
|
|
1094
|
-
|
|
1095
|
-
```bash
|
|
1096
|
-
# Executar script de teste
|
|
1097
|
-
bun run test-crypto-auth.ts
|
|
1098
|
-
```
|
|
1099
|
-
|
|
1100
|
-
**Saída Esperada**:
|
|
1101
|
-
```
|
|
1102
|
-
🔐 Testando Autenticação Criptográfica Ed25519
|
|
1103
|
-
|
|
1104
|
-
1️⃣ Gerando par de chaves Ed25519...
|
|
1105
|
-
✅ Chave pública: 7443b54b...
|
|
1106
|
-
✅ Chave privada: ******** (NUNCA enviar ao servidor!)
|
|
1107
|
-
|
|
1108
|
-
2️⃣ Preparando requisição assinada...
|
|
1109
|
-
✅ Mensagem construída
|
|
1110
|
-
|
|
1111
|
-
3️⃣ Assinando mensagem com chave privada...
|
|
1112
|
-
✅ Assinatura: e29d2819...
|
|
1113
|
-
|
|
1114
|
-
4️⃣ Enviando requisição ao servidor...
|
|
1115
|
-
📡 Status: 200
|
|
1116
|
-
✅ SUCESSO! Assinatura validada
|
|
1117
|
-
|
|
1118
|
-
5️⃣ Testando proteção contra replay attack...
|
|
1119
|
-
📡 Replay Status: 401
|
|
1120
|
-
✅ Proteção funcionando! Replay attack bloqueado
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
---
|
|
1124
|
-
|
|
1125
|
-
### Teste Manual no Frontend
|
|
1126
|
-
|
|
1127
|
-
1. Abrir http://localhost:5173
|
|
1128
|
-
2. Navegar para "Crypto Auth Demo"
|
|
1129
|
-
3. Clicar em "Gerar Novo Par de Chaves"
|
|
1130
|
-
4. Verificar chaves exibidas
|
|
1131
|
-
5. Clicar em "GET /api/crypto-auth/public" → 200 OK
|
|
1132
|
-
6. Clicar em "GET /api/crypto-auth/protected" → 200 OK com dados
|
|
1133
|
-
7. Clicar em "Limpar Chaves"
|
|
1134
|
-
8. Clicar em "GET /api/crypto-auth/protected" → 401 Unauthorized
|
|
1135
|
-
|
|
1136
|
-
---
|
|
1137
|
-
|
|
1138
|
-
### Teste de Casos Edge
|
|
1139
|
-
|
|
1140
|
-
```typescript
|
|
1141
|
-
// Teste 1: Timestamp muito antigo
|
|
1142
|
-
const oldTimestamp = Date.now() - (10 * 60 * 1000) // 10 min atrás
|
|
1143
|
-
// Esperado: 401 "Timestamp inválido"
|
|
1144
|
-
|
|
1145
|
-
// Teste 2: Chave pública inválida
|
|
1146
|
-
const invalidKey = "not-a-hex-string"
|
|
1147
|
-
// Esperado: 401 "Chave pública inválida"
|
|
1148
|
-
|
|
1149
|
-
// Teste 3: Assinatura incorreta
|
|
1150
|
-
const wrongSignature = "0000000000000000000000000000000000000000"
|
|
1151
|
-
// Esperado: 401 "Assinatura inválida"
|
|
1152
|
-
|
|
1153
|
-
// Teste 4: Nonce reutilizado
|
|
1154
|
-
const sameNonce = "abc123"
|
|
1155
|
-
// Request 1: 200 OK
|
|
1156
|
-
// Request 2: 401 "Nonce já utilizado"
|
|
1157
|
-
|
|
1158
|
-
// Teste 5: Usuário não-admin tentando rota admin
|
|
1159
|
-
// Esperado: 403 "Permissão negada"
|
|
1160
|
-
```
|
|
1161
|
-
|
|
1162
|
-
---
|
|
1163
|
-
|
|
1164
|
-
## 🔍 Debug e Logging
|
|
1165
|
-
|
|
1166
|
-
### Ativar Logs Detalhados
|
|
1167
|
-
|
|
1168
|
-
```typescript
|
|
1169
|
-
// config/app.config.ts
|
|
1170
|
-
plugins: {
|
|
1171
|
-
config: {
|
|
1172
|
-
'crypto-auth': {
|
|
1173
|
-
enableMetrics: true, // Ativa logs de métricas
|
|
1174
|
-
logLevel: 'debug' // Nível de log
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
```
|
|
1179
|
-
|
|
1180
|
-
### Logs Úteis
|
|
1181
|
-
|
|
1182
|
-
```typescript
|
|
1183
|
-
// Cliente
|
|
1184
|
-
console.log('Keys:', client.getKeys())
|
|
1185
|
-
console.log('Is initialized:', client.isInitialized())
|
|
1186
|
-
|
|
1187
|
-
// Middleware
|
|
1188
|
-
logger.debug('Authenticating request', {
|
|
1189
|
-
path: context.path,
|
|
1190
|
-
method: context.method,
|
|
1191
|
-
hasAuthHeaders: !!authHeaders
|
|
1192
|
-
})
|
|
1193
|
-
|
|
1194
|
-
// Service
|
|
1195
|
-
logger.info('Request validated', {
|
|
1196
|
-
publicKey: publicKey.substring(0, 8) + '...',
|
|
1197
|
-
isAdmin,
|
|
1198
|
-
permissions
|
|
1199
|
-
})
|
|
1200
|
-
```
|
|
1201
|
-
|
|
1202
|
-
---
|
|
1203
|
-
|
|
1204
|
-
## 📚 Referências Técnicas
|
|
1205
|
-
|
|
1206
|
-
### Bibliotecas Utilizadas
|
|
1207
|
-
|
|
1208
|
-
- **@noble/curves**: Implementação Ed25519
|
|
1209
|
-
- **@noble/hashes**: SHA256 e utilitários
|
|
1210
|
-
|
|
1211
|
-
### Algoritmos
|
|
1212
|
-
|
|
1213
|
-
- **Ed25519**: Curva elíptica para assinatura digital
|
|
1214
|
-
- **SHA-256**: Hash da mensagem antes de assinar
|
|
1215
|
-
|
|
1216
|
-
### Padrões de Segurança
|
|
1217
|
-
|
|
1218
|
-
- **NIST FIPS 186-5**: Digital Signature Standard
|
|
1219
|
-
- **RFC 8032**: Edwards-Curve Digital Signature Algorithm (EdDSA)
|
|
1220
|
-
|
|
1221
|
-
---
|
|
1222
|
-
|
|
1223
|
-
## 🚀 Próximos Passos / Melhorias Futuras
|
|
1224
|
-
|
|
1225
|
-
### Funcionalidades Planejadas
|
|
1226
|
-
|
|
1227
|
-
1. **Key Rotation Automática**
|
|
1228
|
-
- Sugerir rotação de chaves antigas
|
|
1229
|
-
- Transição suave entre chaves
|
|
1230
|
-
|
|
1231
|
-
2. **Blacklist de Chaves**
|
|
1232
|
-
- Revogar chaves comprometidas
|
|
1233
|
-
- Armazenamento distribuído de revogações
|
|
1234
|
-
|
|
1235
|
-
3. **Multi-Device Support**
|
|
1236
|
-
- Mesmo usuário, múltiplas chaves
|
|
1237
|
-
- Sincronização de permissões
|
|
1238
|
-
|
|
1239
|
-
4. **Audit Log**
|
|
1240
|
-
- Histórico de autenticações
|
|
1241
|
-
- Análise de padrões suspeitos
|
|
1242
|
-
|
|
1243
|
-
5. **2FA Opcional**
|
|
1244
|
-
- Adicionar segundo fator além da assinatura
|
|
1245
|
-
- TOTP ou WebAuthn
|
|
1246
|
-
|
|
1247
|
-
---
|
|
1248
|
-
|
|
1249
|
-
## ✅ Checklist de Manutenção
|
|
1250
|
-
|
|
1251
|
-
Ao modificar este plugin, verificar:
|
|
1252
|
-
|
|
1253
|
-
- [ ] Testes automatizados passam (`bun run test-crypto-auth.ts`)
|
|
1254
|
-
- [ ] Frontend funciona (http://localhost:5173 → Crypto Auth Demo)
|
|
1255
|
-
- [ ] Replay attack protection ativo
|
|
1256
|
-
- [ ] Timestamp validation funcionando
|
|
1257
|
-
- [ ] User context propagado corretamente
|
|
1258
|
-
- [ ] Rotas públicas acessíveis sem auth
|
|
1259
|
-
- [ ] Rotas protegidas exigem autenticação
|
|
1260
|
-
- [ ] Rotas admin verificam `isAdmin`
|
|
1261
|
-
- [ ] Nonces sendo limpos periodicamente
|
|
1262
|
-
- [ ] Logs de segurança sendo gerados
|
|
1263
|
-
- [ ] Documentação atualizada (este arquivo!)
|
|
1264
|
-
|
|
1265
|
-
---
|
|
1266
|
-
|
|
1267
|
-
## 📞 Suporte
|
|
1268
|
-
|
|
1269
|
-
**Logs importantes**: `plugins/crypto-auth/server/*.ts`
|
|
1270
|
-
**Testes**: `test-crypto-auth.ts`
|
|
1271
|
-
**Exemplos**: `app/client/src/pages/CryptoAuthPage.tsx`
|
|
1272
|
-
|
|
1273
|
-
**Arquivos críticos** (não modificar sem entender):
|
|
1274
|
-
- `CryptoAuthService.ts` - Validação de assinaturas
|
|
1275
|
-
- `AuthMiddleware.ts` - Decisões de autenticação
|
|
1276
|
-
- `CryptoAuthClient.ts` - Geração e assinatura
|
|
1277
|
-
|
|
1278
|
-
---
|
|
1279
|
-
|
|
1280
|
-
**Última atualização**: Janeiro 2025
|
|
1281
|
-
**Versão do Plugin**: 1.0.0
|
|
1282
|
-
**Compatível com**: FluxStack v1.4.1+
|