From 4a83fb45d1785873e26f6a7aec0d1cf3587de238 Mon Sep 17 00:00:00 2001 From: Rafael Lopes Date: Fri, 29 May 2026 12:22:37 -0300 Subject: [PATCH] =?UTF-8?q?Atualizada=20wiki=20de=20autentica=C3=A7=C3=A3o?= =?UTF-8?q?=20e=20acesso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Acess-Control.md | 72 +++--- Auth.md | 609 ++++++++++++++++++++++++----------------------- 2 files changed, 351 insertions(+), 330 deletions(-) diff --git a/Acess-Control.md b/Acess-Control.md index cf35874..a2e2024 100644 --- a/Acess-Control.md +++ b/Acess-Control.md @@ -29,8 +29,9 @@ Frontend -> Backend cria/atualiza usuarios -> Backend cria/atualiza usuarios_provedores -> Backend consulta usuarios_perfis e usuarios_areas - -> Backend emite JWT com perfis/areas - -> Frontend salva authToken/authUser + -> Backend emite JWT com perfis/areas + -> Frontend salva authToken/authUser em sessionStorage + -> Frontend envia Authorization: Bearer nas rotas privadas -> Frontend navega para /home ``` @@ -296,45 +297,56 @@ http://localhost:3001/admin/access/options http://localhost:3001/admin/access/users ``` -### Frontend sem depender de AD - -No console do navegador: - -```js -localStorage.setItem('authUser', JSON.stringify({ - username: 'admin@sothis.com.br', - name: 'Admin Demo', - email: 'admin@sothis.com.br', +### Frontend sem depender de AD + +Para testes manuais antigos era comum simular sessao no navegador. Esse caminho nao representa mais o fluxo real porque a API agora valida JWT assinado. + +O caminho recomendado e: + +1. criar o usuario real no AD ou Microsoft; +2. fazer login pela tela; +3. atribuir perfil/area pelo Admin; +4. repetir login ou atualizar a sessao. + +Para teste local sem provedor corporativo, gere um JWT valido pelo backend ou use um usuario real de desenvolvimento. + +Exemplo apenas para testar renderizacao visual do frontend, sem acesso real a API privada: + +```js +sessionStorage.setItem('authUser', JSON.stringify({ + username: 'admin@sothis.com.br', + name: 'Admin Demo', + email: 'admin@sothis.com.br', perfis: ['Admin'], profiles: ['Admin'], - areas: ['Suporte'], - accessStatus: 'assigned' -})); -localStorage.setItem('authToken', 'dev-token'); -location.href = '/home'; -``` + areas: ['Suporte'], + accessStatus: 'assigned' +})); +sessionStorage.setItem('authToken', 'token-jwt-valido-gerado-pelo-backend'); +location.href = '/home'; +``` Para usuario sem atribuicao: ```js -localStorage.setItem('authUser', JSON.stringify({ - username: 'novo.usuario', - name: 'Novo Usuario', - email: 'novo.usuario@sothis.com.br', +sessionStorage.setItem('authUser', JSON.stringify({ + username: 'novo.usuario', + name: 'Novo Usuario', + email: 'novo.usuario@sothis.com.br', perfis: [], profiles: [], - areas: [], - accessStatus: 'unassigned' -})); -localStorage.setItem('authToken', 'dev-token'); -location.href = '/home'; -``` + areas: [], + accessStatus: 'unassigned' +})); +sessionStorage.setItem('authToken', 'token-jwt-valido-gerado-pelo-backend'); +location.href = '/home'; +``` --- ## Limitacoes atuais -- Os endpoints `/admin/access/*` ainda nao possuem guard JWT nem checagem de perfil Admin. +- Os endpoints `/admin/access/*` exigem JWT. Rotas sensiveis exigem perfil `Admin`; visoes operacionais podem aceitar `Admin` ou `Supervisor`. - A alteracao de acesso substitui perfil/area atuais por um unico perfil e uma unica area principal. - Ainda nao ha auditoria das alteracoes de acesso. - O painel Admin consome os endpoints reais, mas ainda possui fallback visual mockado se o backend estiver indisponivel. @@ -344,8 +356,6 @@ location.href = '/home'; ## Proximos passos sugeridos -- Adicionar `AuthGuard` JWT. -- Proteger `/admin/access/*` para `Admin`. -- Registrar auditoria em `logs_auditoria`. +- Registrar auditoria em `logs_auditoria`. - Criar endpoints CRUD completos para `areas`. - Permitir multiplas areas por usuario na UI, mantendo uma area principal. diff --git a/Auth.md b/Auth.md index e7d91ae..2034368 100644 --- a/Auth.md +++ b/Auth.md @@ -1,299 +1,310 @@ -# Módulo de Autenticação - -## Visão geral - -O módulo `auth` centraliza toda a lógica de autenticação do Omnichannel. Ele suporta múltiplos provedores de identidade e emite JWT próprio da aplicação, independente de qual provedor foi usado. - -Provedores implementados: - -- **LDAP / Active Directory** — login com usuário e senha do AD corporativo -- **Microsoft OAuth (Entra ID)** — login via conta Microsoft com redirect OAuth 2.0 - -A arquitetura foi desenhada para facilitar a adição de novos provedores no futuro. - ---- - -## Estrutura de arquivos - -``` -src/modules/auth/ -├── auth.module.ts # Registro do módulo no NestJS -├── auth.controller.ts # Rotas HTTP -├── auth.service.ts # Fachada — delega para os providers -├── auth.config.ts # Leitura de variáveis de ambiente -├── auth-token.service.ts # Emissão de JWT da aplicação -├── user-access.service.ts # Sincronização do usuário autenticado com o banco -├── auth.types.ts # Interfaces TypeScript compartilhadas -└── providers/ - ├── ldap-auth.provider.ts # Autenticação LDAP/AD - ├── microsoft-oauth.provider.ts # Autenticação Microsoft OAuth - └── oauth-state.service.ts # Proteção CSRF para OAuth -``` - ---- - -## Rotas disponíveis - -| Método | Rota | Descrição | -|--------|---------------------------------|------------------------------------------------| -| GET | `/auth/config` | Retorna quais provedores estão habilitados | -| POST | `/auth/login` | Login com usuário e senha (LDAP/AD) | -| GET | `/auth/oauth/microsoft/start` | Inicia o fluxo OAuth com a Microsoft | -| GET | `/auth/oauth/microsoft/callback`| Callback que a Microsoft chama após o login | - ---- - -## Variáveis de ambiente - -```env -# Servidor -PORT=3001 -FRONTEND_URL=http://localhost:3000 - -# JWT -JWT_SECRET=uma-chave-longa-e-aleatoria -JWT_EXPIRES_IN=8h - -# Provedores ativos (separados por vírgula) -AUTH_PROVIDERS=ldap - -# LDAP / Active Directory -LDAP_ENABLED=true -LDAP_URL=ldaps://servidor-ad:636 -LDAP_DOMAIN=empresa.com.br -LDAP_USER_DN_TEMPLATE={{username}}@empresa.com.br -LDAP_SEARCH_BASE=DC=empresa,DC=com -LDAP_SEARCH_FILTER=(&(objectClass=user)(sAMAccountName={{username}})) -LDAP_TIMEOUT_MS=5000 - -# Microsoft Entra ID (desabilitado por padrão) -MICROSOFT_ENABLED=false -MICROSOFT_TENANT_ID=common -MICROSOFT_CLIENT_ID= -MICROSOFT_CLIENT_SECRET= -MICROSOFT_REDIRECT_URI=http://localhost:3001/auth/oauth/microsoft/callback -MICROSOFT_SUCCESS_REDIRECT_URL=http://localhost:3000/login -``` - -> `JWT_SECRET` deve ser uma string longa e aleatória. Em produção, nunca use o valor padrão do `.env.development`. - ---- - -## Fluxo LDAP / Active Directory - -``` -Frontend - → POST /auth/login { username, password } - → AuthController - → AuthService.loginWithLdap() - → LdapAuthProvider.authenticate() - → Conecta no servidor AD (LDAP_URL) - → Faz bind com o usuário e senha - → Se o bind falhar: UnauthorizedException - → Busca dados do usuário no diretório (se LDAP_SEARCH_BASE configurado) - → Monta objeto AuthenticatedUser - → UserAccessService.syncAuthenticatedUser() - → Cria/atualiza usuarios e usuarios_provedores - → Carrega perfis, areas e area principal - → AuthTokenService.issueToken() - → Gera JWT assinado com JWT_SECRET - → Retorna { token, user } para o frontend -``` - -O AD apenas valida a identidade. O JWT emitido é da aplicação, não do AD. - ---- - -## Fluxo Microsoft OAuth - -``` -1. Frontend redireciona para GET /auth/oauth/microsoft/start - → Backend gera um state assinado (proteção CSRF) - → Backend redireciona para login.microsoftonline.com - -2. Usuário autentica na Microsoft - -3. Microsoft chama GET /auth/oauth/microsoft/callback?code=...&state=... - → Backend valida o state (assinatura + expiração) - → Backend troca o code por access_token (chamada server-to-server) - → Backend consulta Microsoft Graph /me para obter dados do usuário - → UserAccessService.syncAuthenticatedUser() sincroniza o usuário no banco - → AuthTokenService.issueToken() gera JWT próprio - → Backend redireciona para MICROSOFT_SUCCESS_REDIRECT_URL?token=... - -4. Frontend salva o token e navega para /home -``` - ---- - -## Proteção CSRF com OAuth State - -O `OAuthStateService` protege o fluxo OAuth contra ataques de CSRF. - -**Como funciona:** - -1. No início do fluxo, o backend cria um state: - - Gera um nonce aleatório + timestamp - - Converte para base64url - - Assina com HMAC-SHA256 usando o `JWT_SECRET` - - Formato final: `payload.assinatura` - -2. No callback, o backend verifica: - - O state tem os dois pedaços (`payload.assinatura`) - - A assinatura é válida (recalcula e compara com `timingSafeEqual`) - - O state não expirou (padrão: 10 minutos, configurável via `MICROSOFT_STATE_MAX_AGE_MS`) - -Se qualquer verificação falhar, o callback é rejeitado com `400 Bad Request`. - ---- - -## JWT da aplicação - -Após qualquer autenticação bem-sucedida, o `AuthTokenService` emite um JWT com o seguinte payload: - -```json -{ - "sub": "4", - "name": "Nome Completo", - "email": "usuario@empresa.com", - "provider": "ldap", - "username": "usuario", - "perfis": ["Admin"], - "profiles": ["Admin"], - "areas": ["Suporte"], - "areaPrincipal": "Suporte", - "accessStatus": "assigned" -} -``` - -O `sub` usa o ID interno da tabela `usuarios`, convertido para string. O mesmo ID também é retornado no objeto de usuário como `databaseId`. - -O JWT é emitido e salvo no frontend, mas ainda falta a camada de `AuthGuard` no NestJS para validar o token nas rotas privadas. Portanto, hoje o token representa a sessão do usuário para o frontend, mas o backend ainda precisa ser endurecido para produção. - ---- - -## Sincronização de usuário e acesso - -Após o provedor autenticar o usuário, o `UserAccessService`: - -1. faz upsert em `usuarios` usando email ou fallback `provider:username`; -2. faz upsert em `usuarios_provedores`; -3. consulta perfis em `usuarios_perfis` + `perfis_acesso`; -4. consulta especialidades em `usuarios_areas` + `areas`; -5. retorna o usuário enriquecido com: - - `databaseId`; - - `perfis` / `profiles`; - - `areas`; - - `areaPrincipal`; - - `accessStatus`. - -`accessStatus` fica como `assigned` quando o usuário possui perfil e área. Usuários sem vínculo suficiente entram como `unassigned` e caem na tela de pendência no frontend. - ---- - -## Como adicionar um novo provedor - -1. Crie o arquivo em `src/modules/auth/providers/novo-provedor.provider.ts`: - -```typescript -import { Injectable } from '@nestjs/common'; -import { AuthConfigService } from '../auth.config'; -import { AuthTokenService } from '../auth-token.service'; -import { AuthResult } from '../auth.types'; - -@Injectable() -export class NovoProvedorProvider { - constructor( - private readonly authConfig: AuthConfigService, - private readonly authToken: AuthTokenService, - ) {} - - async authenticate(/* dados necessários */): Promise { - // 1. Valide as credenciais no provedor externo - // 2. Monte o objeto AuthenticatedUser - // 3. Emita o token com this.authToken.issueToken(user) - // 4. Retorne { token, user } - } -} -``` - -2. Registre o provider em `auth.module.ts`: - -```typescript -providers: [ - AuthConfigService, - AuthService, - AuthTokenService, - LdapAuthProvider, - MicrosoftOAuthProvider, - OAuthStateService, - NovoProvedorProvider, // adicione aqui -], -``` - -3. Injete no `AuthService` e exponha o método necessário: - -```typescript -constructor( - private readonly authConfig: AuthConfigService, - private readonly ldapAuthProvider: LdapAuthProvider, - private readonly microsoftOAuthProvider: MicrosoftOAuthProvider, - private readonly novoProvedorProvider: NovoProvedorProvider, // injete aqui -) {} - -loginComNovoProvedor(dados: any) { - return this.novoProvedorProvider.authenticate(dados); -} -``` - -4. Adicione a rota no `AuthController`. - -5. Se o provedor precisar de configuração, adicione as variáveis no `AuthConfigService` e no `.env`. - ---- - -## Diagnóstico de problemas - -### Login LDAP falha com `UnauthorizedException` - -- Verifique se `LDAP_URL` está acessível a partir do servidor backend -- Confirme que `LDAP_DOMAIN` ou `LDAP_USER_DN_TEMPLATE` está correto -- Teste a conectividade: `ldapsearch -H ldaps://servidor:636 -x` -- Verifique `LDAP_TIMEOUT_MS` — servidores lentos podem estar expirando -- O erro é genérico intencionalmente para não vazar informações. Adicione um `console.log(_error)` temporário no `catch` do `ldap-auth.provider.ts` para ver o erro real - -### Login Microsoft falha com `400 Bad Request` - -- O state expirou (padrão: 10 minutos). Se o usuário demorou muito na tela da Microsoft, repita o fluxo -- Verifique se `MICROSOFT_REDIRECT_URI` no `.env` é idêntico ao cadastrado no Azure App Registration -- Confirme que `MICROSOFT_CLIENT_ID` e `MICROSOFT_CLIENT_SECRET` estão corretos e não expiraram - -### Token inválido no frontend - -- Verifique se `JWT_SECRET` não mudou entre deploys — isso invalida todos os tokens emitidos anteriormente -- Confirme que o frontend está enviando o header `Authorization: Bearer ` - -### `GET /auth/config` retorna os provedores errados - -- Verifique `LDAP_ENABLED` e `MICROSOFT_ENABLED` no `.env` -- Reinicie o servidor — variáveis de ambiente são lidas na inicialização - ---- - -## O que ainda falta para produção - -- [x] Tabela `usuarios` no banco de dados -- [x] Tabela `usuarios_provedores` para vincular provedores externos ao usuário interno -- [x] `sub` do JWT usando ID interno do banco -- [ ] Guard NestJS para proteger rotas privadas (`@UseGuards(AuthGuard)`) -- [ ] Roles e permissões validadas no backend -- [ ] Auditoria de login -- [ ] Trocar token na query string por cookie HTTP-only (reduz exposição no browser) - ---- - -## Documentacao complementar - -A sincronizacao de usuarios autenticados com o banco, os perfis de acesso, as areas operacionais e os endpoints administrativos estao documentados em: - -- [`access-control.md`](./access-control.md) +# Modulo de Autenticacao + +## Visao geral + +O modulo `auth` centraliza login, emissao de JWT, validacao de token, autorizacao por perfil e sincronizacao do usuario autenticado com o banco do Omnichannel. + +Provedores implementados: + +- **LDAP / Active Directory**: login com usuario e senha corporativos. +- **Microsoft OAuth / Entra ID**: login via conta Microsoft. + +Depois que o provedor confirma a identidade, o backend emite um JWT proprio da aplicacao. Esse token deve ser enviado pelo frontend em todas as rotas privadas usando: + +```http +Authorization: Bearer +``` + +--- + +## Estrutura de arquivos + +```text +src/modules/auth/ +|-- auth.module.ts +|-- auth.controller.ts +|-- auth.service.ts +|-- auth.config.ts +|-- auth-token.service.ts +|-- auth.types.ts +|-- user-access.service.ts +|-- decorators/ +| |-- public.decorator.ts +| `-- roles.decorator.ts +|-- dto/ +| `-- login.dto.ts +|-- guards/ +| |-- jwt-auth.guard.ts +| `-- roles.guard.ts +|-- providers/ +| |-- ldap-auth.provider.ts +| |-- microsoft-oauth.provider.ts +| `-- oauth-state.service.ts +`-- repositories/ + `-- user-access.repository.ts +``` + +Responsabilidades principais: + +- `AuthController`: expoe rotas publicas de login e OAuth. +- `AuthService`: fachada que delega para LDAP ou Microsoft. +- `AuthTokenService`: emite e valida JWT. +- `JwtAuthGuard`: exige Bearer token nas rotas privadas. +- `RolesGuard`: valida perfis com `@Roles()`. +- `UserAccessService`: regra de sincronizacao do usuario autenticado. +- `UserAccessRepository`: instrucoes SQL do fluxo de acesso. +- `LoginDto`: validacao formal do payload de login. + +--- + +## Rotas publicas + +As rotas abaixo nao exigem JWT: + +| Metodo | Rota | Descricao | +|---|---|---| +| GET | `/health` | Health check da API | +| GET | `/auth/config` | Informa provedores de login habilitados | +| POST | `/auth/login` | Login LDAP/AD | +| GET | `/auth/oauth/microsoft/start` | Inicia login Microsoft | +| GET | `/auth/oauth/microsoft/callback` | Recebe callback Microsoft | + +Todas as demais rotas HTTP exigem JWT valido. + +--- + +## Rotas privadas e perfis + +O `JwtAuthGuard` foi registrado como guard global. Isso significa que toda rota e privada por padrao, exceto as marcadas com `@Public()`. + +Exemplo: + +```typescript +@Public() +@Get('config') +getConfig() {} +``` + +Para autorizacao por perfil, o backend usa `@Roles()`: + +```typescript +@Roles('Admin') +@Get('users') +listUsers() {} +``` + +Regras atuais: + +- `Admin`: acessa administracao, usuarios, areas, base de conhecimento e configuracoes sensiveis. +- `Supervisor`: acessa visoes operacionais liberadas para supervisao. +- `Agente`: acessa fluxos autenticados sem permissao administrativa. + +--- + +## Fluxo LDAP / Active Directory + +```text +Frontend + -> POST /auth/login { username, password } + -> LoginDto valida payload + -> AuthController + -> AuthService.loginWithLdap() + -> LdapAuthProvider.authenticate() + -> valida se LDAP esta habilitado + -> conecta no servidor LDAP + -> faz bind com usuario e senha + -> busca dados do usuario no diretorio, se configurado + -> monta AuthenticatedUser + -> UserAccessService.syncAuthenticatedUser() + -> UserAccessRepository executa SQL + -> cria/atualiza usuarios + -> cria/atualiza usuarios_provedores + -> carrega perfis e areas + -> AuthTokenService.issueToken() + -> retorna { token, user } +``` + +O erro retornado ao usuario continua generico (`Autenticacao falhou`). O erro real e registrado no log interno do provider para diagnostico sem vazar detalhes sensiveis. + +--- + +## Fluxo Microsoft OAuth + +```text +Frontend + -> GET /auth/oauth/microsoft/start +Backend + -> gera state assinado com HMAC-SHA256 usando JWT_SECRET + -> redireciona para Microsoft +Usuario + -> autentica na Microsoft +Microsoft + -> chama /auth/oauth/microsoft/callback?code=...&state=... +Backend + -> valida state + -> troca code por access_token + -> consulta Microsoft Graph /me + -> sincroniza usuario no banco + -> emite JWT da aplicacao + -> redireciona para o frontend com payload no fragmento da URL +Frontend + -> le #auth=... + -> salva sessao em sessionStorage + -> limpa a URL +``` + +O token nao e mais enviado em query string. Ele vai no fragmento `#auth=...`, que nao e enviado ao servidor em requisicoes HTTP e e removido pelo frontend apos processamento. + +--- + +## JWT da aplicacao + +Payload principal: + +```json +{ + "sub": "4", + "name": "Nome Completo", + "email": "usuario@empresa.com", + "provider": "ldap", + "username": "usuario", + "perfis": ["Admin"], + "profiles": ["Admin"], + "areas": ["Suporte"], + "areaPrincipal": "Suporte", + "accessStatus": "assigned" +} +``` + +O `sub` usa o ID interno da tabela `usuarios`. + +O token e: + +- assinado com `JWT_SECRET`; +- emitido com expiracao definida por `JWT_EXPIRES_IN`; +- validado em todas as rotas privadas; +- enviado pelo frontend como Bearer token. + +--- + +## Sessao no frontend + +O frontend armazena `authToken` e `authUser` em `sessionStorage`, nao em `localStorage`. + +Motivo: + +- reduz persistencia indevida apos fechar o navegador; +- evita reaproveitamento de sessoes antigas salvas localmente; +- continua simples para o modelo atual do produto. + +Observacao: uma alternativa ainda mais forte para producao seria cookie `HttpOnly` + `Secure` + `SameSite`, mas exigiria ajuste maior no fluxo do frontend e no CORS. + +--- + +## WebSocket WhatsApp + +O namespace Socket.IO `/whatsapp` tambem exige JWT no handshake. + +O frontend envia: + +```javascript +io(WHATSAPP_SOCKET_URL, { + auth: { + token: getAuthToken() + } +}); +``` + +Sem token valido, o socket nao conecta e nao recebe QR Code, status ou mensagens. + +--- + +## Rate limit + +O backend usa `@nestjs/throttler` com limite global configuravel: + +```env +RATE_LIMIT_TTL_MS=60000 +RATE_LIMIT_MAX=300 +``` + +Padrao atual: 300 requisicoes por IP a cada 60 segundos. + +--- + +## Variaveis de ambiente + +```env +FRONTEND_URL=http://localhost:3000 +JWT_SECRET=uma-chave-longa-e-aleatoria +JWT_EXPIRES_IN=8h + +RATE_LIMIT_TTL_MS=60000 +RATE_LIMIT_MAX=300 + +AUTH_PROVIDERS=ldap,microsoft + +LDAP_ENABLED=true +LDAP_URL=ldaps://servidor-ad:636 +LDAP_DOMAIN=empresa.com.br +LDAP_USER_DN_TEMPLATE={{username}}@empresa.com.br +LDAP_SEARCH_BASE=DC=empresa,DC=com +LDAP_SEARCH_FILTER=(&(objectClass=user)(sAMAccountName={{username}})) +LDAP_TIMEOUT_MS=5000 + +MICROSOFT_ENABLED=false +MICROSOFT_TENANT_ID=common +MICROSOFT_CLIENT_ID= +MICROSOFT_CLIENT_SECRET= +MICROSOFT_REDIRECT_URI=http://localhost:3001/auth/oauth/microsoft/callback +MICROSOFT_SUCCESS_REDIRECT_URL=http://localhost:3000/login +``` + +`JWT_SECRET` deve ser forte e exclusivo por ambiente. + +--- + +## Diagnostico + +### Login LDAP falha + +- Verifique `LDAP_URL`, `LDAP_DOMAIN` e `LDAP_USER_DN_TEMPLATE`. +- Confirme conectividade do backend com o AD. +- Verifique se `LDAP_SEARCH_BASE` e `LDAP_SEARCH_FILTER` batem com o diretorio. +- Consulte logs do backend; o provider registra o motivo interno sem expor para o usuario. + +### Token invalido ou expirado + +- Verifique se o frontend esta enviando `Authorization: Bearer `. +- Verifique se `JWT_SECRET` nao mudou depois da emissao do token. +- Faca login novamente para renovar a sessao. + +### Usuario autenticado cai como `unassigned` + +- O login funcionou, mas faltam perfil/area no banco. +- Um Admin deve atribuir perfil e area para o usuario. + +### Microsoft OAuth falha + +- Verifique `MICROSOFT_REDIRECT_URI` no Azure e no `.env`. +- Confirme `MICROSOFT_CLIENT_ID` e `MICROSOFT_CLIENT_SECRET`. +- Refaca o fluxo se o state expirar. + +--- + +## Estado atual + +- [x] Login LDAP/AD +- [x] Login Microsoft OAuth +- [x] State OAuth assinado +- [x] JWT emitido pela aplicacao +- [x] JWT validado nas rotas privadas +- [x] Roles no backend +- [x] Bearer token no frontend +- [x] Token fora de query string no OAuth +- [x] WebSocket WhatsApp protegido por token +- [x] SQL de acesso isolado em repository +- [x] DTO e ValidationPipe no login +- [x] Rate limit global +- [ ] Auditoria formal de login em tabela propria +- [ ] Cookie HttpOnly como alternativa futura ao sessionStorage