diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 29341606..b3b167a0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -88,7 +88,8 @@ "Bash(PORT=5555 DEBUG=false TEST_NEW_VAR=hello node test-output.js)", "Bash(bunx prettier:*)", "Read(//c/c/Users/Marcos/Documents/GitHub/aviso-projeto/FluxStack/**)", - "Read(//c/**)" + "Read(//c/**)", + "Bash(LOG_LEVEL=debug timeout 5 bun run dev)" ], "deny": [] } diff --git a/.env.example b/.env.example index 70e30de3..764d0a7c 100644 --- a/.env.example +++ b/.env.example @@ -27,4 +27,11 @@ LOG_LEVEL=info # Build Configuration BUILD_TARGET=bun -BUILD_OUTDIR=dist \ No newline at end of file +BUILD_OUTDIR=dist + +# Crypto Auth Plugin +CRYPTO_AUTH_ENABLED=true +CRYPTO_AUTH_MAX_TIME_DRIFT=300000 +# Adicione chaves públicas de admin separadas por vírgula (64 chars hex cada) +CRYPTO_AUTH_ADMIN_KEYS= +CRYPTO_AUTH_ENABLE_METRICS=true \ No newline at end of file diff --git a/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md b/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md new file mode 100644 index 00000000..f6e0ac17 --- /dev/null +++ b/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md @@ -0,0 +1,475 @@ +# 🔐 Crypto Auth - Guia de Middlewares + +> **✅ Nova Abordagem**: Middlewares declarativos - sem necessidade de configurar listas de paths! + +## 📖 **Visão Geral** + +O plugin Crypto Auth agora usa **middlewares Elysia nativos** aplicados diretamente nas rotas. Isso torna o código mais limpo, explícito e fácil de manter. + +## 🚀 **Quick Start** + +### **1. Configuração Simplificada** + +```typescript +// fluxstack.config.ts +plugins: { + enabled: ['crypto-auth'], + config: { + 'crypto-auth': { + enabled: true, + maxTimeDrift: 300000, // 5 minutos + adminKeys: [ + 'abc123def456...' // Chaves públicas dos admins + ], + enableMetrics: true + } + } +} +``` + +**✅ Removido**: `protectedRoutes` e `publicRoutes` +**✅ Novo**: Middlewares aplicados diretamente nas rotas + +--- + +## 🔌 **Middlewares Disponíveis** + +### **1. `cryptoAuthRequired()` - Requer Autenticação** + +Bloqueia a rota se não houver assinatura válida. + +```typescript +import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server' +import { Elysia } from 'elysia' + +export const protectedRoutes = new Elysia() + .use(cryptoAuthRequired()) // ⚡ Middleware aplicado + + .get('/profile', ({ request }) => { + const user = getCryptoAuthUser(request)! // Garantido que existe + return { + publicKey: user.publicKey, + isAdmin: user.isAdmin + } + }) +``` + +**Response se não autenticado (401):** +```json +{ + "error": { + "message": "Authentication required", + "code": "CRYPTO_AUTH_REQUIRED", + "statusCode": 401 + } +} +``` + +--- + +### **2. `cryptoAuthAdmin()` - Requer Admin** + +Bloqueia a rota se não for administrador. + +```typescript +import { cryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +export const adminRoutes = new Elysia() + .use(cryptoAuthAdmin()) // ⚡ Requer admin + + .delete('/users/:id', ({ params, request }) => { + const user = getCryptoAuthUser(request)! // Garantido admin + return { + deleted: params.id, + by: user.publicKey + } + }) +``` + +**Response se não for admin (403):** +```json +{ + "error": { + "message": "Admin privileges required", + "code": "ADMIN_REQUIRED", + "statusCode": 403, + "yourPermissions": ["user", "read"] + } +} +``` + +--- + +### **3. `cryptoAuthPermissions([...])` - Requer Permissões** + +Bloqueia se não tiver as permissões específicas. + +```typescript +import { cryptoAuthPermissions } from '@/plugins/crypto-auth/server' + +export const writeRoutes = new Elysia() + .use(cryptoAuthPermissions(['write', 'edit'])) // ⚡ Requer ambas + + .post('/posts', ({ body }) => { + return { created: body } + }) +``` + +**Response se sem permissão (403):** +```json +{ + "error": { + "message": "Insufficient permissions", + "code": "PERMISSION_DENIED", + "statusCode": 403, + "required": ["write", "edit"], + "yours": ["read"] + } +} +``` + +--- + +### **4. `cryptoAuthOptional()` - Autenticação Opcional** + +Não bloqueia, mas adiciona `user` se autenticado. + +```typescript +import { cryptoAuthOptional, getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +export const feedRoutes = new Elysia() + .use(cryptoAuthOptional()) // ⚡ Opcional + + .get('/posts', ({ request }) => { + const user = getCryptoAuthUser(request) // Pode ser null + + return { + posts: getPosts(), + personalizedFor: user ? user.publicKey : 'anonymous' + } + }) +``` + +--- + +## 🎯 **Padrões de Uso** + +### **Padrão 1: Grupo de Rotas Protegidas** + +```typescript +import { Elysia } from 'elysia' +import { cryptoAuthRequired } from '@/plugins/crypto-auth/server' + +export const apiRoutes = new Elysia({ prefix: '/api' }) + + // Rotas públicas + .get('/health', () => ({ status: 'ok' })) + .get('/docs', () => ({ version: '1.0.0' })) + + // Grupo protegido + .group('/users', (app) => app + .use(cryptoAuthRequired()) // ⚡ Todas as rotas do grupo requerem auth + + .get('/', ({ request }) => { + const user = getCryptoAuthUser(request)! + return { users: getUsers(user.publicKey) } + }) + + .post('/', ({ body }) => { + return { created: body } + }) + ) +``` + +--- + +### **Padrão 2: Mix de Permissões no Mesmo Grupo** + +```typescript +export const postsRoutes = new Elysia({ prefix: '/api/posts' }) + + // Leitura: apenas auth + .group('', (app) => app + .use(cryptoAuthRequired()) + + .get('/', () => ({ posts: [] })) + .get('/:id', ({ params }) => ({ post: params.id })) + ) + + // Escrita: auth + permissão write + .group('', (app) => app + .use(cryptoAuthPermissions(['write'])) + + .post('/', ({ body }) => ({ created: body })) + .put('/:id', ({ params, body }) => ({ updated: params.id })) + ) + + // Deleção: só admin + .group('', (app) => app + .use(cryptoAuthAdmin()) + + .delete('/:id', ({ params }) => ({ deleted: params.id })) + ) +``` + +--- + +### **Padrão 3: Auth Opcional com Comportamento Diferente** + +```typescript +import { cryptoAuthOptional, getCryptoAuthUser, isCryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +export const contentRoutes = new Elysia({ prefix: '/api/content' }) + .use(cryptoAuthOptional()) // ⚡ Auth opcional para todas + + .get('/articles/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request) + const isAdmin = isCryptoAuthAdmin(request) + const article = getArticle(params.id) + + return { + ...article, + premium: user ? article.premiumContent : '[Premium - Login Required]', + canEdit: isAdmin, + canComment: !!user + } + }) +``` + +--- + +## 🛠️ **Helpers Disponíveis** + +### **`getCryptoAuthUser(request)`** + +Retorna o usuário autenticado ou `null`. + +```typescript +import { getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +.get('/me', ({ request }) => { + const user = getCryptoAuthUser(request) + + if (!user) { + return { error: 'Not authenticated' } + } + + return { user } +}) +``` + +--- + +### **`isCryptoAuthAuthenticated(request)`** + +Verifica se está autenticado. + +```typescript +import { isCryptoAuthAuthenticated } from '@/plugins/crypto-auth/server' + +.get('/status', ({ request }) => { + return { + authenticated: isCryptoAuthAuthenticated(request) + } +}) +``` + +--- + +### **`isCryptoAuthAdmin(request)`** + +Verifica se é admin. + +```typescript +import { isCryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +.get('/posts/:id', ({ request, params }) => { + const post = getPost(params.id) + + return { + ...post, + canEdit: isCryptoAuthAdmin(request) + } +}) +``` + +--- + +### **`hasCryptoAuthPermission(request, permission)`** + +Verifica permissão específica. + +```typescript +import { hasCryptoAuthPermission } from '@/plugins/crypto-auth/server' + +.post('/posts/:id/publish', ({ request, params }) => { + if (!hasCryptoAuthPermission(request, 'publish')) { + return { error: 'Permission denied' } + } + + return publishPost(params.id) +}) +``` + +--- + +## 📋 **Estrutura do User** + +```typescript +interface CryptoAuthUser { + publicKey: string // Chave pública Ed25519 (hex) + isAdmin: boolean // Se está na lista adminKeys + permissions: string[] // ['read', 'write', 'admin', ...] +} +``` + +**Como o `isAdmin` é determinado:** +```typescript +// Plugin verifica se a publicKey está em adminKeys +const isAdmin = config.adminKeys.includes(user.publicKey) +``` + +--- + +## 🔄 **Migrando da Abordagem Antiga** + +### **❌ Antes (listas de paths)** + +```typescript +// fluxstack.config.ts +plugins: { + config: { + 'crypto-auth': { + protectedRoutes: ['/api/users/*', '/api/admin/*'], + publicRoutes: ['/api/health', '/api/docs'] + } + } +} + +// Rotas (sem controle explícito) +export const routes = new Elysia() + .get('/api/users', handler) // Protegido pelo plugin +``` + +### **✅ Agora (middlewares declarativos)** + +```typescript +// fluxstack.config.ts +plugins: { + config: { + 'crypto-auth': { + adminKeys: ['abc123...'] + // Sem protectedRoutes/publicRoutes! + } + } +} + +// Rotas (controle explícito) +import { cryptoAuthRequired } from '@/plugins/crypto-auth/server' + +export const routes = new Elysia() + .get('/health', handler) // ✅ Público explicitamente + + .group('/users', (app) => app + .use(cryptoAuthRequired()) // ✅ Protegido explicitamente + .get('/', handler) + ) +``` + +--- + +## 🎯 **Vantagens da Nova Abordagem** + +| Aspecto | Antes | Agora | +|---------|-------|-------| +| **Clareza** | Rotas implicitamente protegidas | ✅ Explícito no código | +| **Manutenção** | Editar config + rotas | ✅ Editar apenas rotas | +| **Type Safety** | Sem garantia de user | ✅ TypeScript sabe que user existe | +| **Flexibilidade** | Apenas on/off | ✅ Permissões, admin, opcional | +| **Debugging** | Difícil rastrear proteção | ✅ Fácil ver middleware aplicado | + +--- + +## 🧪 **Testando** + +### **Rota Pública** +```bash +curl http://localhost:3000/api/crypto-auth/public +# ✅ 200 OK - sem headers +``` + +### **Rota Protegida (sem auth)** +```bash +curl http://localhost:3000/api/crypto-auth/protected +# ❌ 401 Unauthorized +``` + +### **Rota Protegida (com auth)** +```bash +curl http://localhost:3000/api/crypto-auth/protected \ + -H "x-public-key: abc123..." \ + -H "x-timestamp: 1234567890" \ + -H "x-nonce: xyz789" \ + -H "x-signature: def456..." +# ✅ 200 OK +``` + +### **Rota Admin (sem ser admin)** +```bash +curl http://localhost:3000/api/crypto-auth/admin \ + -H "x-public-key: user123..." \ + -H "x-signature: ..." +# ❌ 403 Forbidden - "Admin privileges required" +``` + +--- + +## 📚 **Exemplos Completos** + +Veja exemplos práticos em: +- `app/server/routes/crypto-auth-demo.routes.ts` - Rotas de demonstração +- `plugins/crypto-auth/server/middlewares.ts` - Implementação dos middlewares + +--- + +## 🆘 **Troubleshooting** + +### **Erro: "CryptoAuthService not initialized"** + +**Causa**: Plugin não está carregado. + +**Solução**: +```typescript +// fluxstack.config.ts +plugins: { + enabled: ['crypto-auth'], // ✅ Adicione aqui +} +``` + +### **User sempre null** + +**Causa**: Não está usando o middleware. + +**Solução**: +```typescript +// ❌ Errado +.get('/protected', ({ request }) => { + const user = getCryptoAuthUser(request) // null +}) + +// ✅ Correto +.use(cryptoAuthRequired()) +.get('/protected', ({ request }) => { + const user = getCryptoAuthUser(request)! // Garantido +}) +``` + +### **403 ao invés de 401** + +**Causa**: Usuário autenticado mas sem permissão. + +**Solução**: Verificar se `adminKeys` ou permissões estão corretas. + +--- + +**✅ Pronto!** Agora você tem controle total e explícito sobre quais rotas são protegidas, sem precisar configurar listas de paths! diff --git a/CRYPTO-AUTH-MIDDLEWARES.md b/CRYPTO-AUTH-MIDDLEWARES.md new file mode 100644 index 00000000..c62d0d5d --- /dev/null +++ b/CRYPTO-AUTH-MIDDLEWARES.md @@ -0,0 +1,473 @@ +# 🔐 Crypto Auth - Middlewares Elysia + +## 🚀 Guia Rápido + +### Uso Básico + +```typescript +import { Elysia } from 'elysia' +import { cryptoAuthRequired, cryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +export const myRoutes = new Elysia() + // ✅ Aplica autenticação a todas as rotas + .use(cryptoAuthRequired()) + + .get('/users', ({ request }) => { + const user = (request as any).user + return { users: [], requestedBy: user.publicKey } + }) + + .post('/users', ({ request, body }) => { + const user = (request as any).user + return { created: body, by: user.publicKey } + }) +``` + +--- + +## 📚 Middlewares Disponíveis + +### 1️⃣ `cryptoAuthRequired()` - Requer Autenticação + +Valida assinatura e bloqueia acesso se não autenticado. + +```typescript +import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +export const protectedRoutes = new Elysia() + .use(cryptoAuthRequired()) // ✅ Todas as rotas protegidas + + .get('/profile', ({ request }) => { + const user = getCryptoAuthUser(request)! + return { + publicKey: user.publicKey, + isAdmin: user.isAdmin, + permissions: user.permissions + } + }) +``` + +**Retorno se não autenticado**: +```json +{ + "error": { + "message": "Authentication required", + "code": "CRYPTO_AUTH_REQUIRED", + "statusCode": 401 + } +} +``` + +--- + +### 2️⃣ `cryptoAuthAdmin()` - Requer Admin + +Valida autenticação E verifica se usuário é admin. + +```typescript +import { cryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +export const adminRoutes = new Elysia() + .use(cryptoAuthAdmin()) // ✅ Apenas admins + + .get('/admin/stats', () => ({ + totalUsers: 100, + systemHealth: 'optimal' + })) + + .delete('/admin/users/:id', ({ params, request }) => { + const user = (request as any).user + return { + deleted: params.id, + by: user.publicKey + } + }) +``` + +**Retorno se não for admin**: +```json +{ + "error": { + "message": "Admin privileges required", + "code": "ADMIN_REQUIRED", + "statusCode": 403, + "yourPermissions": ["read"] + } +} +``` + +--- + +### 3️⃣ `cryptoAuthPermissions(permissions)` - Requer Permissões + +Valida autenticação E verifica permissões específicas. + +```typescript +import { cryptoAuthPermissions } from '@/plugins/crypto-auth/server' + +export const writeRoutes = new Elysia() + .use(cryptoAuthPermissions(['write'])) // ✅ Requer permissão 'write' + + .put('/posts/:id', ({ params, body }) => ({ + updated: params.id, + data: body + })) + + .patch('/posts/:id/publish', ({ params }) => ({ + published: params.id + })) +``` + +**Múltiplas permissões**: +```typescript +.use(cryptoAuthPermissions(['write', 'publish'])) +``` + +**Retorno se sem permissão**: +```json +{ + "error": { + "message": "Insufficient permissions", + "code": "PERMISSION_DENIED", + "statusCode": 403, + "required": ["write"], + "yours": ["read"] + } +} +``` + +--- + +### 4️⃣ `cryptoAuthOptional()` - Autenticação Opcional + +Adiciona `user` se autenticado, mas NÃO bloqueia se não autenticado. + +```typescript +import { cryptoAuthOptional, getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +export const mixedRoutes = new Elysia() + .use(cryptoAuthOptional()) // ✅ Opcional + + // Comportamento diferente se autenticado + .get('/posts/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request) + + return { + post: { + id: params.id, + title: 'Post Title', + // Conteúdo completo apenas se autenticado + content: user ? 'Full content...' : 'Preview...' + }, + viewer: user ? { + publicKey: user.publicKey, + canEdit: user.isAdmin + } : null + } + }) +``` + +--- + +## 🛠️ Helper Functions + +### `getCryptoAuthUser(request)` - Obter Usuário + +```typescript +import { getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +.get('/me', ({ request }) => { + const user = getCryptoAuthUser(request) + + if (!user) { + return { error: 'Not authenticated' } + } + + return { user } +}) +``` + +--- + +### `isCryptoAuthAuthenticated(request)` - Verificar se Autenticado + +```typescript +import { isCryptoAuthAuthenticated } from '@/plugins/crypto-auth/server' + +.get('/posts/:id', ({ request, params }) => { + const isAuth = isCryptoAuthAuthenticated(request) + + return { + post: { id: params.id }, + canComment: isAuth + } +}) +``` + +--- + +### `isCryptoAuthAdmin(request)` - Verificar se é Admin + +```typescript +import { isCryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +.get('/posts/:id', ({ request, params, set }) => { + if (!isCryptoAuthAdmin(request)) { + set.status = 403 + return { error: 'Admin only' } + } + + return { post: params.id } +}) +``` + +--- + +### `hasCryptoAuthPermission(request, permission)` - Verificar Permissão + +```typescript +import { hasCryptoAuthPermission } from '@/plugins/crypto-auth/server' + +.put('/posts/:id', ({ request, params, set }) => { + if (!hasCryptoAuthPermission(request, 'write')) { + set.status = 403 + return { error: 'Write permission required' } + } + + return { updated: params.id } +}) +``` + +--- + +## 🎯 Padrões de Uso + +### Padrão 1: Grupo de Rotas Protegidas + +```typescript +export const apiRoutes = new Elysia() + // Rotas públicas + .get('/health', () => ({ status: 'ok' })) + .get('/posts', () => ({ posts: [] })) + + // Grupo protegido + .group('/users', (app) => app + .use(cryptoAuthRequired()) + .get('/', () => ({ users: [] })) + .post('/', ({ body }) => ({ created: body })) + .delete('/:id', ({ params }) => ({ deleted: params.id })) + ) + + // Grupo admin + .group('/admin', (app) => app + .use(cryptoAuthAdmin()) + .get('/stats', () => ({ stats: {} })) + ) +``` + +--- + +### Padrão 2: Middleware Cascata + +```typescript +export const routes = new Elysia() + // Aplica a todas as rotas + .use(cryptoAuthRequired()) + + .get('/profile', () => ({ profile: {} })) + + // Sub-grupo com restrição adicional + .group('/admin', (app) => app + .use(cryptoAuthAdmin()) // Admin adicional ao required + .get('/users', () => ({ users: [] })) + ) +``` + +--- + +### Padrão 3: Verificação Manual Combinada + +```typescript +export const routes = new Elysia() + .use(cryptoAuthRequired()) + + .delete('/posts/:id', ({ request, params, set }) => { + const user = getCryptoAuthUser(request)! + + // Buscar post do DB + const post = { id: params.id, authorKey: 'abc...' } + + // Apenas autor ou admin pode deletar + const canDelete = user.isAdmin || + user.publicKey === post.authorKey + + if (!canDelete) { + set.status = 403 + return { + error: 'Apenas o autor ou admin podem deletar' + } + } + + return { deleted: params.id } + }) +``` + +--- + +### Padrão 4: Rotas Condicionais + +```typescript +export const routes = new Elysia() + .use(cryptoAuthOptional()) + + .get('/posts/:id/download', ({ request, params, set }) => { + const user = getCryptoAuthUser(request) + + // Usuários autenticados: download ilimitado + // Não autenticados: limite de 3 por dia + if (!user) { + const dailyLimit = checkRateLimit(request.headers.get('x-forwarded-for')) + if (dailyLimit > 3) { + set.status = 429 + return { error: 'Rate limit exceeded. Authenticate for unlimited access.' } + } + } + + return { download: `post-${params.id}.pdf` } + }) +``` + +--- + +## 🔍 Debugging + +### Log de Autenticação + +```typescript +import { cryptoAuthRequired } from '@/plugins/crypto-auth/server' + +export const routes = new Elysia() + .use(cryptoAuthRequired({ + logger: yourLogger // ✅ Passar logger para debug + })) + + .get('/users', ({ request }) => { + const user = (request as any).user + console.log('User:', user) + return { users: [] } + }) +``` + +--- + +### Rota de Debug + +```typescript +export const debugRoutes = new Elysia() + .use(cryptoAuthOptional()) + + .get('/debug/auth', ({ request }) => { + const user = getCryptoAuthUser(request) + + return { + authenticated: !!user, + user: user || null, + headers: { + publicKey: request.headers.get('x-public-key'), + timestamp: request.headers.get('x-timestamp'), + nonce: request.headers.get('x-nonce'), + signature: request.headers.get('x-signature')?.substring(0, 16) + '...' + } + } + }) +``` + +--- + +## ⚠️ Importante + +1. **Ordem importa**: Aplique middlewares antes de definir rotas + ```typescript + .use(cryptoAuthRequired()) // ✅ Primeiro + .get('/users', ...) // ✅ Depois + ``` + +2. **Grupos herdam middlewares**: + ```typescript + .use(cryptoAuthRequired()) + .group('/api', ...) // ✅ Herda cryptoAuthRequired + ``` + +3. **User object sempre disponível**: Em rotas com `cryptoAuthRequired`, `cryptoAuthAdmin` ou `cryptoAuthPermissions` + +4. **Null check necessário**: Em rotas com `cryptoAuthOptional`: + ```typescript + const user = getCryptoAuthUser(request) + if (user) { // ✅ Verificar antes de usar + ... + } + ``` + +--- + +## 📦 TypeScript Types + +```typescript +interface CryptoAuthUser { + publicKey: string // Chave pública (ID único) + isAdmin: boolean // Se é administrador + permissions: string[] // ["read"] ou ["admin", "read", "write", "delete"] +} +``` + +--- + +## 🆚 Comparação com Config + +### ❌ Antes (Config Global) +```typescript +// config/app.config.ts +plugins: { + config: { + 'crypto-auth': { + protectedRoutes: ["/api/users/*"] + } + } +} + +// routes/users.routes.ts +.get('/users', ({ request }) => { + // Protegido automaticamente +}) +``` + +### ✅ Agora (Middlewares) +```typescript +// routes/users.routes.ts +import { cryptoAuthRequired } from '@/plugins/crypto-auth/server' + +export const routes = new Elysia() + .use(cryptoAuthRequired()) // ✅ Explícito + .get('/users', ({ request }) => { + // Protegido + }) +``` + +**Vantagens**: +- ✅ Explícito e visível +- ✅ Type-safe +- ✅ Mais flexível +- ✅ Melhor autocomplete +- ✅ Não depende de config global + +--- + +## 📚 Ver Mais + +- **Exemplo completo**: `app/server/routes/example-with-crypto-auth.routes.ts` +- **Documentação AI**: `plugins/crypto-auth/ai-context.md` +- **Demo rotas**: `app/server/routes/crypto-auth-demo.routes.ts` + +--- + +**Última atualização**: Janeiro 2025 diff --git a/CRYPTO-AUTH-USAGE.md b/CRYPTO-AUTH-USAGE.md new file mode 100644 index 00000000..994e6177 --- /dev/null +++ b/CRYPTO-AUTH-USAGE.md @@ -0,0 +1,491 @@ +# 🔐 Como Usar Crypto Auth no Servidor + +## 📋 Índice +1. [Middlewares Disponíveis](#middlewares-disponíveis) +2. [Uso Básico](#uso-básico) +3. [Acessar Dados do Usuário](#acessar-dados-do-usuário) +4. [Verificar Permissões](#verificar-permissões) +5. [Exemplos Completos](#exemplos-completos) +6. [Helper Functions](#helper-functions) + +--- + +## 🚀 Middlewares Disponíveis + +### 1️⃣ `cryptoAuthRequired()` - Requer Autenticação + +Valida assinatura e **bloqueia** acesso se não autenticado. + +```typescript +import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +export const protectedRoutes = new Elysia() + .use(cryptoAuthRequired()) // ✅ Todas as rotas protegidas + + .get('/profile', ({ request }) => { + const user = getCryptoAuthUser(request)! + return { + publicKey: user.publicKey, + isAdmin: user.isAdmin, + permissions: user.permissions + } + }) +``` + +**Retorno se não autenticado**: `401 Unauthorized` + +--- + +### 2️⃣ `cryptoAuthAdmin()` - Requer Admin + +Valida autenticação E verifica se usuário é admin. + +```typescript +import { cryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +export const adminRoutes = new Elysia() + .use(cryptoAuthAdmin()) // ✅ Apenas admins + + .get('/admin/stats', () => ({ + totalUsers: 100, + systemHealth: 'optimal' + })) + + .delete('/admin/users/:id', ({ params }) => ({ + deleted: params.id + })) +``` + +**Retorno se não for admin**: `403 Forbidden` + +--- + +### 3️⃣ `cryptoAuthPermissions(permissions)` - Requer Permissões + +Valida autenticação E verifica permissões específicas. + +```typescript +import { cryptoAuthPermissions } from '@/plugins/crypto-auth/server' + +export const writeRoutes = new Elysia() + .use(cryptoAuthPermissions(['write'])) // ✅ Requer permissão 'write' + + .put('/posts/:id', ({ params, body }) => ({ + updated: params.id, + data: body + })) +``` + +**Múltiplas permissões**: +```typescript +.use(cryptoAuthPermissions(['write', 'publish'])) +``` + +--- + +### 4️⃣ `cryptoAuthOptional()` - Autenticação Opcional + +Adiciona `user` se autenticado, mas **NÃO bloqueia** se não autenticado. + +```typescript +import { cryptoAuthOptional, getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +export const mixedRoutes = new Elysia() + .use(cryptoAuthOptional()) // ✅ Opcional + + .get('/posts/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request) + + return { + post: { + id: params.id, + title: 'Post Title', + // Conteúdo completo apenas se autenticado + content: user ? 'Full content...' : 'Preview...' + }, + viewer: user ? { + publicKey: user.publicKey, + canEdit: user.isAdmin + } : null + } + }) +``` + +--- + +## 🛠️ Uso Básico + +### Aplicar Middleware a Todas as Rotas + +```typescript +import { Elysia } from 'elysia' +import { cryptoAuthRequired } from '@/plugins/crypto-auth/server' + +export const myRoutes = new Elysia() + // ✅ Aplica autenticação a todas as rotas + .use(cryptoAuthRequired()) + + .get('/users', ({ request }) => { + const user = (request as any).user + return { users: [], requestedBy: user.publicKey } + }) + + .post('/users', ({ request, body }) => { + const user = (request as any).user + return { created: body, by: user.publicKey } + }) +``` + +--- + +### Aplicar a Grupos Específicos + +```typescript +export const apiRoutes = new Elysia() + // Rotas públicas + .get('/health', () => ({ status: 'ok' })) + .get('/posts', () => ({ posts: [] })) + + // Grupo protegido + .group('/users', (app) => app + .use(cryptoAuthRequired()) + .get('/', () => ({ users: [] })) + .post('/', ({ body }) => ({ created: body })) + ) + + // Grupo admin + .group('/admin', (app) => app + .use(cryptoAuthAdmin()) + .get('/stats', () => ({ stats: {} })) + ) +``` + +--- + +## 📦 Acessar Dados do Usuário + +### Interface `CryptoAuthUser` + +```typescript +interface CryptoAuthUser { + publicKey: string // Chave pública (ID único) + isAdmin: boolean // Se é admin + permissions: string[] // [\"read\"] ou [\"admin\", \"read\", \"write\", \"delete\"] +} +``` + +### Acessar no Handler + +```typescript +import { getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +.get('/me', ({ request }) => { + const user = getCryptoAuthUser(request)! // ! porque já passou pelo middleware + + return { + id: user.publicKey, + isAdmin: user.isAdmin, + permissions: user.permissions + } +}) +``` + +--- + +## 🔒 Verificar Permissões + +### Verificar se é Admin + +```typescript +import { isCryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +.delete('/posts/:id', ({ request, params, set }) => { + if (!isCryptoAuthAdmin(request)) { + set.status = 403 + return { error: 'Admin only' } + } + + return { deleted: params.id } +}) +``` + +--- + +### Verificar Permissão Específica + +```typescript +import { hasCryptoAuthPermission } from '@/plugins/crypto-auth/server' + +.put('/posts/:id', ({ request, params, set }) => { + if (!hasCryptoAuthPermission(request, 'write')) { + set.status = 403 + return { error: 'Write permission required' } + } + + return { updated: params.id } +}) +``` + +--- + +## 🎯 Exemplos Completos + +### Exemplo 1: CRUD de Posts + +```typescript +import { Elysia, t } from 'elysia' +import { + cryptoAuthRequired, + cryptoAuthAdmin, + cryptoAuthOptional, + getCryptoAuthUser +} from '@/plugins/crypto-auth/server' + +export const postsRoutes = new Elysia() + + // ✅ PÚBLICO - Listar posts + .get('/posts', () => ({ + posts: [ + { id: 1, title: 'Post 1', author: 'João' }, + { id: 2, title: 'Post 2', author: 'Maria' } + ] + })) + + // 🔒 PROTEGIDO - Criar post + .post('/posts', ({ request, body }) => { + const user = getCryptoAuthUser(request)! + const { title, content } = body as { title: string; content: string } + + return { + success: true, + post: { + title, + content, + author: user.publicKey, + createdAt: new Date() + } + } + }, { + body: t.Object({ + title: t.String(), + content: t.String() + }) + }) + .use(cryptoAuthRequired()) // Aplica apenas ao .post acima + + // 🔒 ADMIN - Deletar post + .delete('/posts/:id', ({ params }) => ({ + success: true, + message: `Post ${params.id} deletado` + })) + .use(cryptoAuthAdmin()) // Aplica apenas ao .delete acima +``` + +--- + +### Exemplo 2: Rotas Condicionais + +```typescript +export const mixedRoutes = new Elysia() + .use(cryptoAuthOptional()) + + .get('/posts/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request) + + // Post básico (público) + const post = { + id: params.id, + title: 'Título do Post', + excerpt: 'Prévia do conteúdo...' + } + + // Se autenticado, retorna conteúdo completo + if (user) { + return { + ...post, + fullContent: 'Conteúdo completo do post...', + comments: [/* comentários */], + viewer: { + publicKey: user.publicKey, + canEdit: user.isAdmin + } + } + } + + // Se não autenticado, apenas prévia + return post + }) +``` + +--- + +### Exemplo 3: Middleware Cascata + +```typescript +export const routes = new Elysia() + // Aplica a todas as rotas + .use(cryptoAuthRequired()) + + .get('/profile', () => ({ profile: {} })) + + // Sub-grupo com restrição adicional + .group('/admin', (app) => app + .use(cryptoAuthAdmin()) // Admin adicional ao required + .get('/users', () => ({ users: [] })) + ) +``` + +--- + +## 🛠️ Helper Functions + +### `getCryptoAuthUser(request)` - Obter Usuário + +```typescript +import { getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +.get('/me', ({ request }) => { + const user = getCryptoAuthUser(request) + + if (!user) { + return { error: 'Not authenticated' } + } + + return { user } +}) +``` + +--- + +### `isCryptoAuthAuthenticated(request)` - Verificar se Autenticado + +```typescript +import { isCryptoAuthAuthenticated } from '@/plugins/crypto-auth/server' + +.get('/posts/:id', ({ request, params }) => { + const isAuth = isCryptoAuthAuthenticated(request) + + return { + post: { id: params.id }, + canComment: isAuth + } +}) +``` + +--- + +### `isCryptoAuthAdmin(request)` - Verificar se é Admin + +```typescript +import { isCryptoAuthAdmin } from '@/plugins/crypto-auth/server' + +.get('/posts/:id', ({ request, params, set }) => { + if (!isCryptoAuthAdmin(request)) { + set.status = 403 + return { error: 'Admin only' } + } + + return { post: params.id } +}) +``` + +--- + +### `hasCryptoAuthPermission(request, permission)` - Verificar Permissão + +```typescript +import { hasCryptoAuthPermission } from '@/plugins/crypto-auth/server' + +.put('/posts/:id', ({ request, params, set }) => { + if (!hasCryptoAuthPermission(request, 'write')) { + set.status = 403 + return { error: 'Write permission required' } + } + + return { updated: params.id } +}) +``` + +--- + +## 🔍 Debugging + +### Log de Autenticação + +```typescript +import { cryptoAuthRequired } from '@/plugins/crypto-auth/server' + +export const routes = new Elysia() + .use(cryptoAuthRequired({ + logger: yourLogger // ✅ Passar logger para debug + })) + + .get('/users', ({ request }) => { + const user = (request as any).user + console.log('User:', user) + return { users: [] } + }) +``` + +--- + +### Rota de Debug + +```typescript +export const debugRoutes = new Elysia() + .use(cryptoAuthOptional()) + + .get('/debug/auth', ({ request }) => { + const user = getCryptoAuthUser(request) + + return { + authenticated: !!user, + user: user || null, + headers: { + publicKey: request.headers.get('x-public-key'), + timestamp: request.headers.get('x-timestamp'), + nonce: request.headers.get('x-nonce'), + signature: request.headers.get('x-signature')?.substring(0, 16) + '...' + } + } + }) +``` + +--- + +## ⚠️ Importante + +1. **Ordem importa**: Aplique middlewares antes de definir rotas + ```typescript + .use(cryptoAuthRequired()) // ✅ Primeiro + .get('/users', ...) // ✅ Depois + ``` + +2. **Grupos herdam middlewares**: + ```typescript + .use(cryptoAuthRequired()) + .group('/api', ...) // ✅ Herda cryptoAuthRequired + ``` + +3. **User object sempre disponível**: Em rotas com `cryptoAuthRequired`, `cryptoAuthAdmin` ou `cryptoAuthPermissions` + +4. **Null check necessário**: Em rotas com `cryptoAuthOptional`: + ```typescript + const user = getCryptoAuthUser(request) + if (user) { // ✅ Verificar antes de usar + ... + } + ``` + +--- + +## 📚 Ver Mais + +- **Documentação completa de middlewares**: `CRYPTO-AUTH-MIDDLEWARES.md` +- **Exemplo completo**: `app/server/routes/example-with-crypto-auth.routes.ts` +- **Documentação AI**: `plugins/crypto-auth/ai-context.md` +- **Demo rotas**: `app/server/routes/crypto-auth-demo.routes.ts` + +--- + +**Última atualização**: Janeiro 2025 diff --git a/EXEMPLO-ROTA-PROTEGIDA.md b/EXEMPLO-ROTA-PROTEGIDA.md new file mode 100644 index 00000000..46434f6d --- /dev/null +++ b/EXEMPLO-ROTA-PROTEGIDA.md @@ -0,0 +1,347 @@ +# 🔐 Como Criar Rotas com Crypto-Auth + +Guia prático para desenvolvedores criarem rotas usando o sistema de autenticação. + +## 📋 Passo a Passo + +### 1️⃣ Criar Arquivo de Rotas + +Crie um arquivo em `app/server/routes/`: + +```typescript +// app/server/routes/posts.routes.ts +import { Elysia, t } from 'elysia' +import { + cryptoAuthRequired, + cryptoAuthAdmin, + cryptoAuthOptional, + getCryptoAuthUser +} from '@/plugins/crypto-auth/server' + +export const postsRoutes = new Elysia({ prefix: '/posts' }) + + // ======================================== + // 🌐 ROTA PÚBLICA - Qualquer um pode acessar + // ======================================== + .get('/', () => { + return { + success: true, + posts: [ + { id: 1, title: 'Post público' }, + { id: 2, title: 'Outro post' } + ] + } + }) + + // ======================================== + // 🔒 ROTA PROTEGIDA - Requer autenticação + // ======================================== + .guard({}, (app) => + app.use(cryptoAuthRequired()) + + // GET /api/posts/my-posts - Lista posts do usuário autenticado + .get('/my-posts', ({ request }) => { + const user = getCryptoAuthUser(request)! + + return { + success: true, + message: `Posts de ${user.publicKey.substring(0, 8)}...`, + posts: [ + { id: 1, title: 'Meu post privado', author: user.publicKey } + ] + } + }) + + // POST /api/posts - Criar novo post (autenticação obrigatória) + .post('/', ({ request, body }) => { + const user = getCryptoAuthUser(request)! + const { title, content } = body as { title: string; content: string } + + return { + success: true, + message: 'Post criado com sucesso', + post: { + id: Date.now(), + title, + content, + author: user.publicKey, + createdAt: new Date().toISOString() + } + } + }, { + body: t.Object({ + title: t.String(), + content: t.String() + }) + }) + ) + + // ======================================== + // 👑 ROTA ADMIN - Apenas administradores + // ======================================== + .guard({}, (app) => + app.use(cryptoAuthAdmin()) + + // DELETE /api/posts/:id - Deletar qualquer post (só admin) + .delete('/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request)! + + return { + success: true, + message: `Post ${params.id} deletado por admin`, + deletedBy: user.publicKey.substring(0, 8) + '...' + } + }) + + // GET /api/posts/moderation - Painel de moderação + .get('/moderation', ({ request }) => { + const user = getCryptoAuthUser(request)! + + return { + success: true, + message: 'Painel de moderação', + admin: user.publicKey.substring(0, 8) + '...', + pendingPosts: [], + reportedPosts: [] + } + }) + ) + + // ======================================== + // 🌓 ROTA COM AUTH OPCIONAL - Funciona com/sem auth + // ======================================== + .guard({}, (app) => + app.use(cryptoAuthOptional()) + + // GET /api/posts/:id - Detalhes do post (conteúdo extra se autenticado) + .get('/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request) + const isAuthenticated = !!user + + return { + success: true, + post: { + id: params.id, + title: 'Post de exemplo', + content: 'Conteúdo público', + // ✅ Conteúdo extra apenas para autenticados + extraContent: isAuthenticated + ? 'Conteúdo premium para usuários autenticados' + : null, + canEdit: isAuthenticated, + viewer: user ? user.publicKey.substring(0, 8) + '...' : 'anonymous' + } + } + }) + ) +``` + +### 2️⃣ Registrar no Router Principal + +```typescript +// app/server/routes/index.ts +import { Elysia } from 'elysia' +import { postsRoutes } from './posts.routes' // ✅ Importar suas rotas + +export const apiRoutes = new Elysia({ prefix: '/api' }) + .use(userRoutes) + .use(postsRoutes) // ✅ Adicionar aqui + // ... outras rotas +``` + +### 3️⃣ Como o Cliente Faz Login + +O cliente precisa enviar headers de autenticação criptográfica: + +```typescript +// Frontend/Cliente +import { generateKeyPair, sign } from './crypto-utils' + +// 1. Gerar par de chaves (feito uma vez) +const { publicKey, privateKey } = generateKeyPair() + +// 2. Fazer requisição autenticada +const timestamp = Date.now() +const nonce = crypto.randomUUID() +const message = `GET:/api/posts/my-posts:${timestamp}:${nonce}` +const signature = sign(message, privateKey) + +fetch('http://localhost:3000/api/posts/my-posts', { + headers: { + 'X-Public-Key': publicKey, + 'X-Timestamp': timestamp.toString(), + 'X-Nonce': nonce, + 'X-Signature': signature + } +}) +``` + +## 🧪 Testando as Rotas + +### Rota Pública (sem auth) +```bash +curl http://localhost:3000/api/posts +# ✅ 200 OK +``` + +### Rota Protegida (sem auth) +```bash +curl http://localhost:3000/api/posts/my-posts +# ❌ 401 {"error": {"message": "Authentication required"}} +``` + +### Rota Protegida (com auth) +```bash +curl http://localhost:3000/api/posts/my-posts \ + -H "X-Public-Key: abc123..." \ + -H "X-Timestamp: 1234567890" \ + -H "X-Nonce: uuid-here" \ + -H "X-Signature: signature-here" +# ✅ 200 OK {"posts": [...]} +``` + +### Rota Admin (sem admin) +```bash +curl http://localhost:3000/api/posts/moderation \ + -H "X-Public-Key: user-key..." \ + # ... outros headers +# ❌ 403 {"error": {"message": "Admin privileges required"}} +``` + +### Rota Opcional (ambos funcionam) +```bash +# Sem auth +curl http://localhost:3000/api/posts/123 +# ✅ 200 OK {"post": {"extraContent": null}} + +# Com auth +curl http://localhost:3000/api/posts/123 -H "X-Public-Key: ..." +# ✅ 200 OK {"post": {"extraContent": "Conteúdo premium..."}} +``` + +## 📦 Middlewares Disponíveis + +### `cryptoAuthRequired()` +Bloqueia acesso se não autenticado. +```typescript +.use(cryptoAuthRequired()) +.get('/protected', ({ request }) => { + const user = getCryptoAuthUser(request)! // ✅ Sempre existe +}) +``` + +### `cryptoAuthAdmin()` +Bloqueia se não for admin. +```typescript +.use(cryptoAuthAdmin()) +.delete('/users/:id', ({ request }) => { + const user = getCryptoAuthUser(request)! // ✅ Sempre admin +}) +``` + +### `cryptoAuthOptional()` +Adiciona user se autenticado, mas não bloqueia. +```typescript +.use(cryptoAuthOptional()) +.get('/feed', ({ request }) => { + const user = getCryptoAuthUser(request) // ⚠️ Pode ser null + if (user) { + return { message: 'Feed personalizado' } + } + return { message: 'Feed público' } +}) +``` + +### `cryptoAuthPermissions(['write', 'delete'])` +Bloqueia se não tiver permissões específicas. +```typescript +.use(cryptoAuthPermissions(['write', 'delete'])) +.put('/posts/:id', ({ request }) => { + const user = getCryptoAuthUser(request)! // ✅ Tem as permissões +}) +``` + +## 🔑 Helpers Úteis + +```typescript +import { + getCryptoAuthUser, + isCryptoAuthAuthenticated, + isCryptoAuthAdmin, + hasCryptoAuthPermission +} from '@/plugins/crypto-auth/server' + +// Dentro de uma rota +({ request }) => { + // Pegar usuário autenticado (null se não autenticado) + const user = getCryptoAuthUser(request) + + // Verificar se está autenticado + if (!isCryptoAuthAuthenticated(request)) { + return { error: 'Login required' } + } + + // Verificar se é admin + if (isCryptoAuthAdmin(request)) { + return { message: 'Admin panel' } + } + + // Verificar permissão específica + if (hasCryptoAuthPermission(request, 'delete')) { + return { message: 'Can delete' } + } +} +``` + +## ⚠️ Boas Práticas + +### ✅ Fazer +- Usar `.guard({})` para isolar middlewares +- Verificar `null` em rotas com `cryptoAuthOptional()` +- Usar `!` apenas após middlewares obrigatórios +- Separar rotas por nível de permissão + +### ❌ Não Fazer +- Usar `.use()` fora de `.guard()` (afeta TODAS as rotas seguintes) +- Esquecer `.as('plugin')` ao criar middlewares customizados +- Assumir que `user` existe sem middleware de proteção + +## 🎯 Padrão Recomendado + +```typescript +export const myRoutes = new Elysia({ prefix: '/my-feature' }) + + // Públicas primeiro + .get('/public', () => ({ ... })) + + // Auth opcional (separado) + .guard({}, (app) => + app.use(cryptoAuthOptional()) + .get('/optional', ({ request }) => { + const user = getCryptoAuthUser(request) + // ... + }) + ) + + // Protegidas (separado) + .guard({}, (app) => + app.use(cryptoAuthRequired()) + .get('/protected', ({ request }) => { + const user = getCryptoAuthUser(request)! + // ... + }) + ) + + // Admin (separado) + .guard({}, (app) => + app.use(cryptoAuthAdmin()) + .delete('/admin-only', ({ request }) => { + const user = getCryptoAuthUser(request)! + // ... + }) + ) +``` + +--- + +**Pronto!** Agora você sabe criar rotas com autenticação crypto-auth no FluxStack. 🚀 diff --git a/QUICK-START-CRYPTO-AUTH.md b/QUICK-START-CRYPTO-AUTH.md new file mode 100644 index 00000000..2f012811 --- /dev/null +++ b/QUICK-START-CRYPTO-AUTH.md @@ -0,0 +1,221 @@ +# ⚡ Quick Start: Crypto-Auth em 5 Minutos + +## 🎯 Como Criar uma Rota Protegida + +### 🚀 Opção 1: CLI (Recomendado) + +Use o comando `crypto-auth:make:route` para gerar rotas automaticamente: + +```bash +# Rota com autenticação obrigatória (padrão) +bun flux crypto-auth:make:route users + +# Rota apenas para admins +bun flux crypto-auth:make:route admin-panel --auth admin + +# Rota com autenticação opcional +bun flux crypto-auth:make:route blog --auth optional + +# Rota pública (sem auth) +bun flux crypto-auth:make:route public-api --auth public +``` + +O comando cria automaticamente: +- ✅ Arquivo de rotas em `app/server/routes/[nome].routes.ts` +- ✅ Middlewares de autenticação configurados +- ✅ Templates de CRUD completos +- ✅ Exemplos de uso de `getCryptoAuthUser()` + +**Tipos de `--auth` disponíveis:** +- `required` - Autenticação obrigatória (padrão) +- `admin` - Apenas administradores +- `optional` - Auth opcional (rota pública com conteúdo extra para autenticados) +- `public` - Completamente pública (sem middleware) + +--- + +### ⚙️ Opção 2: Manual + +#### 1️⃣ Criar Arquivo de Rotas + +```typescript +// app/server/routes/minhas-rotas.routes.ts +import { Elysia } from 'elysia' +import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server' + +export const minhasRotas = new Elysia({ prefix: '/minhas-rotas' }) + + // Rota pública + .get('/publica', () => ({ message: 'Todos podem ver' })) + + // Rota protegida + .guard({}, (app) => + app.use(cryptoAuthRequired()) + .get('/protegida', ({ request }) => { + const user = getCryptoAuthUser(request)! + return { + message: 'Área restrita', + user: user.publicKey.substring(0, 8) + '...' + } + }) + ) +``` + +#### 2️⃣ Registrar no Router + +```typescript +// app/server/routes/index.ts +import { minhasRotas } from './minhas-rotas.routes' + +export const apiRoutes = new Elysia({ prefix: '/api' }) + .use(minhasRotas) // ✅ Adicionar aqui +``` + +#### 3️⃣ Testar + +```bash +# Pública (funciona) +curl http://localhost:3000/api/minhas-rotas/publica +# ✅ {"message": "Todos podem ver"} + +# Protegida (sem auth) +curl http://localhost:3000/api/minhas-rotas/protegida +# ❌ {"error": {"message": "Authentication required", "code": "CRYPTO_AUTH_REQUIRED", "statusCode": 401}} +``` + +## 🔐 Tipos de Middleware + +### `cryptoAuthRequired()` - Autenticação Obrigatória +```typescript +.guard({}, (app) => + app.use(cryptoAuthRequired()) + .get('/protegida', ({ request }) => { + const user = getCryptoAuthUser(request)! // ✅ Sempre existe + return { user } + }) +) +``` + +### `cryptoAuthAdmin()` - Apenas Administradores +```typescript +.guard({}, (app) => + app.use(cryptoAuthAdmin()) + .delete('/deletar/:id', ({ request, params }) => { + const user = getCryptoAuthUser(request)! // ✅ Sempre admin + return { message: `${params.id} deletado` } + }) +) +``` + +### `cryptoAuthOptional()` - Autenticação Opcional +```typescript +.guard({}, (app) => + app.use(cryptoAuthOptional()) + .get('/feed', ({ request }) => { + const user = getCryptoAuthUser(request) // ⚠️ Pode ser null + + if (user) { + return { message: 'Feed personalizado', user } + } + return { message: 'Feed público' } + }) +) +``` + +### `cryptoAuthPermissions([...])` - Permissões Específicas +```typescript +.guard({}, (app) => + app.use(cryptoAuthPermissions(['write', 'delete'])) + .put('/editar/:id', ({ request }) => { + const user = getCryptoAuthUser(request)! // ✅ Tem as permissões + return { message: 'Editado' } + }) +) +``` + +## 📊 Exemplo Real Funcionando + +Veja o arquivo criado: **`app/server/routes/exemplo-posts.routes.ts`** + +Rotas disponíveis: +- ✅ `GET /api/exemplo-posts` - Pública +- ✅ `GET /api/exemplo-posts/:id` - Auth opcional +- ✅ `GET /api/exemplo-posts/meus-posts` - Protegida +- ✅ `POST /api/exemplo-posts/criar` - Protegida +- ✅ `GET /api/exemplo-posts/admin/todos` - Admin +- ✅ `DELETE /api/exemplo-posts/admin/:id` - Admin + +## 🧪 Testando Agora + +```bash +# Pública +curl http://localhost:3000/api/exemplo-posts +# ✅ {"success":true,"posts":[...]} + +# Auth opcional (sem auth) +curl http://localhost:3000/api/exemplo-posts/1 +# ✅ {"success":true,"post":{"premiumContent":null,"viewer":"Visitante anônimo"}} + +# Protegida (sem auth) +curl http://localhost:3000/api/exemplo-posts/meus-posts +# ❌ {"error":{"message":"Authentication required"}} + +# Admin (sem auth) +curl http://localhost:3000/api/exemplo-posts/admin/todos +# ❌ {"error":{"message":"Authentication required"}} +``` + +## 🔑 Helpers Úteis + +```typescript +import { + getCryptoAuthUser, + isCryptoAuthAuthenticated, + isCryptoAuthAdmin, + hasCryptoAuthPermission +} from '@/plugins/crypto-auth/server' + +({ request }) => { + const user = getCryptoAuthUser(request) // User | null + const isAuth = isCryptoAuthAuthenticated(request) // boolean + const isAdmin = isCryptoAuthAdmin(request) // boolean + const canDelete = hasCryptoAuthPermission(request, 'delete') // boolean +} +``` + +## ⚠️ Importante + +### ✅ Fazer +```typescript +// Isolar middlewares com .guard({}) +.guard({}, (app) => + app.use(cryptoAuthRequired()) + .get('/protected', () => {}) +) + +// Verificar null em auth opcional +.guard({}, (app) => + app.use(cryptoAuthOptional()) + .get('/feed', ({ request }) => { + const user = getCryptoAuthUser(request) + if (user) { /* autenticado */ } + }) +) +``` + +### ❌ Não Fazer +```typescript +// ❌ Usar .use() sem .guard() (afeta TODAS as rotas seguintes) +export const myRoutes = new Elysia() + .use(cryptoAuthRequired()) // ❌ ERRADO + .get('/publica', () => {}) // Esta rota ficará protegida! +``` + +## 📚 Documentação Completa + +- **Guia Detalhado**: `EXEMPLO-ROTA-PROTEGIDA.md` +- **Referência de Middlewares**: `CRYPTO-AUTH-MIDDLEWARE-GUIDE.md` + +--- + +**Pronto!** Agora você pode criar rotas protegidas em minutos. 🚀 diff --git a/app/client/src/App.tsx b/app/client/src/App.tsx index 0b5b72a1..fd843fc3 100644 --- a/app/client/src/App.tsx +++ b/app/client/src/App.tsx @@ -15,6 +15,7 @@ import { OverviewPage } from './pages/Overview' import { DemoPage } from './pages/Demo' import { HybridLivePage } from './pages/HybridLive' import { ApiDocsPage } from './pages/ApiDocs' +import { CryptoAuthPage } from './pages/CryptoAuthPage' import { MainLayout } from './components/MainLayout' import { LiveClock } from './components/LiveClock' @@ -166,6 +167,7 @@ function AppContent() { {[ { id: 'overview', label: 'Visão Geral', icon: , path: '/' }, { id: 'demo', label: 'Demo', icon: , path: '/demo' }, + { id: 'crypto-auth', label: 'Crypto Auth', icon: , path: '/crypto-auth' }, { id: 'hybrid-live', label: 'Hybrid Live', icon: , path: '/hybrid-live' }, { id: 'live-app', label: 'Live App', icon: , path: '/live-app' }, { id: 'api-docs', label: 'API Docs', icon: , path: '/api-docs' }, @@ -208,8 +210,8 @@ function AppContent() { {[ { id: 'overview', label: 'Visão', icon: , path: '/' }, { id: 'demo', label: 'Demo', icon: , path: '/demo' }, + { id: 'crypto-auth', label: 'Crypto', icon: , path: '/crypto-auth' }, { id: 'hybrid-live', label: 'Hybrid', icon: , path: '/hybrid-live' }, - { id: 'live-app', label: 'Live', icon: , path: '/live-app' }, { id: 'api-docs', label: 'Docs', icon: , path: '/api-docs' }, { id: 'tests', label: 'Testes', icon: , path: '/tests' } ].map(tab => ( @@ -256,6 +258,7 @@ function AppContent() { /> } /> + } /> } /> } /> new CryptoAuthClient({ + storage: 'sessionStorage', // Usar sessionStorage ao invés de localStorage + autoInit: true // Gerar automaticamente ao inicializar + })) + const [keys, setKeys] = useState(null) + const [loading, setLoading] = useState(false) + const [publicDataResult, setPublicDataResult] = useState(null) + const [protectedDataResult, setProtectedDataResult] = useState(null) + const [copiedKey, setCopiedKey] = useState('') + const [showImportModal, setShowImportModal] = useState(false) + const [importKey, setImportKey] = useState('') + const [importError, setImportError] = useState('') + + useEffect(() => { + const existingKeys = authClient.getKeys() + if (existingKeys) { + setKeys(existingKeys) + } + }, [authClient]) + + const handleCreateKeys = () => { + setLoading(true) + try { + const newKeys = authClient.createNewKeys() + setKeys(newKeys) + } catch (error) { + console.error('Erro ao criar chaves:', error) + alert('Erro ao criar chaves: ' + (error as Error).message) + } finally { + setLoading(false) + } + } + + const handleClearKeys = () => { + setLoading(true) + try { + authClient.clearKeys() + setKeys(null) + setPublicDataResult(null) + setProtectedDataResult(null) + } catch (error) { + console.error('Erro ao limpar chaves:', error) + } finally { + setLoading(false) + } + } + + const handleImportKey = () => { + setImportError('') + setLoading(true) + try { + const trimmedKey = importKey.trim() + const importedKeys = authClient.importPrivateKey(trimmedKey) + setKeys(importedKeys) + setShowImportModal(false) + setImportKey('') + } catch (error) { + console.error('Erro ao importar chave:', error) + setImportError((error as Error).message) + } finally { + setLoading(false) + } + } + + const openImportModal = () => { + setShowImportModal(true) + setImportKey('') + setImportError('') + } + + const handlePublicRequest = async () => { + setLoading(true) + try { + const response = await fetch('/api/crypto-auth/public') + const data = await response.json() + setPublicDataResult(data) + } catch (error) { + console.error('Erro na requisição pública:', error) + setPublicDataResult({ error: (error as Error).message }) + } finally { + setLoading(false) + } + } + + const handleProtectedRequest = async () => { + setLoading(true) + try { + const response = await authClient.fetch('/api/crypto-auth/protected') + const data = await response.json() + setProtectedDataResult(data) + } catch (error) { + console.error('Erro na requisição protegida:', error) + setProtectedDataResult({ error: (error as Error).message }) + } finally { + setLoading(false) + } + } + + const copyToClipboard = (text: string, type: string) => { + navigator.clipboard.writeText(text) + setCopiedKey(type) + setTimeout(() => setCopiedKey(''), 2000) + } + + return ( +
+ {/* Header */} +
+
+ +

🔐 Crypto Auth Demo

+
+

+ Autenticação criptográfica usando Ed25519 - SEM sessões no servidor +

+
+ + {/* Keys Status */} +
+

+ + Suas Chaves Criptográficas +

+ + {!keys ? ( +
+ +

Nenhum par de chaves gerado

+
+ + +
+
+ ) : ( +
+
+
+ +
+

+ Chaves Ativas + + sessionStorage + +

+

+ Criadas em: {keys.createdAt.toLocaleString()} +

+
+
+
+ + +
+
+ +
+
+

Chave Pública (enviada ao servidor)

+
+ + {keys.publicKey} + + +
+
+ +
+

⚠️ Chave Privada (NUNCA compartilhar!)

+
+ + {keys.privateKey} + + +
+

+ Esta chave fica APENAS no seu navegador e nunca é enviada ao servidor +

+
+
+
+ )} +
+ + {/* API Tests */} +
+

🧪 Testes de API

+ +
+ {/* Public Request */} +
+

Rota Pública

+

Não requer autenticação

+ + {publicDataResult && ( +
+                {JSON.stringify(publicDataResult, null, 2)}
+              
+ )} +
+ + {/* Protected Request */} +
+

Rota Protegida

+

Requer assinatura criptográfica

+ + {protectedDataResult && ( +
+                {JSON.stringify(protectedDataResult, null, 2)}
+              
+ )} +
+
+
+ + {/* How it Works */} +
+

🔍 Como Funciona (SEM Sessões)

+
+
+ +
+ 1. Geração de Chaves: Par de chaves Ed25519 gerado LOCALMENTE no navegador +
+
+
+ +
+ 2. Chave Privada: NUNCA sai do navegador, armazenada em sessionStorage (válida apenas durante a sessão) +
+
+
+ +
+ 3. Assinatura: Cada requisição é assinada: publicKey + timestamp + nonce + mensagem +
+
+
+ +
+ 4. Validação: Servidor valida assinatura usando a chave pública recebida +
+
+
+ +
+ 5. Headers Enviados: x-public-key, x-timestamp, x-nonce, x-signature +
+
+
+ +
+ 6. Sem Sessões: Servidor NÃO armazena nada, apenas valida assinaturas +
+
+
+
+ + {/* Import Modal */} + {showImportModal && ( +
+
+
+

+ + Importar Chave Privada +

+ +
+ +
+ +