# API SSO — Banco de Talentos ↔ Conta Angra

Documentação completa da integração entre o **Banco de Talentos** (Laravel 9) e o serviço de identidade **Conta Angra** (OAuth 2 Passport).

---

## Sumário

1. [Visão geral](#1-visão-geral)
2. [Variáveis de ambiente](#2-variáveis-de-ambiente)
3. [Fluxos de autenticação](#3-fluxos-de-autenticação)
4. [Endpoints — Serviço de Identidade (AUTH_SERVICE_URL)](#4-endpoints--serviço-de-identidade-auth_service_url)
5. [Endpoints — SSO Popup (Conta Angra)](#5-endpoints--sso-popup-conta-angra)
6. [Endpoints — UserService (gestão de usuários)](#6-endpoints--userservice-gestão-de-usuários)
7. [Sincronização para o banco Conta Angra](#7-sincronização-para-o-banco-conta-angra)
8. [Middleware de autenticação](#8-middleware-de-autenticação)
9. [Sessão SSO — SsoWebSession](#9-sessão-sso--ssowebsession)
10. [Circuit Breaker](#10-circuit-breaker)
11. [Tratamento de erros HTTP](#11-tratamento-de-erros-http)
12. [Rotas web relacionadas à autenticação](#12-rotas-web-relacionadas-à-autenticação)

---

## 1. Visão geral

O Banco de Talentos utiliza **dois mecanismos de autenticação SSO**:

| Mecanismo | Variável base | Quando é usado |
|-----------|--------------|----------------|
| **OAuth Password Grant** | `AUTH_SERVICE_URL` | Login e cadastro com CPF/CNPJ + senha via formulário próprio do BT |
| **SSO Popup** | `ANGRA_SSO_LOGIN_URL` / `ANGRA_SSO_EXCHANGE_URL` | Login via popup do portal Angra (sem senha no BT) |

Em ambos os casos, após autenticação bem-sucedida, o BT cria ou atualiza o registro local de `candidates` ou `companies` e persiste um token na sessão PHP.

---

## 2. Variáveis de ambiente

Arquivo: `.env`

```env
# ── Serviço de Identidade OAuth (Conta Angra) ──────────────────────────
AUTH_SERVICE_URL=https://angra.rj.gov.br/api   # Base URL da API de identidade
APP_ID=banco-de-talentos                        # Identificador do app enviado no header App:
PASSPORT_CLIENT_ID=                             # Client ID do Passport OAuth
PASSPORT_SECRET=                                # Client Secret do Passport OAuth

# ── SSO Popup ────────────────────────────────────────────────────────────
ANGRA_SSO_LOGIN_URL=https://angra.rj.gov.br/login          # URL do portal para abrir popup
ANGRA_SSO_EXCHANGE_URL=https://angra.rj.gov.br/api/sso/exchange  # Endpoint de troca de code
ANGRA_SSO_ORIGIN=https://angra.rj.gov.br                   # Origem permitida no postMessage
ANGRA_SSO_SECRET=                                           # Secret compartilhado para exchange

# ── Banco de dados Conta Angra (escrita direta para sync empresa) ────────
ANGRA_DB_HOST=127.0.0.1
ANGRA_DB_PORT=3306
ANGRA_DB_DATABASE=laravel_site
ANGRA_DB_USERNAME=root
ANGRA_DB_PASSWORD=
ANGRA_STRUCTURE_DEFAULT_ID=546177b8-2c1e-4338-9545-d48588de5bce

# ── HTTP client ──────────────────────────────────────────────────────────
TALENT_BANK_HTTP_TIMEOUT=20         # Timeout em segundos
TALENT_BANK_HTTP_RETRY=2            # Nº de retentativas (só em ConnectionException)
TALENT_BANK_HTTP_RETRY_MS=350       # Intervalo entre retentativas

# ── Flags de desenvolvimento ─────────────────────────────────────────────
TALENT_BANK_BYPASS_AUTH=false       # true = pula validação de token (dev only)
TALENT_BANK_LOCAL_REGISTER=false    # true = cadastro sem chamar SSO externo
TALENT_BANK_INACTIVITY_TIMEOUT_MINUTES=60
```

### Headers comuns enviados a AUTH_SERVICE_URL

Todos os requests para o serviço de identidade incluem:

```
Accept: application/json
App: {APP_ID}
Authorization: Bearer {access_token}   (quando autenticado)
X-Correlation-ID: {uuid}              (quando presente no request atual)
```

Quando `AUTH_SERVICE_URL` aponta para `localhost` ou `127.0.0.1`, o proxy HTTP é desabilitado (`proxy: ''`) para evitar interceptação por proxies corporativos (Squid, etc.).

---

## 3. Fluxos de autenticação

### 3.1 Login com formulário (OAuth Password Grant)

```
Browser → POST /entrar → AuthWebController::login()
  → AuthService::login()  →  POST {AUTH_SERVICE_URL}/api/login
  ← 200 { access_token, data: { document, name, email, permissions } }
  → SsoWebSession::storeTokens($payload)
  → Se empresa não existe no BT: UserService::selfAssignPermission() + companyService::store()
  → Se candidato não existe no BT: UserService::selfAssignPermission() + candidateService::store()
  → AuthService::session()  →  GET {AUTH_SERVICE_URL}/api/session   (refresh payload)
  → redirect → /area-empresa  ou  /perfil
```

### 3.2 Cadastro de candidato

```
Browser → POST /cadastro/candidato → AuthWebController::registerCandidate()
  → AuthService::register()  →  POST {AUTH_SERVICE_URL}/api/user
      { document, username, password, email, name, user_type_id:2,
        permissions:["talent-bank-user-candidate"], module_name }
  ← 200 { access_token, ... }
  → candidateService::store()   (cria registro local em candidates)
  → Mail::queue(WelcomeCandidateMail)
  → this->login($loginRequest)   (faz login automático)
```

### 3.3 Cadastro de empresa

```
Browser → POST /cadastro/empresa → AuthWebController::registerCompany()
  → AuthService::register()  →  POST {AUTH_SERVICE_URL}/api/user
      { document, username, password, email, name, user_type_id:2,
        permissions:["talent-bank-user-company"], module_name }
  ← 200 { access_token, ... }
  → companyService::store()   (cria registro local em companies)
  → AngraCompanySyncService::syncToAngra()   (escrita direta no banco mysql_angra)
  → this->login($loginRequest)   (faz login automático)
```

### 3.4 SSO Popup (Conta Angra)

```
Browser → clica "Entrar com Conta Angra"
  → Abre popup: {ANGRA_SSO_LOGIN_URL}
  → Usuário faz login no portal Angra
  → Portal faz postMessage({ code, state }) para o BT
  → JS captura e chama POST /entrar/sso-callback

AuthWebController::ssoCallback()
  → POST {ANGRA_SSO_EXCHANGE_URL}
      { code, secret: ANGRA_SSO_SECRET }
  ← 200 { cpf, name, email, phone }
  → AngraSsoProvisioningService::syncCandidate() ou syncCompany()
     (cria/atualiza registro local sem chamar AUTH_SERVICE_URL)
  → SsoWebSession::bootstrapAngraSsoUser(doc, name, email, permission)
     (sessão sintética — sem token OAuth real)
  ← JSON { success: true, redirect_url }
  → Browser redireciona para /perfil ou /area-empresa
```

---

## 4. Endpoints — Serviço de Identidade (AUTH_SERVICE_URL)

Base URL configurada em `AUTH_SERVICE_URL`. Todos os requests são feitos pelo `AuthService` com o client HTTP descrito na seção 2.

---

### 4.1 Login

```
POST {AUTH_SERVICE_URL}/api/login
```

**Body (JSON):**

```json
{
  "grant_type": "password",
  "client_id": "{PASSPORT_CLIENT_ID}",
  "client_secret": "{PASSPORT_SECRET}",
  "username": "12345678901",
  "password": "senha123",
  "scope": ""
}
```

- `username`: CPF (11 dígitos, sem máscara) ou CNPJ (14 dígitos, sem máscara)

**Resposta 200:**

```json
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "data": {
    "id": 42,
    "document": "12345678901",
    "name": "Fulano de Tal",
    "email": "fulano@example.com",
    "permissions": {
      "siati": ["talent-bank-user-candidate"]
    }
  }
}
```

**Erros:**

| Status | Significado |
|--------|-------------|
| 401 | Credenciais inválidas |
| 429 | Rate limit atingido (registra falha no circuit breaker) |
| 5xx | Erro no servidor SSO (registra falha no circuit breaker) |
| 503 | Circuit breaker aberto (retornado localmente, sem chamar SSO) |

**Usado por:** `AuthWebController::login()`

---

### 4.2 Sessão (validação de token)

```
GET {AUTH_SERVICE_URL}/api/session
Authorization: Bearer {access_token}
```

**Resposta 200:**

```json
{
  "data": {
    "id": 42,
    "document": "12345678901",
    "name": "Fulano de Tal",
    "email": "fulano@example.com",
    "permissions": {
      "siati": ["talent-bank-user-candidate"]
    }
  }
}
```

**Erros:**

| Status | Significado |
|--------|-------------|
| 401 | Token inválido ou expirado |
| 503 | Serviço indisponível |

**Usado por:**
- `Middleware\AuthSSO` — valida cada request de API autenticado
- `Middleware\AuthSSOWeb` — valida cada request web autenticado
- `AuthWebController::refreshSsoUserPayload()` — atualiza payload de sessão pós-login

---

### 4.3 Cadastro de usuário

```
POST {AUTH_SERVICE_URL}/api/user
Content-Type: application/json
```

**Body (JSON):**

```json
{
  "document": "12345678901",
  "username": "12345678901",
  "password": "senha123",
  "password_confirmation": "senha123",
  "email": "fulano@example.com",
  "email_confirmation": "fulano@example.com",
  "name": "Fulano de Tal",
  "user_type_id": 2,
  "first_access": 0,
  "permissions": ["talent-bank-user-candidate"],
  "module_name": "Banco de Talentos"
}
```

- Para empresa: `"permissions": ["talent-bank-user-company"]` e `document` com 14 dígitos (CNPJ)
- Campos `null` ou `""` são omitidos antes do envio

**Resposta 200:** Igual ao login (retorna `access_token` + `data`)

**Erros:**

| Status | Estrutura da resposta | Significado |
|--------|----------------------|-------------|
| 422 | `{ "errors": { "document": ["..."] } }` | Validação: documento/email duplicado, senha fraca |
| 5xx | HTML ou JSON `{ message }` | Erro no servidor SSO |

O método `AuthService::validationErrorsFromRegisterResponse()` normaliza todas as variações de resposta de erro (Laravel `errors`, `data.errors`, `message`, `error_description`, HTML do Squid) para um array compatível com `withErrors()`.

**Usado por:** `AuthWebController::registerCandidate()` e `registerCompany()`

---

### 4.4 Logout

```
POST {AUTH_SERVICE_URL}/api/logout
Authorization: Bearer {access_token}
```

**Resposta 200:**

```json
{ "message": "Logged out successfully" }
```

Falhas de rede são silenciadas — a sessão local é sempre destruída independentemente.

**Não chamado** quando a sessão é sintética (SSO popup ou modo dev local).

**Usado por:** `AuthWebController::logout()` e `logoutCompleto()`

---

### 4.5 Recuperação de senha — enviar e-mail

```
POST {AUTH_SERVICE_URL}/api/password/email
Content-Type: application/json

{ "email": "fulano@example.com" }
```

**Usado por:** `PasswordResetWebController::sendResetEmail()`

---

### 4.6 Recuperação de senha — validar token

```
POST {AUTH_SERVICE_URL}/api/password/token-validator
Content-Type: application/json

{
  "token": "abc123...",
  "email": "fulano@example.com"
}
```

**Usado por:** `PasswordResetWebController::showReset()` (via GET `/redefinir-senha?token=...`)

---

### 4.7 Recuperação de senha — redefinir

```
POST {AUTH_SERVICE_URL}/api/password/reset
Content-Type: application/json

{
  "token": "abc123...",
  "email": "fulano@example.com",
  "password": "novaSenha123",
  "password_confirmation": "novaSenha123"
}
```

**Usado por:** `PasswordResetWebController::reset()`

---

## 5. Endpoints — SSO Popup (Conta Angra)

### 5.1 Abertura do popup

O frontend abre uma popup para:

```
{ANGRA_SSO_LOGIN_URL}
```

Após o usuário se autenticar no portal Angra, o portal dispara um `postMessage` para a janela pai com:

```json
{ "code": "authorization_code_aqui", "state": "state_token_aqui" }
```

O JavaScript do BT valida a origem (`ANGRA_SSO_ORIGIN`) antes de processar a mensagem.

---

### 5.2 Troca de code por dados do usuário (exchange)

```
POST {ANGRA_SSO_EXCHANGE_URL}
Content-Type: application/json
Accept: application/json
X-Correlation-ID: {uuid}   (opcional)
```

**Body:**

```json
{
  "code": "authorization_code_aqui",
  "secret": "{ANGRA_SSO_SECRET}"
}
```

**Resposta 200:**

```json
{
  "cpf": "12345678901",
  "name": "Fulano de Tal",
  "email": "fulano@example.com",
  "phone": "(21) 98888-7777"
}
```

- `cpf`: pode conter CPF (11 dígitos) ou CNPJ (14 dígitos)
- `phone`: pode ser vazio

**Erros:**

| Status | Significado no BT |
|--------|------------------|
| 401 | Secret inválido → `angra_sso.exchange_unauthorized` |
| 404 | Code não encontrado ou expirado → `angra_sso.exchange_not_found` |
| 5xx | Falha no servidor → `angra_sso.exchange_failed` |
| ConnectionException | Timeout/rede → 503 `angra_sso.exchange_failed` |

**Chamado por:** `AuthWebController::ssoCallback()` (rota `POST /entrar/sso-callback`)

---

### 5.3 Rota callback no BT

```
POST /entrar/sso-callback
Content-Type: application/json

{ "code": "...", "state": "..." }
```

**Resposta 200 (sucesso):**

```json
{
  "success": true,
  "redirect_url": "https://banco-de-talentos.test/perfil"
}
```

**Resposta de erro:**

```json
{ "error": "mensagem traduzida" }
```

Com status HTTP correspondente (401, 404, 422, 500, 503).

---

## 6. Endpoints — UserService (gestão de usuários)

Classe `App\Http\Services\UserService`. Todos usam `Authorization: Bearer {token}` e a base `AUTH_SERVICE_URL`.

| Método | Endpoint | Descrição |
|--------|----------|-----------|
| GET | `/api/user/?{query}` | Listar usuários com filtros |
| GET | `/api/user/{id}` | Buscar usuário por ID (header `App: siati`) |
| PUT | `/api/user/{id}` | Atualizar usuário |
| DELETE | `/api/user/{id}` | Remover usuário |
| PUT | `/api/user/{id}/permission` | Atualizar permissões do usuário |
| GET | `/api/user/{id}/reset/passwordDefault` | Resetar para senha padrão |
| POST | `/api/user/modifyPassword` | Alterar senha (primeiro acesso) |
| PUT | `/api/user/{id}/status` | Ativar/desativar usuário |
| POST | `/api/user/{id}/sync-permissions` | Sincronizar permissões |
| GET | `/api/user/public/?{query}` | Dados públicos de usuários |
| GET | `/api/user/document/{document}` | Buscar por CPF/CNPJ |
| POST | `/api/user/permission/self-assign` | Usuário se auto-atribui permissão |
| POST | `/api/user/self-edit` | Usuário edita os próprios dados |

---

### 6.1 Self-assign de permissão

Chamado no login de candidato/empresa que ainda não existe no BT, para garantir a permissão correta antes de provisionar o registro local.

```
POST {AUTH_SERVICE_URL}/api/user/permission/self-assign
Authorization: Bearer {access_token}
Content-Type: application/json

{ "permissions": ["talent-bank-user-candidate"] }
```

ou

```json
{ "permissions": ["talent-bank-user-company"] }
```

---

### 6.2 Self-edit (edição de perfil)

Suporta upload de foto via multipart:

```
POST {AUTH_SERVICE_URL}/api/user/self-edit
Authorization: Bearer {access_token}
Content-Type: multipart/form-data

photo=<file>
name=Novo Nome
email=novo@email.com
```

Ou sem foto:

```
POST {AUTH_SERVICE_URL}/api/user/self-edit
Content-Type: application/json

{ "name": "Novo Nome", "email": "novo@email.com" }
```

**Usado por:** `CandidateWebController::selfEdit()`, `CompanyAreaWebController::selfEdit()`

---

## 7. Sincronização para o banco Conta Angra

Quando uma **empresa** se cadastra no BT, o sistema faz escrita direta no banco de dados `mysql_angra` (conexão separada configurada em `ANGRA_DB_*`) para provisionar o acesso no portal Angra.

Classe: `App\Http\Services\TalentBank\AngraCompanySyncService`

### 7.1 Tabelas afetadas

**`users`** — cria conta de acesso:

```sql
INSERT INTO users (
  name,          -- razão social da empresa
  email,         -- e-mail de contato
  password,      -- Hash::make($senha) — bcrypt
  cpf,           -- CNPJ da empresa (armazenado neste campo)
  matriculation, -- CNPJ
  status,        -- 1 (ativo)
  isPasswordDefault,   -- false
  isServidorPublico,   -- false
  isWhatsapp,          -- false
  created_at,
  updated_at
)
```

**`permissoes`** — concede 3 permissões padrão:
- `GERENCIADOR_DE_CONTA_ANGRA`
- `GERENCIADOR_DE_LINHAS_DO_TEMPO`
- `GUEST`

**`empresas`** — cria registro mínimo de empresa.

**`enderecos`** — cria endereço padrão vazio.

**`solicitantes_conta_empresa`** — registra a solicitação de criação via BT com `structure_id = ANGRA_STRUCTURE_DEFAULT_ID`.

### 7.2 Quando é executado

- `POST /cadastro/empresa` → após cadastro bem-sucedido no SSO e no BT
- Executado fora da transação principal (falhas são silenciadas com log)

---

## 8. Middleware de autenticação

### 8.1 `AuthSSO` (API)

Classe: `App\Http\Middleware\AuthSSO`
Rotas: `/api/talent-bank/*` (exceto endpoints públicos)

```
Request → AuthSSO::handle()
  → Se TALENT_BANK_BYPASS_AUTH=true: $request->user = usuário fictício, next()
  → AuthService::session($request)  →  GET /api/session
  → Se 200: $request->user = payload['data'], $request->talentBankUser = Model
  → Se não 200: abort(status_http_do_sso)
```

### 8.2 `AuthSSOWeb` (Web)

Classe: `App\Http\Middleware\AuthSSOWeb`
Rotas: Todas as rotas protegidas do web.php

```
Request → AuthSSOWeb::handle()
  → Se TALENT_BANK_BYPASS_AUTH=true: injeta usuário fictício, next()
  → Se sessão local (__talent_bank_local_session__): usa session('sso.user_payload'), next()
  → Se sessão Angra SSO (__angra_sso_session__): usa session('sso.user_payload'), next()
  → Se token OAuth real: AuthService::session() → GET /api/session
      → Se 200: atualiza sessão, next()
      → Se 401/403: SsoWebSession::forget(), redirect → /entrar
      → Se 5xx: redirect → /entrar com erro de serviço
```

### 8.3 `OptionalAuthSSOWeb` (Web opcional)

Classe: `App\Http\Middleware\OptionalAuthSSOWeb`
Rotas: `/vagas/{job}` (acesso público ou autenticado)

Igual ao `AuthSSOWeb`, mas em caso de falha/ausência de token, simplesmente chama `next()` sem redirecionar.

### 8.4 `TalentBankWeb`

Classe: `App\Http\Middleware\TalentBankWeb`

Após autenticação, resolve o model Eloquent do usuário (`Candidate` ou `Company`) baseado na permissão e injeta em `$request->talentBankUser`.

---

## 9. Sessão SSO — SsoWebSession

Classe: `App\Support\SsoWebSession`

Chaves de sessão utilizadas:

| Chave | Conteúdo |
|-------|----------|
| `sso.access_token` | Token Bearer (real ou flag sintético) |
| `sso.token_type` | `"Bearer"` |
| `sso.user_payload` | Array completo da resposta do SSO (inclui `data.document`, `data.name`, `data.email`, `data.permissions`) |
| `sso.profile_photo_path` | Path relativo da foto de perfil (empresas) |

### Tipos de sessão

| Valor de `sso.access_token` | Tipo | Chama /api/session? |
|-----------------------------|------|---------------------|
| `__talent_bank_local_session__` | Dev local (sem SSO) | Não |
| `__angra_sso_session__` | SSO Popup | Não |
| Token JWT real | OAuth Passport | Sim (a cada request via middleware) |

---

## 10. Circuit Breaker

Classe: `App\Support\TalentBank\IntegrationCircuitBreaker`
Integração protegida: `auth_service`

| Parâmetro | Padrão | Config |
|-----------|--------|--------|
| Threshold de falhas | 5 | `talent_bank.circuit_breaker.failure_threshold` |
| Janela de contagem | 60s | `talent_bank.circuit_breaker.window_seconds` |
| Tempo aberto | 120s | `talent_bank.circuit_breaker.open_seconds` |

**Conta como falha:** respostas 5xx, 429, `ConnectionException`

**Conta como sucesso:** qualquer resposta que não seja 5xx/429

**Comportamento com circuito aberto:** retorna imediatamente `HTTP 503 { "message": "Serviço de autenticação temporariamente indisponível." }` sem fazer HTTP call.

---

## 11. Tratamento de erros HTTP

### Erros de rede

- `ConnectionException` → retry automático até `TALENT_BANK_HTTP_RETRY` vezes com `TALENT_BANK_HTTP_RETRY_MS` de intervalo
- Após esgotar retries → exceção propagada → circuit breaker registra falha

### Proxy Squid

Se o body da resposta contém a string `squid` ou `<!doctype`/`<html`, o sistema detecta que um proxy corporativo interceptou a requisição e exibe mensagem orientando a verificar VPN e `AUTH_SERVICE_URL`.

### Normalização de erros 422

`AuthService::validationErrorsFromRegisterResponse()` suporta múltiplos formatos de resposta:

```
{ "errors": { "document": ["..."] } }          → formato Laravel padrão
{ "data": { "errors": { ... } } }              → formato aninhado
{ "data": { "document": ["..."] } }            → formato flat em data
{ "message": "..." }                            → mensagem genérica
{ "error": "..." }                              → campo error
{ "error_description": "..." }                  → OAuth error
{ "detail": "..." }                             → formato alternativo
HTML                                            → mensagem amigável (sem exibir HTML)
```

---

## 12. Rotas web relacionadas à autenticação

| Método | Rota | Controller | Middleware | Descrição |
|--------|------|-----------|-----------|-----------|
| GET | `/entrar` | `AuthWebController::showLogin` | — | Formulário de login |
| POST | `/entrar` | `AuthWebController::login` | — | Submissão do login |
| POST | `/entrar/sso-callback` | `AuthWebController::ssoCallback` | — | Callback do popup SSO |
| GET | `/auth/status` | `AuthWebController::authStatus` | — | Status JSON da sessão (frontend) |
| POST | `/sair` | `AuthWebController::logout` | `sso.session` | Logout completo |
| POST | `/sair/completo` | `AuthWebController::logoutCompleto` | `sso.session` | Logout via JSON (bridge) |
| POST | `/auth/activity` | `AuthWebController::touchActivity` | `auth.sso.web` | Renova timeout de inatividade |
| GET | `/cadastro/candidato` | `AuthWebController::showRegisterCandidate` | — | Formulário de cadastro |
| POST | `/cadastro/candidato` | `AuthWebController::registerCandidate` | — | Submissão cadastro candidato |
| GET | `/cadastro/empresa` | `AuthWebController::showRegisterCompany` | — | Formulário cadastro empresa |
| POST | `/cadastro/empresa` | `AuthWebController::registerCompany` | — | Submissão cadastro empresa |
| GET | `/esqueci-senha` | `PasswordResetWebController::showForgot` | `throttle:password-recovery` | Form esqueci senha |
| POST | `/esqueci-senha` | `PasswordResetWebController::sendResetEmail` | `throttle:password-recovery` | Envia e-mail de reset |
| GET | `/redefinir-senha` | `PasswordResetWebController::showReset` | `throttle:password-recovery` | Form nova senha |
| POST | `/redefinir-senha` | `PasswordResetWebController::reset` | `throttle:password-recovery` | Confirma nova senha |

---

*Gerado em 18/05/2026 — Banco de Talentos Laravel 9*
