This commit is contained in:
parent
cb08a9f48a
commit
b2aa1a7712
87
README.md
Normal file
87
README.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Omnichannel Backend
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Backend da plataforma Omnichannel da Sothis, responsavel por autenticacao, atendimento, integracao com WhatsApp, gestao administrativa, contatos, base de conhecimento e servicos de apoio ao painel web.
|
||||
|
||||
Este repositorio contem apenas a API. Para subir o projeto completo com frontend, backend e configuracao de deploy, utilize o repositorio de orquestracao:
|
||||
|
||||
https://chaleiradev.sothistelecom.com/Sothis/omnichannel-deploy
|
||||
|
||||
## Stack
|
||||
|
||||
- Node.js
|
||||
- NestJS
|
||||
- TypeScript
|
||||
- PostgreSQL
|
||||
- Socket.IO
|
||||
- whatsapp-web.js
|
||||
- JSON Web Token para autenticacao
|
||||
- LDAP/AD e Microsoft OAuth como provedores de login configuraveis
|
||||
|
||||
## Documentacao da API
|
||||
|
||||
A documentacao detalhada das rotas fica disponivel no Swagger do backend no ambiente publicado.
|
||||
|
||||
Para documentacao tecnica complementar do backend, decisoes, modulos e operacao, acesse a wiki:
|
||||
|
||||
https://chaleiradev.sothistelecom.com/Sothis/omnichannel-backend/wiki
|
||||
|
||||
## Execucao local
|
||||
|
||||
Instale as dependencias:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Crie o arquivo de ambiente a partir do exemplo:
|
||||
|
||||
```bash
|
||||
cp .env.example .env.development
|
||||
```
|
||||
|
||||
Configure as variaveis de banco, JWT, CORS e integracoes no `.env.development`.
|
||||
|
||||
Execute em modo desenvolvimento:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Por padrao, a API usa a porta configurada em `PORT` ou `BACKEND_PORT`. Se nenhuma variavel for definida, utiliza `3001`.
|
||||
|
||||
## Build e producao
|
||||
|
||||
Gere o build:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Execute a versao compilada:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## Estrutura principal
|
||||
|
||||
- `src/infra`: configuracao, banco de dados, logger e infraestrutura compartilhada.
|
||||
- `src/modules/auth`: autenticacao, JWT, LDAP/AD e Microsoft OAuth.
|
||||
- `src/modules/admin`: recursos administrativos, base de conhecimento, contatos e controles de acesso.
|
||||
- `src/modules/whatsapp`: conexao WhatsApp, QR Code, mensagens, transferencia e eventos em tempo real.
|
||||
- `src/modules/attendance`: apoio as regras de atendimento.
|
||||
- `src/modules/call`, `src/modules/chat` e `src/modules/home`: modulos de apoio as telas e fluxos do frontend.
|
||||
|
||||
## Observacoes
|
||||
|
||||
- O banco de dados nao e criado por este repositorio. A configuracao deve apontar para uma instancia PostgreSQL existente.
|
||||
- Arquivos locais como `.env*`, logs, `dist`, dumps e sessoes do WhatsApp ficam fora do Git.
|
||||
- Para operacao completa, deploy e atualizacao em producao, consulte o repositorio `omnichannel-deploy`.
|
||||
@ -1,351 +0,0 @@
|
||||
# Controle de Acesso, Areas e Usuarios
|
||||
|
||||
## Visao geral
|
||||
|
||||
O backend agora usa o banco PostgreSQL para manter o usuario interno da aplicacao e suas atribuicoes operacionais.
|
||||
|
||||
A autenticacao continua sendo feita por provedores externos:
|
||||
|
||||
- LDAP / Active Directory
|
||||
- Microsoft OAuth
|
||||
|
||||
Depois que o provedor confirma a identidade, o backend sincroniza esse usuario no banco e consulta:
|
||||
|
||||
- perfis de acesso
|
||||
- areas vinculadas
|
||||
- area principal
|
||||
- status de acesso
|
||||
|
||||
Esse status determina qual experiencia o frontend deve renderizar em `/home`.
|
||||
|
||||
---
|
||||
|
||||
## Fluxo de login com banco
|
||||
|
||||
```text
|
||||
Frontend
|
||||
-> POST /auth/login ou OAuth Microsoft
|
||||
-> Backend autentica no provedor externo
|
||||
-> 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
|
||||
-> Frontend navega para /home
|
||||
```
|
||||
|
||||
O provedor externo valida identidade. O banco define acesso dentro do Omnichannel.
|
||||
|
||||
---
|
||||
|
||||
## Tabelas usadas
|
||||
|
||||
### `usuarios`
|
||||
|
||||
Representa uma pessoa autenticada no produto.
|
||||
|
||||
Campos principais:
|
||||
|
||||
- `id`
|
||||
- `nome`
|
||||
- `email`
|
||||
- `ativo`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
### `usuarios_provedores`
|
||||
|
||||
Relaciona o usuario interno com o provedor externo usado no login.
|
||||
|
||||
Exemplos:
|
||||
|
||||
- `ldap` + username do AD
|
||||
- `microsoft` + userPrincipalName/email
|
||||
|
||||
### `perfis_acesso`
|
||||
|
||||
Define os niveis de acesso.
|
||||
|
||||
Perfis iniciais:
|
||||
|
||||
- `Agente`
|
||||
- `Supervisor`
|
||||
- `Admin`
|
||||
|
||||
### `usuarios_perfis`
|
||||
|
||||
Relaciona usuarios e perfis.
|
||||
|
||||
Hoje o frontend usa o perfil principal para decidir a experiencia em `/home`.
|
||||
|
||||
### `areas`
|
||||
|
||||
Representa as areas operacionais.
|
||||
|
||||
Areas iniciais:
|
||||
|
||||
- `Suporte`
|
||||
- `Financeiro`
|
||||
- `Comercial`
|
||||
|
||||
### `usuarios_areas`
|
||||
|
||||
Relaciona usuarios e areas.
|
||||
|
||||
Um usuario pode ter mais de uma area, mas a regra atual permite no maximo uma area principal por usuario.
|
||||
|
||||
---
|
||||
|
||||
## Status de acesso
|
||||
|
||||
O backend devolve `accessStatus` no usuario autenticado.
|
||||
|
||||
Valores:
|
||||
|
||||
- `assigned`: usuario tem pelo menos um perfil e uma area
|
||||
- `unassigned`: usuario existe/autenticou, mas ainda nao tem perfil ou area
|
||||
|
||||
### Comportamento no frontend
|
||||
|
||||
```text
|
||||
assigned + Admin -> /home renderiza painel Admin
|
||||
assigned + Supervisor -> /home renderiza painel Supervisor
|
||||
assigned + Agente -> /home renderiza tela do Atendente
|
||||
unassigned -> /home renderiza tela de acesso aguardando configuracao
|
||||
```
|
||||
|
||||
Esse comportamento permite que um usuario novo entre via AD/OAuth, seja criado no banco e fique bloqueado ate um Admin atribuir perfil e area.
|
||||
|
||||
---
|
||||
|
||||
## Payload do usuario autenticado
|
||||
|
||||
Exemplo de resposta do login:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "jwt",
|
||||
"user": {
|
||||
"id": "1",
|
||||
"databaseId": 1,
|
||||
"name": "Atendente Demo",
|
||||
"email": "atendente@sothis.com.br",
|
||||
"username": "atendente",
|
||||
"provider": "ldap",
|
||||
"perfis": ["Agente"],
|
||||
"profiles": ["Agente"],
|
||||
"areas": ["Suporte"],
|
||||
"areaPrincipal": "Suporte",
|
||||
"accessStatus": "assigned"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No JWT tambem entram:
|
||||
|
||||
- `name`
|
||||
- `email`
|
||||
- `provider`
|
||||
- `username`
|
||||
- `perfis`
|
||||
- `profiles`
|
||||
- `areas`
|
||||
- `areaPrincipal`
|
||||
- `accessStatus`
|
||||
|
||||
---
|
||||
|
||||
## Variaveis de ambiente
|
||||
|
||||
O backend le as variaveis de banco pelo `.env.development` em desenvolvimento.
|
||||
|
||||
```env
|
||||
DB_HOST=10.0.120.75
|
||||
DB_PORT=5432
|
||||
DB_USER=desenvolvimento
|
||||
DB_PASSWORD=********
|
||||
DB_NAME=omnichannel-dev
|
||||
```
|
||||
|
||||
Observacao:
|
||||
|
||||
- `npm run dev` usa `NODE_ENV=development` e carrega `.env.development`.
|
||||
- `npm run start` usa `NODE_ENV=production`; nesse caso, configure `.env.production` ou variaveis do ambiente.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints administrativos
|
||||
|
||||
Base path:
|
||||
|
||||
```text
|
||||
/admin/access
|
||||
```
|
||||
|
||||
### Listar opcoes de acesso
|
||||
|
||||
```http
|
||||
GET /admin/access/options
|
||||
```
|
||||
|
||||
Resposta:
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": [
|
||||
{ "id": 3, "nome": "Admin" },
|
||||
{ "id": 1, "nome": "Agente" },
|
||||
{ "id": 2, "nome": "Supervisor" }
|
||||
],
|
||||
"areas": [
|
||||
{ "id": 3, "nome": "Comercial" },
|
||||
{ "id": 2, "nome": "Financeiro" },
|
||||
{ "id": 1, "nome": "Suporte" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Listar usuarios
|
||||
|
||||
```http
|
||||
GET /admin/access/users
|
||||
```
|
||||
|
||||
Resposta:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"nome": "Admin Demo",
|
||||
"email": "admin@sothis.com.br",
|
||||
"ativo": true,
|
||||
"perfis": [{ "id": 3, "nome": "Admin" }],
|
||||
"areas": [{ "id": 1, "nome": "Suporte", "principal": true }],
|
||||
"perfilPrincipal": { "id": 3, "nome": "Admin" },
|
||||
"areaPrincipal": { "id": 1, "nome": "Suporte", "principal": true },
|
||||
"accessStatus": "assigned"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Atualizar acesso de usuario
|
||||
|
||||
```http
|
||||
PUT /admin/access/users/:id
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"perfilId": 2,
|
||||
"areaId": 1
|
||||
}
|
||||
```
|
||||
|
||||
Comportamento atual:
|
||||
|
||||
- remove perfis anteriores do usuario
|
||||
- remove areas anteriores do usuario
|
||||
- cria o novo perfil informado
|
||||
- cria a nova area como principal
|
||||
- retorna o usuario atualizado
|
||||
|
||||
Para remover atribuicoes:
|
||||
|
||||
```json
|
||||
{
|
||||
"perfilId": null,
|
||||
"areaId": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usuarios de demonstracao
|
||||
|
||||
A migration `003_demo_access.sql` cria usuarios iniciais para demonstracao:
|
||||
|
||||
| Usuario | Email | Perfil | Area |
|
||||
|---|---|---|---|
|
||||
| Admin Demo | `admin@sothis.com.br` | Admin | Suporte |
|
||||
| Supervisor Demo | `supervisor@sothis.com.br` | Supervisor | Suporte |
|
||||
| Atendente Demo | `atendente@sothis.com.br` | Agente | Suporte |
|
||||
|
||||
Esses usuarios existem no banco, mas nao substituem a autenticacao real.
|
||||
|
||||
Para logar pela tela de login, o usuario precisa existir no AD ou no provedor Microsoft configurado.
|
||||
|
||||
---
|
||||
|
||||
## Como testar
|
||||
|
||||
### Backend
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Endpoints de validacao:
|
||||
|
||||
```text
|
||||
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',
|
||||
perfis: ['Admin'],
|
||||
profiles: ['Admin'],
|
||||
areas: ['Suporte'],
|
||||
accessStatus: 'assigned'
|
||||
}));
|
||||
localStorage.setItem('authToken', 'dev-token');
|
||||
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',
|
||||
perfis: [],
|
||||
profiles: [],
|
||||
areas: [],
|
||||
accessStatus: 'unassigned'
|
||||
}));
|
||||
localStorage.setItem('authToken', 'dev-token');
|
||||
location.href = '/home';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Limitacoes atuais
|
||||
|
||||
- Os endpoints `/admin/access/*` ainda nao possuem guard JWT nem checagem de perfil Admin.
|
||||
- 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.
|
||||
- A senha do banco nao deve ser commitada; arquivos `.env.*` seguem ignorados pelo Git.
|
||||
|
||||
---
|
||||
|
||||
## Proximos passos sugeridos
|
||||
|
||||
- Adicionar `AuthGuard` JWT.
|
||||
- Proteger `/admin/access/*` para `Admin`.
|
||||
- Registrar auditoria em `logs_auditoria`.
|
||||
- Criar endpoints CRUD completos para `areas`.
|
||||
- Permitir multiplas areas por usuario na UI, mantendo uma area principal.
|
||||
268
docs/auth.md
268
docs/auth.md
@ -1,268 +0,0 @@
|
||||
# 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
|
||||
├── 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
|
||||
→ 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
|
||||
→ 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": "identificador-do-usuario",
|
||||
"name": "Nome Completo",
|
||||
"email": "usuario@empresa.com",
|
||||
"provider": "ldap",
|
||||
"username": "usuario"
|
||||
}
|
||||
```
|
||||
|
||||
O `sub` é atualmente o email ou identificador externo. Quando houver banco de dados, deve ser substituído pelo ID interno da tabela `users`.
|
||||
|
||||
---
|
||||
|
||||
## 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<AuthResult> {
|
||||
// 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 <token>`
|
||||
|
||||
### `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
|
||||
|
||||
- [ ] Tabela `users` no banco de dados
|
||||
- [ ] Tabela `auth_identities` para vincular provedores externos ao usuário interno
|
||||
- [ ] `sub` do JWT usando ID interno do banco, não email externo
|
||||
- [ ] Guard NestJS para proteger rotas privadas (`@UseGuards(AuthGuard)`)
|
||||
- [ ] Roles e permissões
|
||||
- [ ] 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)
|
||||
@ -1,132 +0,0 @@
|
||||
# Modulo de Chat WhatsApp (Backend)
|
||||
|
||||
## Visao geral
|
||||
|
||||
O modulo de WhatsApp do backend e desenvolvido em **NestJS** e utiliza a biblioteca **whatsapp-web.js** (que roda uma instancia headless do Chromium via **Puppeteer**) para integrar a aplicacao diretamente com o WhatsApp Web em tempo real.
|
||||
|
||||
A arquitetura e constituida por quatro pilares:
|
||||
1. **Puppeteer Client**: Controla o WhatsApp Web, gera o QR Code para autenticacao e escuta eventos de novas mensagens.
|
||||
2. **WebSocket Gateway**: Transmite eventos em tempo real (novas mensagens, atualizacao de status) para o frontend usando Socket.io.
|
||||
3. **Persistencia Hibrida**: Mantem conversas indexadas localmente via JSON para performance e o status de atribuicao de atendimento gravado no banco relacional (PostgreSQL).
|
||||
4. **Servico de Atribuicao**: Gerencia o vinculo dos chats com os atendentes (`usuarios`) e suas respectivas `areas` operacionais.
|
||||
|
||||
---
|
||||
|
||||
## Fluxo de Eventos e Mensagens
|
||||
|
||||
### 1. Inicializacao e Conexao
|
||||
* O backend inicia o cliente do `whatsapp-web.js` na porta configurada.
|
||||
* Se nenhuma sessao ativa for encontrada na pasta `/whatsapp-session`, o cliente gera um QR Code em formato Base64.
|
||||
* Esse QR Code e enviado via WebSocket (`qr`) para o frontend configurar o dispositivo.
|
||||
* Uma vez conectado, o status muda para `CONNECTED` e a pasta de sessao e gravada localmente.
|
||||
|
||||
### 2. Captura de Mensagens (`message_create`)
|
||||
Utilizamos o evento global `message_create` para capturar tanto mensagens recebidas do cliente quanto mensagens enviadas pelo proprio atendente (seja pela tela ou por outro dispositivo sincronizado):
|
||||
|
||||
```text
|
||||
Puppeteer (Message)
|
||||
-> Trata de-duplicacao ou broadcast
|
||||
-> Se possuir midia, baixa os buffers em tempo real
|
||||
-> Transmite via WebSocket 'message' para o frontend
|
||||
-> Salva ou atualiza a conversa na persistencia local JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Persistencia e Banco de Dados
|
||||
|
||||
### 1. Cache Local JSON (`whatsapp-chats-persist.json`)
|
||||
Para evitar sobrecarregar o banco relacional e garantir latencias imperceptiveis de scroll, os chats e seus metadados de visualizacao sao armazenados de forma persistente em um arquivo JSON local na raiz do backend.
|
||||
|
||||
### 2. Tabela de Atribuicao de Chat (`whatsapp_chat_atribuicoes`)
|
||||
O controle de quem esta atendendo qual chat e estritamente transacional e reside no banco PostgreSQL.
|
||||
|
||||
A estrutura da tabela e criada pela migration `004_whatsapp.sql`:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS whatsapp_chat_atribuicoes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
chat_id VARCHAR(255) NOT NULL,
|
||||
user_id SERIAL REFERENCES usuarios(id) ON DELETE CASCADE,
|
||||
area_id SERIAL REFERENCES areas(id) ON DELETE SET NULL,
|
||||
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(chat_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### Regras de Atribuicao:
|
||||
* `chat_id`: Identificador unico da conversa no WhatsApp (ex: `5511999999999@c.us` ou `...@lid`).
|
||||
* `user_id`: ID inteiro (`usuarios.id`) do atendente que assumiu.
|
||||
* `area_id`: ID inteiro (`areas.id`) do setor sob o qual o atendimento esta sendo prestado (ex: `1` para Suporte).
|
||||
|
||||
---
|
||||
|
||||
## Endpoints do Modulo
|
||||
|
||||
Base path: `/whatsapp`
|
||||
|
||||
### 1. Enviar Mensagem
|
||||
```http
|
||||
POST /whatsapp/send
|
||||
Content-Type: application/json
|
||||
```
|
||||
**Payload**:
|
||||
```json
|
||||
{
|
||||
"to": "5511999999999@c.us",
|
||||
"message": "Ola, tudo bem?",
|
||||
"media": {
|
||||
"data": "base64String...",
|
||||
"mimetype": "image/png",
|
||||
"filename": "comprovante.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Atribuir Chat
|
||||
```http
|
||||
POST /whatsapp/assign
|
||||
Content-Type: application/json
|
||||
```
|
||||
**Payload**:
|
||||
```json
|
||||
{
|
||||
"chatId": "5511999999999@c.us",
|
||||
"userId": 4,
|
||||
"areaId": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Liberar Chat
|
||||
```http
|
||||
DELETE /whatsapp/release/:chatId
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Limitacoes e Solucoes de Bugs
|
||||
|
||||
### 1. Payload Too Large (Upload de Midia)
|
||||
Para permitir o envio de imagens, videos e audios pesados codificados em Base64, o limite padrao de payload do NestJS foi estendido para **50MB** no `main.ts` tanto para formato JSON quanto para `urlencoded`.
|
||||
|
||||
### 2. Nomes Numericos do WhatsApp (Smart Name Resolution)
|
||||
Devido a latencias do WhatsApp Web, as mensagens recebidas as vezes reportam apenas o telefone/JID como nome do contato. O servico de WhatsApp contem uma camada reativa de reparacao automatica:
|
||||
* Checa se o nome e puramente numerico.
|
||||
* Se for, faz uma chamada em background para o Puppeteer (`client.getContactById`) para obter o `notifyName` real do cliente e atualizar o cache local.
|
||||
|
||||
---
|
||||
|
||||
## Como Rodar e Testar
|
||||
|
||||
### Requisitos locais
|
||||
* Node.js v18+
|
||||
* PostgreSQL ativo com as migrations executadas
|
||||
* Google Chrome ou Chromium instalado (o Puppeteer tentara usar o bundle local se omitido)
|
||||
|
||||
### Executando em desenvolvimento
|
||||
```bash
|
||||
cd backend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Os logs do NestJS no terminal indicarao as fases de inicializacao do Puppeteer, geracao de QR Code ou recuperacao de sessao ativa.
|
||||
Loading…
Reference in New Issue
Block a user