Atualizada wiki de autenticação e acesso
parent
e51129a1dd
commit
4a83fb45d1
@ -30,7 +30,8 @@ Frontend
|
|||||||
-> Backend cria/atualiza usuarios_provedores
|
-> Backend cria/atualiza usuarios_provedores
|
||||||
-> Backend consulta usuarios_perfis e usuarios_areas
|
-> Backend consulta usuarios_perfis e usuarios_areas
|
||||||
-> Backend emite JWT com perfis/areas
|
-> Backend emite JWT com perfis/areas
|
||||||
-> Frontend salva authToken/authUser
|
-> Frontend salva authToken/authUser em sessionStorage
|
||||||
|
-> Frontend envia Authorization: Bearer <token> nas rotas privadas
|
||||||
-> Frontend navega para /home
|
-> Frontend navega para /home
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -298,10 +299,21 @@ http://localhost:3001/admin/access/users
|
|||||||
|
|
||||||
### Frontend sem depender de AD
|
### Frontend sem depender de AD
|
||||||
|
|
||||||
No console do navegador:
|
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
|
```js
|
||||||
localStorage.setItem('authUser', JSON.stringify({
|
sessionStorage.setItem('authUser', JSON.stringify({
|
||||||
username: 'admin@sothis.com.br',
|
username: 'admin@sothis.com.br',
|
||||||
name: 'Admin Demo',
|
name: 'Admin Demo',
|
||||||
email: 'admin@sothis.com.br',
|
email: 'admin@sothis.com.br',
|
||||||
@ -310,14 +322,14 @@ localStorage.setItem('authUser', JSON.stringify({
|
|||||||
areas: ['Suporte'],
|
areas: ['Suporte'],
|
||||||
accessStatus: 'assigned'
|
accessStatus: 'assigned'
|
||||||
}));
|
}));
|
||||||
localStorage.setItem('authToken', 'dev-token');
|
sessionStorage.setItem('authToken', 'token-jwt-valido-gerado-pelo-backend');
|
||||||
location.href = '/home';
|
location.href = '/home';
|
||||||
```
|
```
|
||||||
|
|
||||||
Para usuario sem atribuicao:
|
Para usuario sem atribuicao:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
localStorage.setItem('authUser', JSON.stringify({
|
sessionStorage.setItem('authUser', JSON.stringify({
|
||||||
username: 'novo.usuario',
|
username: 'novo.usuario',
|
||||||
name: 'Novo Usuario',
|
name: 'Novo Usuario',
|
||||||
email: 'novo.usuario@sothis.com.br',
|
email: 'novo.usuario@sothis.com.br',
|
||||||
@ -326,7 +338,7 @@ localStorage.setItem('authUser', JSON.stringify({
|
|||||||
areas: [],
|
areas: [],
|
||||||
accessStatus: 'unassigned'
|
accessStatus: 'unassigned'
|
||||||
}));
|
}));
|
||||||
localStorage.setItem('authToken', 'dev-token');
|
sessionStorage.setItem('authToken', 'token-jwt-valido-gerado-pelo-backend');
|
||||||
location.href = '/home';
|
location.href = '/home';
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -334,7 +346,7 @@ location.href = '/home';
|
|||||||
|
|
||||||
## Limitacoes atuais
|
## 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.
|
- 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.
|
- 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.
|
- 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
|
## 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`.
|
- Criar endpoints CRUD completos para `areas`.
|
||||||
- Permitir multiplas areas por usuario na UI, mantendo uma area principal.
|
- Permitir multiplas areas por usuario na UI, mantendo uma area principal.
|
||||||
|
|||||||
413
Auth.md
413
Auth.md
@ -1,155 +1,165 @@
|
|||||||
# Módulo de Autenticação
|
# Modulo de Autenticacao
|
||||||
|
|
||||||
## Visão geral
|
## Visao 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.
|
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:
|
Provedores implementados:
|
||||||
|
|
||||||
- **LDAP / Active Directory** — login com usuário e senha do AD corporativo
|
- **LDAP / Active Directory**: login com usuario e senha corporativos.
|
||||||
- **Microsoft OAuth (Entra ID)** — login via conta Microsoft com redirect OAuth 2.0
|
- **Microsoft OAuth / Entra ID**: login via conta Microsoft.
|
||||||
|
|
||||||
A arquitetura foi desenhada para facilitar a adição de novos provedores no futuro.
|
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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Estrutura de arquivos
|
## Estrutura de arquivos
|
||||||
|
|
||||||
```
|
```text
|
||||||
src/modules/auth/
|
src/modules/auth/
|
||||||
├── auth.module.ts # Registro do módulo no NestJS
|
|-- auth.module.ts
|
||||||
├── auth.controller.ts # Rotas HTTP
|
|-- auth.controller.ts
|
||||||
├── auth.service.ts # Fachada — delega para os providers
|
|-- auth.service.ts
|
||||||
├── auth.config.ts # Leitura de variáveis de ambiente
|
|-- auth.config.ts
|
||||||
├── auth-token.service.ts # Emissão de JWT da aplicação
|
|-- auth-token.service.ts
|
||||||
├── user-access.service.ts # Sincronização do usuário autenticado com o banco
|
|-- auth.types.ts
|
||||||
├── auth.types.ts # Interfaces TypeScript compartilhadas
|
|-- user-access.service.ts
|
||||||
└── providers/
|
|-- decorators/
|
||||||
├── ldap-auth.provider.ts # Autenticação LDAP/AD
|
| |-- public.decorator.ts
|
||||||
├── microsoft-oauth.provider.ts # Autenticação Microsoft OAuth
|
| `-- roles.decorator.ts
|
||||||
└── oauth-state.service.ts # Proteção CSRF para OAuth
|
|-- 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 disponíveis
|
## Rotas publicas
|
||||||
|
|
||||||
| Método | Rota | Descrição |
|
As rotas abaixo nao exigem JWT:
|
||||||
|--------|---------------------------------|------------------------------------------------|
|
|
||||||
| GET | `/auth/config` | Retorna quais provedores estão habilitados |
|
| Metodo | Rota | Descricao |
|
||||||
| POST | `/auth/login` | Login com usuário e senha (LDAP/AD) |
|
|---|---|---|
|
||||||
| GET | `/auth/oauth/microsoft/start` | Inicia o fluxo OAuth com a Microsoft |
|
| GET | `/health` | Health check da API |
|
||||||
| GET | `/auth/oauth/microsoft/callback`| Callback que a Microsoft chama após o login |
|
| 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Variáveis de ambiente
|
## Rotas privadas e perfis
|
||||||
|
|
||||||
```env
|
O `JwtAuthGuard` foi registrado como guard global. Isso significa que toda rota e privada por padrao, exceto as marcadas com `@Public()`.
|
||||||
# Servidor
|
|
||||||
PORT=3001
|
|
||||||
FRONTEND_URL=http://localhost:3000
|
|
||||||
|
|
||||||
# JWT
|
Exemplo:
|
||||||
JWT_SECRET=uma-chave-longa-e-aleatoria
|
|
||||||
JWT_EXPIRES_IN=8h
|
|
||||||
|
|
||||||
# Provedores ativos (separados por vírgula)
|
```typescript
|
||||||
AUTH_PROVIDERS=ldap
|
@Public()
|
||||||
|
@Get('config')
|
||||||
# LDAP / Active Directory
|
getConfig() {}
|
||||||
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`.
|
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
|
## Fluxo LDAP / Active Directory
|
||||||
|
|
||||||
```
|
```text
|
||||||
Frontend
|
Frontend
|
||||||
→ POST /auth/login { username, password }
|
-> POST /auth/login { username, password }
|
||||||
→ AuthController
|
-> LoginDto valida payload
|
||||||
→ AuthService.loginWithLdap()
|
-> AuthController
|
||||||
→ LdapAuthProvider.authenticate()
|
-> AuthService.loginWithLdap()
|
||||||
→ Conecta no servidor AD (LDAP_URL)
|
-> LdapAuthProvider.authenticate()
|
||||||
→ Faz bind com o usuário e senha
|
-> valida se LDAP esta habilitado
|
||||||
→ Se o bind falhar: UnauthorizedException
|
-> conecta no servidor LDAP
|
||||||
→ Busca dados do usuário no diretório (se LDAP_SEARCH_BASE configurado)
|
-> faz bind com usuario e senha
|
||||||
→ Monta objeto AuthenticatedUser
|
-> busca dados do usuario no diretorio, se configurado
|
||||||
→ UserAccessService.syncAuthenticatedUser()
|
-> monta AuthenticatedUser
|
||||||
→ Cria/atualiza usuarios e usuarios_provedores
|
-> UserAccessService.syncAuthenticatedUser()
|
||||||
→ Carrega perfis, areas e area principal
|
-> UserAccessRepository executa SQL
|
||||||
→ AuthTokenService.issueToken()
|
-> cria/atualiza usuarios
|
||||||
→ Gera JWT assinado com JWT_SECRET
|
-> cria/atualiza usuarios_provedores
|
||||||
→ Retorna { token, user } para o frontend
|
-> carrega perfis e areas
|
||||||
|
-> AuthTokenService.issueToken()
|
||||||
|
-> retorna { token, user }
|
||||||
```
|
```
|
||||||
|
|
||||||
O AD apenas valida a identidade. O JWT emitido é da aplicação, não do AD.
|
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
|
## 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
|
||||||
```
|
```
|
||||||
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
|
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.
|
||||||
|
|
||||||
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
|
## JWT da aplicacao
|
||||||
|
|
||||||
O `OAuthStateService` protege o fluxo OAuth contra ataques de CSRF.
|
Payload principal:
|
||||||
|
|
||||||
**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
|
```json
|
||||||
{
|
{
|
||||||
@ -166,134 +176,135 @@ Após qualquer autenticação bem-sucedida, o `AuthTokenService` emite um JWT co
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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 `sub` usa o ID interno da tabela `usuarios`.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sincronização de usuário e acesso
|
## Sessao no frontend
|
||||||
|
|
||||||
Após o provedor autenticar o usuário, o `UserAccessService`:
|
O frontend armazena `authToken` e `authUser` em `sessionStorage`, nao em `localStorage`.
|
||||||
|
|
||||||
1. faz upsert em `usuarios` usando email ou fallback `provider:username`;
|
Motivo:
|
||||||
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.
|
- 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Como adicionar um novo provedor
|
## WebSocket WhatsApp
|
||||||
|
|
||||||
1. Crie o arquivo em `src/modules/auth/providers/novo-provedor.provider.ts`:
|
O namespace Socket.IO `/whatsapp` tambem exige JWT no handshake.
|
||||||
|
|
||||||
```typescript
|
O frontend envia:
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AuthConfigService } from '../auth.config';
|
|
||||||
import { AuthTokenService } from '../auth-token.service';
|
|
||||||
import { AuthResult } from '../auth.types';
|
|
||||||
|
|
||||||
@Injectable()
|
```javascript
|
||||||
export class NovoProvedorProvider {
|
io(WHATSAPP_SOCKET_URL, {
|
||||||
constructor(
|
auth: {
|
||||||
private readonly authConfig: AuthConfigService,
|
token: getAuthToken()
|
||||||
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`:
|
Sem token valido, o socket nao conecta e nao recebe QR Code, status ou mensagens.
|
||||||
|
|
||||||
```typescript
|
---
|
||||||
providers: [
|
|
||||||
AuthConfigService,
|
## Rate limit
|
||||||
AuthService,
|
|
||||||
AuthTokenService,
|
O backend usa `@nestjs/throttler` com limite global configuravel:
|
||||||
LdapAuthProvider,
|
|
||||||
MicrosoftOAuthProvider,
|
```env
|
||||||
OAuthStateService,
|
RATE_LIMIT_TTL_MS=60000
|
||||||
NovoProvedorProvider, // adicione aqui
|
RATE_LIMIT_MAX=300
|
||||||
],
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Injete no `AuthService` e exponha o método necessário:
|
Padrao atual: 300 requisicoes por IP a cada 60 segundos.
|
||||||
|
|
||||||
```typescript
|
---
|
||||||
constructor(
|
|
||||||
private readonly authConfig: AuthConfigService,
|
|
||||||
private readonly ldapAuthProvider: LdapAuthProvider,
|
|
||||||
private readonly microsoftOAuthProvider: MicrosoftOAuthProvider,
|
|
||||||
private readonly novoProvedorProvider: NovoProvedorProvider, // injete aqui
|
|
||||||
) {}
|
|
||||||
|
|
||||||
loginComNovoProvedor(dados: any) {
|
## Variaveis de ambiente
|
||||||
return this.novoProvedorProvider.authenticate(dados);
|
|
||||||
}
|
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Adicione a rota no `AuthController`.
|
`JWT_SECRET` deve ser forte e exclusivo por ambiente.
|
||||||
|
|
||||||
5. Se o provedor precisar de configuração, adicione as variáveis no `AuthConfigService` e no `.env`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Diagnóstico de problemas
|
## Diagnostico
|
||||||
|
|
||||||
### Login LDAP falha com `UnauthorizedException`
|
### Login LDAP falha
|
||||||
|
|
||||||
- Verifique se `LDAP_URL` está acessível a partir do servidor backend
|
- Verifique `LDAP_URL`, `LDAP_DOMAIN` e `LDAP_USER_DN_TEMPLATE`.
|
||||||
- Confirme que `LDAP_DOMAIN` ou `LDAP_USER_DN_TEMPLATE` está correto
|
- Confirme conectividade do backend com o AD.
|
||||||
- Teste a conectividade: `ldapsearch -H ldaps://servidor:636 -x`
|
- Verifique se `LDAP_SEARCH_BASE` e `LDAP_SEARCH_FILTER` batem com o diretorio.
|
||||||
- Verifique `LDAP_TIMEOUT_MS` — servidores lentos podem estar expirando
|
- Consulte logs do backend; o provider registra o motivo interno sem expor para o usuario.
|
||||||
- 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`
|
### Token invalido ou expirado
|
||||||
|
|
||||||
- O state expirou (padrão: 10 minutos). Se o usuário demorou muito na tela da Microsoft, repita o fluxo
|
- Verifique se o frontend esta enviando `Authorization: Bearer <token>`.
|
||||||
- Verifique se `MICROSOFT_REDIRECT_URI` no `.env` é idêntico ao cadastrado no Azure App Registration
|
- Verifique se `JWT_SECRET` nao mudou depois da emissao do token.
|
||||||
- Confirme que `MICROSOFT_CLIENT_ID` e `MICROSOFT_CLIENT_SECRET` estão corretos e não expiraram
|
- Faca login novamente para renovar a sessao.
|
||||||
|
|
||||||
### Token inválido no frontend
|
### Usuario autenticado cai como `unassigned`
|
||||||
|
|
||||||
- Verifique se `JWT_SECRET` não mudou entre deploys — isso invalida todos os tokens emitidos anteriormente
|
- O login funcionou, mas faltam perfil/area no banco.
|
||||||
- Confirme que o frontend está enviando o header `Authorization: Bearer <token>`
|
- Um Admin deve atribuir perfil e area para o usuario.
|
||||||
|
|
||||||
### `GET /auth/config` retorna os provedores errados
|
### Microsoft OAuth falha
|
||||||
|
|
||||||
- Verifique `LDAP_ENABLED` e `MICROSOFT_ENABLED` no `.env`
|
- Verifique `MICROSOFT_REDIRECT_URI` no Azure e no `.env`.
|
||||||
- Reinicie o servidor — variáveis de ambiente são lidas na inicialização
|
- Confirme `MICROSOFT_CLIENT_ID` e `MICROSOFT_CLIENT_SECRET`.
|
||||||
|
- Refaca o fluxo se o state expirar.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## O que ainda falta para produção
|
## Estado atual
|
||||||
|
|
||||||
- [x] Tabela `usuarios` no banco de dados
|
- [x] Login LDAP/AD
|
||||||
- [x] Tabela `usuarios_provedores` para vincular provedores externos ao usuário interno
|
- [x] Login Microsoft OAuth
|
||||||
- [x] `sub` do JWT usando ID interno do banco
|
- [x] State OAuth assinado
|
||||||
- [ ] Guard NestJS para proteger rotas privadas (`@UseGuards(AuthGuard)`)
|
- [x] JWT emitido pela aplicacao
|
||||||
- [ ] Roles e permissões validadas no backend
|
- [x] JWT validado nas rotas privadas
|
||||||
- [ ] Auditoria de login
|
- [x] Roles no backend
|
||||||
- [ ] Trocar token na query string por cookie HTTP-only (reduz exposição no browser)
|
- [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
|
||||||
## Documentacao complementar
|
- [x] DTO e ValidationPipe no login
|
||||||
|
- [x] Rate limit global
|
||||||
A sincronizacao de usuarios autenticados com o banco, os perfis de acesso, as areas operacionais e os endpoints administrativos estao documentados em:
|
- [ ] Auditoria formal de login em tabela propria
|
||||||
|
- [ ] Cookie HttpOnly como alternativa futura ao sessionStorage
|
||||||
- [`access-control.md`](./access-control.md)
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user