Quando criamos nossa API e disponibilizamos publicamente estamos sujeito a ataques como força bruta e DDoS.
As intenções desses ataques são sobrecarregar nosso servidor com múltiplas requisições e tornar nosso serviço indisponível na rede.
Para evitar tais ataques podemos limitar o número de requisições por IPs, isso vai deixar nosso serviço mais profissional e caso aconteça um ataque não fique indisponível.
1 – Criar Projeto e Instalar Bibliotecas
Crie um simples projeto que será nossa API publica, para isso siga os passos:
- Crie um diretório:
mkdir brute_force
- Entre no diretório:
cd brute_force
- Crie as configurações para o projeto NodeJs:
yarn init
- Instale as seguintes bibliotecas abaixo:
yarn add express
yarn add @types/express -D
yarn add express-async-errors
yarn add typescript -D
yarn add dotenv
yarn add ts-node-dev -D
yarn add tsconfig-paths -D
yarn add cors
yarn add @types/cors -D
2 – API
Nossa API será bem simples, pois o objetivo é tratar os problemas de múltiplas requisições.
Crie uma estrutura de projeto com segue na imagem abaixo:
Antes de começar a codificação, execute o comando abaixo na raiz o projeto para criar as configurações para usar Typescript:
yarn tsc --init
a - Models
Nosso modelo é simples, crie o arquivo user.ts na pasta models:
export default class User{
constructor(
id: number,
name:String
){}
}
b - Controllers
O controle só retorna uma lista de usuários em memória para simular as requisições, crie o arquivo usersController.ts na pasta controllers:
import { Request, Response } from 'express';
import User from '../models/user';
class UsersController {
public async show(request: Request, response: Response): Promise<Response> {
const users:User[] = [
{id:1,name:'user 01'},
{id:2,name:'user 02'},
{id:3,name:'user 03'},
{id:4,name:'user 04'},
{id:5,name:'user 05'},
{id:6,name:'user 06'},
{id:7,name:'user 07'},
{id:8,name:'user 08'},
{id:9,name:'user 09'},
{id:10,name:'user 10'},
];
return response.status(200).json(users);
}
}
export default UsersController;
c - Routes
Nossa rotas para o acesso a API, crie o arquivo index.ts dentro da pasta routes:
import { Router } from 'express';
import UsersController from '../controllers/usersController';
const usersController = new UsersController();
const routes = Router();
routes.get('/users', usersController.show);
export default routes;
d - Erros Customizados
Vamos criar uma classe para customizar os erros, crie o arquivo appError.ts dentro da pasta errors:
class AppError {
public readonly message: string;
public readonly statusCode: number;
constructor(message: string, statusCode = 400) {
this.message = message;
this.statusCode = statusCode;
}
}
export default AppError;
e - Servidor
Crie o arquivo server.ts dentro da pasta src para podemos executar no servidor:
import express, { Request, Response, NextFunction } from 'express';
import 'express-async-errors';
import cors from 'cors';
import AppError from './errors/appError';
import routes from './routes';
const app = express();
app.use(cors());
app.use(express.json());
app.use(routes);
app.use((err: Error, request: Request, response: Response, _: NextFunction) => {
if (err instanceof AppError) {
return response.status(err.statusCode).json({
status: 'error',
message: err.message,
});
}
return response.status(500).json({
status: 'error',
message: 'Internal server error',
});
});
app.listen(3333, () => {
console.log('Server started on port 3333');
});
f - Executar a API
Para rodar a API, execute o seguinte comando no terminal:
yarn dev:server
Saída:
$ yarn dev:server
yarn run v1.21.1
$ ts-node-dev -r tsconfig-paths/register --inspect --transpile-only --ignore-watch node_modules src/server.ts
ts-node-dev ver. 1.0.0-pre.63 (using ts-node ver. 8.10.2, typescript ver. 4.0.3)
Debugger listening on ws://127.0.0.1:9229/31789a56-8b3f-4628-944d-56aeb1a6f061
For help, see: https://nodejs.org/en/docs/inspector
Missing baseUrl in compilerOptions. tsconfig-paths will be skipped
Server started on port 33
- Para obter os dados da API, acesse a rota:
localhost:3333/users
2 – Express Rate Limiter
Com nossa API criada e rodando, agora vamos a parte de limitação de múltiplas requisições por IPs.
Para isso temos que instalar as seguintes bibliotecas:
yarn add rate-limiter-flexible
b - Redis
vamos precisar de um lugar para guardar as requisições poderíamos usar a memória porém se nosso servidor reiniciar vamos perder as informações dos IPs e quantidades de requisições.
Para solucionar o problema optei por usar Redis.
yarn add redis
yarn add @types/redis -D
3 – Middleware
Vamos criar um middleware para receber as requisições e verificar se o IP que realizou tem múltiplas requisições de forma simultânea.
Crie o arquivo rateLimiter.ts na pasta middlewares.
import {Request, Response, NextFunction} from 'express';
import redis from 'redis';
import {RateLimiterRedis} from 'rate-limiter-flexible';
const redisClient = redis.createClient({
host: 'localhost',
port:6379,
password: undefined,
});
const limiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'rateLimiter',
points: 5,//quantas requisições por IP
duration: 5,//segundos
});
export default async function rateTimiter(request:Request, response:Response, next:NextFunction):Promise<any>{
try{
await limiter.consume(request.ip);
return next();
}catch(err){
return response.status(429).json({message: 'Too many requests', code:429})
}
}
No código, fiz a configuração que ser nossa API receber 5 requisições em um intervalo de 5 segundos do mesmo IP retornará uma resposta 429..
Então caso o limite seja rompido será retornar a resposta.
{
"message": "Too many requests",
"code": 429
}
Por fim volte ao arquivo server.ts e adicione o middleware rateLimiter.
import express, { Request, Response, NextFunction } from 'express';
import 'express-async-errors';
import cors from 'cors';
import AppError from './errors/appError';
import routes from './routes';
import rateLimiter from './middlewares/rateLimiter';
const app = express();
app.use(rateLimiter);
app.use(cors());
app.use(express.json());
app.use(routes);
app.use((err: Error, request: Request, response: Response, _: NextFunction) => {
if (err instanceof AppError) {
return response.status(err.statusCode).json({
status: 'error',
message: err.message,
});
}
return response.status(500).json({
status: 'error',
message: 'Internal server error',
});
});
app.listen(3333, () => {
console.log('Server started on port 3333');
});
Top comments (0)