Segurança da API
Proteger sua API contra ataques é fundamental em produção.
Helmet - Headers de Segurança
bash
npm install helmettypescript
// src/app.tsimport helmet from 'helmet'; app.use(helmet()); // Configuração personalizadaapp.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, }, hsts: { maxAge: 31536000, includeSubDomains: true, },}));CORS Configurado
typescript
// src/config/cors.tsimport cors from 'cors'; const allowedOrigins = [ 'http://localhost:3000', 'https://ecommerce.com', 'https://admin.ecommerce.com',]; export const corsConfig = cors({ origin: (origin, callback) => { if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Bloqueado pelo CORS')); } }, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 86400, // 24 horas});Sanitização de Input
bash
npm install express-mongo-sanitize xss-cleantypescript
import mongoSanitize from 'express-mongo-sanitize';import xss from 'xss-clean'; // Remove caracteres $ e . (NoSQL injection)app.use(mongoSanitize()); // Sanitiza input contra XSSapp.use(xss());Validação Robusta com Zod
typescript
// src/schemas/user.schema.tsimport { z } from 'zod'; export const registerSchema = z.object({ body: z.object({ name: z.string() .min(2, 'Nome muito curto') .max(100, 'Nome muito longo') .regex(/^[a-zA-ZÀ-ú\s]+$/, 'Nome inválido'), email: z.string() .email('E-mail inválido') .toLowerCase() .max(255), password: z.string() .min(8, 'Mínimo 8 caracteres') .regex(/[A-Z]/, 'Precisa de letra maiúscula') .regex(/[a-z]/, 'Precisa de letra minúscula') .regex(/[0-9]/, 'Precisa de número') .regex(/[^A-Za-z0-9]/, 'Precisa de caractere especial'), }),});Rate Limiting Avançado
typescript
// src/middlewares/rate-limit.middleware.tsimport rateLimit from 'express-rate-limit';import RedisStore from 'rate-limit-redis';import redis from '../config/redis'; // Rate limit geralexport const generalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutos max: 100, message: { error: 'Muitas requisições' }, standardHeaders: true, legacyHeaders: false, store: new RedisStore({ sendCommand: (...args: string[]) => redis.call(...args), }),}); // Rate limit para auth (mais restrito)export const authLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hora max: 5, // 5 tentativas message: { error: 'Muitas tentativas de login' }, skipSuccessfulRequests: true,}); // Usoapp.use('/api', generalLimiter);app.use('/api/auth/login', authLimiter);Proteção contra Brute Force
typescript
// src/services/auth.service.tsimport redis from '../config/redis'; const MAX_ATTEMPTS = 5;const BLOCK_DURATION = 15 * 60; // 15 minutos export class AuthService { async login(email: string, password: string) { const attemptsKey = `login_attempts:${email}`; const blockKey = `login_blocked:${email}`; // Verifica se está bloqueado const isBlocked = await redis.get(blockKey); if (isBlocked) { throw new AppError('Conta temporariamente bloqueada', 429); } const user = await prisma.user.findUnique({ where: { email } }); const isValid = user && await bcrypt.compare(password, user.password); if (!isValid) { const attempts = await redis.incr(attemptsKey); await redis.expire(attemptsKey, BLOCK_DURATION); if (attempts >= MAX_ATTEMPTS) { await redis.setex(blockKey, BLOCK_DURATION, '1'); await redis.del(attemptsKey); throw new AppError('Conta bloqueada por 15 minutos', 429); } throw new AppError('Credenciais inválidas', 401); } // Reset tentativas no sucesso await redis.del(attemptsKey); return this.generateToken(user); }}Logging de Segurança
typescript
// src/middlewares/security-logger.middleware.tsimport { Request, Response, NextFunction } from 'express'; export const securityLogger = (req: Request, res: Response, next: NextFunction) => { const suspiciousPatterns = [ /(\%27)|(\')|(\-\-)|(\%23)|(#)/i, // SQL Injection /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // XSS /\.\.\//g, // Path traversal ]; const checkValue = (value: string): boolean => { return suspiciousPatterns.some(pattern => pattern.test(value)); }; const allParams = { ...req.query, ...req.body, ...req.params, }; for (const [key, value] of Object.entries(allParams)) { if (typeof value === 'string' && checkValue(value)) { console.warn('⚠️ Tentativa suspeita detectada:', { ip: req.ip, path: req.path, param: key, value: value.substring(0, 100), timestamp: new Date().toISOString(), }); } } next();};Variáveis de Ambiente Seguras
typescript
// src/config/env.tsimport { z } from 'zod'; const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']), DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), // ... outras variáveis}); export const env = envSchema.parse(process.env);Resumo
- ✅ Helmet para headers de segurança
- ✅ CORS configurado corretamente
- ✅ Sanitização contra injeções
- ✅ Rate limiting com Redis
- ✅ Proteção contra brute force
- ✅ Logging de segurança
Próxima aula: Docker e Containers! 🐳