Tratamento de ataques força bruta e DDoS em API NodeJs
Tags: node, express, typescript, ddos
Published: 2020-10-12 15:53:15

Quando criamos nossa **API** e disponibilizamos publicamente estamos sujeito a ataques como **força bruta** e [**DDoS**](https://pt.wikipedia.org/wiki/Ataque_de_nega%C3%A7%C3%A3o_de_servi%C3%A7o). 
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: 
```javascript 
mkdir brute_force 
```

* Entre no diretório:
```javascript 
cd brute_force 
```

* Crie as configurações para o projeto **NodeJs**: 
```javascript 
yarn init 
```

* Instale as seguintes bibliotecas abaixo:

```javascript 
yarn add express
yarn add @types/express -D
yarn add express-async-errors
```

```javascript 
yarn add typescript -D
```

```javascript 
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:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w8r6fkltmqh5zr817f12.png)

Antes de começar a codificação, execute o comando abaixo na raiz o projeto para criar as configurações para usar **Typescript**:

```javascript 
yarn tsc --init
```
**a - Models**

Nosso modelo é simples, crie o arquivo **user.ts** na pasta **models**:

```javascript 
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**:

```javascript 
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**: 

```javascript 
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**:

```javascript 
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:

```javascript 
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:

```javascript 
yarn dev:server
```
Saída:

```javascript 
$ 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:

```javascript 
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:

 **a - [**rate-limiter-flexible**]
(https://www.npmjs.com/package/rate-limiter-flexible).**

```javascript 
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**.

```javascript 
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**.

```javascript 
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]
(https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status/429).**. 
Então caso o limite seja rompido será retornar a resposta.

```javascript 
{
    "message": "Too many requests",
    "code": 429
}
```

Por fim volte ao arquivo **server.ts** e adicione o **middleware rateLimiter**.

```javascript 
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');
});
```

**[Github do projeto]
(https://github.com/wandealves/brute_force).**