# Para quem desenvolve

> Como ligar um sistema à Central de Identidade para login (OpenID Connect) e como usar a API
> da central para gerir pessoas.

A Central de Identidade é um provedor **OpenID Connect** (OIDC): o padrão aberto de login que
fica por cima do OAuth2. Em vez de guardar senhas, o seu sistema manda a pessoa para a central,
ela entra lá, e o sistema recebe de volta quem ela é. Qualquer biblioteca cliente de OIDC da sua
linguagem serve; esta página diz o que a central espera e onde ela foge do comum.

**Antes de integrar, leia também o [Checklist de integração](/docs/integration.md)**: o que o
seu sistema precisa fazer, além do protocolo.

## Antes de começar: o client

Para a central aceitar o seu sistema, ele precisa de um **client**: o cadastro do sistema na
central, com um identificador (`client_id`). Quem cadastra é um administrador global. Leve para
ele:

- **Tipo**: **confidencial**, se o sistema guarda um segredo no servidor (o caso comum de um
  backend), ou **público**, se não tem onde guardar (um app que roda só no navegador ou no
  celular). Essa escolha não muda depois.
- **Redirect URIs**: os endereços para onde a central devolve a pessoa depois do login.
- **Post-logout redirect URIs**, se o sistema for usar a saída pela central (veja
  [Sair](#sair)).
- **Scopes** de que o sistema precisa (veja [Quem é a pessoa](#quem-é-a-pessoa-scopes-e-claims)).
- **Grants**: `authorization_code` e `refresh_token` para login; `client_credentials` só se o
  sistema for chamar a API em nome próprio (veja [API](#api)).

Um client confidencial recebe um **secret**, a senha do sistema. **Ele aparece uma vez só**, para
o administrador, no momento do cadastro: guarde na hora, num cofre de segredos.

## Login

### Endpoints do login

| O quê | Caminho |
|---|---|
| Discovery | `GET /.well-known/openid-configuration` |
| Autorização | `GET /authorize` |
| Token | `POST /token` |
| UserInfo | `GET` ou `POST /userinfo` |
| Chaves públicas (JWKS) | `GET /jwks` |
| Saída | `GET /end-session` |

O documento de discovery tem esses endereços e o `issuer`, mas **não lista os scopes e claims**:
eles estão na seção [Quem é a pessoa](#quem-é-a-pessoa-scopes-e-claims), abaixo.

### O fluxo

A central só aceita o fluxo **authorization code com PKCE**, e o PKCE é **obrigatório para todo
client, inclusive os confidenciais**. PKCE é a prova de que quem troca o código pelo token é o
mesmo sistema que pediu o login: o sistema gera um segredo aleatório (`code_verifier`), manda o
hash dele no pedido de login (`code_challenge`) e o segredo original na troca.

1. O sistema manda a pessoa para `/authorize`:

   ```
   https://home.vetorzero.com.br/authorize
     ?response_type=code
     &client_id=meu-sistema
     &redirect_uri=https://meu-sistema.exemplo.com.br/callback
     &scope=openid profile email
     &state=<aleatório>
     &nonce=<aleatório>
     &code_challenge=<BASE64URL(SHA256(code_verifier))>
     &code_challenge_method=S256
   ```

   **Mande `code_challenge_method=S256` sempre.** Sem ele, a biblioteca do servidor assume
   `plain`, que a central recusa.

2. A pessoa entra com a conta Google (ou passa direto, se já tiver entrado) e volta para a
   `redirect_uri` com `code` e `state`. **Não existe tela de consentimento**: todos os sistemas
   ligados à central são da própria empresa.

3. O sistema troca o código por tokens:

   ```sh
   curl -X POST "https://home.vetorzero.com.br/token" \
     -u 'meu-sistema:SECRET' \
     -d grant_type=authorization_code \
     -d code=CODE \
     -d redirect_uri=https://meu-sistema.exemplo.com.br/callback \
     -d code_verifier=CODE_VERIFIER
   ```

   Um client confidencial se autentica com HTTP Basic (como acima) ou com `client_id` e
   `client_secret` no corpo. Um client público manda só o `client_id` no corpo.

4. A resposta traz `access_token`, `id_token` e `refresh_token`. O `id_token` já tem as claims
   dos scopes pedidos; confira nele o `nonce` que você mandou.

### Tokens

| Token | Validade |
|---|---|
| Código de autorização | 10 minutos |
| Access token | 1 hora |
| ID token | 1 hora |
| Refresh token | 1 mês |

- O access token e o ID token são **JWT assinados com RS256**. As chaves públicas estão em
  `/jwks`.
- **Cada refresh token vale uma vez só**: ao usá-lo, você recebe um novo e o antigo é revogado.
  Guarde sempre o último.
- A central **não tem endpoint de revogação nem de introspecção** de token.

### Regras de redirect URI

- Tem que ser `https`. A exceção é `http` em `localhost` ou `127.0.0.1`, para desenvolvimento.
- A comparação é **exata**, caractere por caractere. Em `http://127.0.0.1` a porta é ignorada;
  em `http://localhost`, **a porta precisa bater**.

## Quem é a pessoa: scopes e claims

Cada scope libera um conjunto de claims (os dados da pessoa). O scope `openid` entra sempre.

| Scope | Claim | Tipo | O que é |
|---|---|---|---|
| `openid` | `sub` | string | Identificador da pessoa, como `vlidc_usr_…` |
| `profile` | `name` | string | Nome |
| `profile` | `picture` | string ou null | Foto da conta Google principal |
| `email` | `email` | string | Email principal |
| `email` | `email_verified` | bool | Se o email principal foi confirmado |
| `verified_emails` | `verified_emails` | string[] | Todos os emails confirmados |
| `employment` | `employer` | string ou null | Domínio da empresa que contratou a pessoa; null para freelancer |
| `employment` | `served_organizations` | string[] | Domínios das empresas que ela atende |
| `employment` | `job_title` | string ou null | Cargo |
| `employment` | `department` | string ou null | Departamento |

As claims vêm no ID token e também em `/userinfo`.

**Identifique a pessoa pelo `sub`, nunca pelo email.** Isso e o resto do que o seu sistema
precisa fazer com essas claims, incluindo como associar usuários que já existiam, estão no
[Checklist de integração](/docs/integration.md).

### /userinfo

`/userinfo` devolve as mesmas claims a partir de um access token. O token vai **só no header**
`Authorization: Bearer <token>`, tanto em `GET` quanto em `POST`; não há parâmetro no corpo.

```sh
curl "https://home.vetorzero.com.br/userinfo" -H "Authorization: Bearer ACCESS_TOKEN"
```

Um token de `client_credentials` é recusado aqui (403): ele não representa uma pessoa.

## Sair

**Sair da central não tira a pessoa do seu sistema**, e sair do seu sistema não tira ela da
central. Cada lado tem a sua sessão.

Para encerrar também a sessão na central, mande a pessoa para `/end-session`, com os parâmetros
na query string:

```
https://home.vetorzero.com.br/end-session
  ?id_token_hint=<o id_token da pessoa>
  &post_logout_redirect_uri=https://meu-sistema.exemplo.com.br/
  &state=<opcional>
```

- A `post_logout_redirect_uri` precisa estar cadastrada no client. Ela também exige
  `id_token_hint` ou `client_id`.
- Com `id_token_hint` válido, a saída é direta. Sem ele, a central pode mostrar uma tela de
  confirmação antes.

## Pessoas desativadas

Quando um administrador desativa alguém, a central revoga os tokens dessa pessoa: `/userinfo` e
o refresh passam a recusar. **Um JWT que o seu sistema valida sozinho, só com a chave pública,
continua válido até expirar** (no máximo 1 hora). Se o seu sistema precisa reagir na hora,
consulte `/userinfo` ou faça refresh em vez de confiar só na validade do JWT.

## API

A API em `/api/v1` permite que outro sistema gerencie pessoas: listar, consultar, cadastrar,
editar, desativar e cuidar de emails. A especificação completa, em OpenAPI, está em [`/api/doc`](https://home.vetorzero.com.br/api/doc)
(para ler no navegador) e em [`/api/doc.json`](https://home.vetorzero.com.br/api/doc.json).

### Acesso

Toda chamada leva um access token no header `Authorization: Bearer`. Há dois jeitos de obter um:

- **Em nome do próprio sistema** (`client_credentials`): o sistema age como ele mesmo, com os
  papéis que o client recebeu. Exige client confidencial.

  ```sh
  curl -X POST "https://home.vetorzero.com.br/token" -u 'meu-sistema:SECRET' -d grant_type=client_credentials
  ```

- **Em nome de uma pessoa**: o access token do login dela. A chamada vale com os papéis da
  pessoa.

Nos dois casos, **o client precisa ter "Acesso à API" liberado** por um administrador global,
que também define os IPs de onde ele pode chamar (sem nenhum IP, qualquer um é aceito). Todos os
endpoints de hoje exigem o papel de administrador de pessoas. As chamadas ficam registradas na
auditoria da central, com o client de origem.

**O bearer é o access token, não o secret.** Mandar o secret no lugar dá 401.

### Endpoints da API

| Método | Caminho | O quê |
|---|---|---|
| `GET` | `/api/v1/users?limit=&offset=` | Lista pessoas |
| `POST` | `/api/v1/users` | Cadastra uma pessoa (`name`, `email`) |
| `GET` | `/api/v1/users/{id}` | Consulta uma pessoa |
| `PUT` | `/api/v1/users/{id}/profile` | Troca nome e email |
| `PUT` | `/api/v1/users/{id}/employment` | Troca empregador, empresas atendidas, cargo e departamento |
| `POST` | `/api/v1/users/{id}/emails` | Adiciona um email (a pessoa confirma pelo link) |
| `DELETE` | `/api/v1/users/{id}/emails/{address}` | Remove um email |
| `POST` | `/api/v1/users/{id}/deactivate` | Desativa |
| `POST` | `/api/v1/users/{id}/reactivate` | Reativa |

O `{id}` é o mesmo `sub` do login. Empresas são identificadas pelo domínio. Uma pessoa
cadastrada pela API recebe o convite por email, como no cadastro pela tela, e não há exclusão de
pessoa: o equivalente é desativar.

### Convenções

- **Os nomes da API estão em inglês** (campos, mensagens de erro).
- **Não há `PATCH`.** O `PUT` manda o estado inteiro do recurso: toda chave é obrigatória,
  inclusive as que aceitam `null`. Chave faltando dá erro.
- **`Idempotency-Key`** (opcional, só em `POST`): repetir a mesma chamada com a mesma chave
  devolve a resposta guardada, com o header `Idempotent-Replayed: true`, em vez de repetir o
  efeito. Só respostas 2xx são guardadas, por pelo menos 7 dias. A mesma chave com outro corpo dá
  422; uma chamada enquanto a primeira ainda roda dá 409.
- **Paginação**: `limit` de 1 a 100 (padrão 50) e `offset`. A resposta traz `items`, `total` e
  os links `next` e `prev`.
- **Erros** seguem o RFC 9457 (`application/problem+json`), com `title`, `status` e, quando
  ajuda, `detail`. Erro de validação é 422, com a lista em `violations`; recurso inexistente é
  404; conflito (como email já usado) é 409.

## Quando algo dá errado

**`invalid_request` "PKCE is required"** no `/authorize`: faltou o `code_challenge`. Se ele foi,
confira o `code_challenge_method=S256`.

**`invalid_client` no `/token`**: secret errado, client desativado ou, no `client_credentials`,
chamada de um IP fora da lista do client.

**Erro no `/authorize` logo de cara, sem passar pelo login**: a `redirect_uri` provavelmente não bate
exatamente com a cadastrada. Atenção à barra no
fim e à porta do `localhost`.

**`access_denied` no `/authorize` durante um teste**: o administrador está impersonando outra
pessoa na central. Impersonando, a central não deixa entrar em nenhum sistema.

**403 em `/userinfo`**: o token é de `client_credentials`, que não representa uma pessoa.

**401 na API**: falta o bearer, o bearer é o secret em vez do access token, ou o client está
desativado, sem acesso à API ou chamando de um IP não liberado. O `detail` diz qual.
