# OAuth 2.0 — Authorization Code + PKCE

> Use OAuth 2.0 quando o **usuário final** precisa autorizar explicitamente
> o acesso de um aplicativo terceiro (ex: software de contabilidade que
> acessa dados de vários clientes). Para integrações máquina-a-máquina,
> prefira **Service Account** (veja [AUTH.md](./AUTH.md)).

## Quando usar

| Cenário | Use |
|---|---|
| Integração própria (ERP, PDV, e-commerce) com a sua **própria** empresa | **Service Account** |
| Software de terceiros (contador, BI, etc.) que precisa acessar **várias empresas** com consentimento | **OAuth 2.0** |
| Mobile app ou SPA que age em nome do usuário | **OAuth 2.0** |

## Fluxo visual

```
┌──────────────┐                                  ┌──────────────┐
│  Seu App     │                                  │  Délfica     │
│  (terceiro)  │                                  │  + Usuário   │
└──────┬───────┘                                  └──────┬───────┘
       │                                                  │
       │ 1. Redireciona o usuário para                   │
       │    /oauth/authorize?...                         │
       │   (client_id, redirect_uri, scope, state,       │
       │    code_challenge, code_challenge_method=S256)  │
       │─────────────────────────────────────────────────▶
       │                                                  │
       │                                       2. Mostra tela
       │                                          de consent
       │                                          (/oauth/consent)
       │                                          usuário aprova
       │                                                  │
       │ 3. Redirect para redirect_uri?code=XYZ&state=...│
       │◀─────────────────────────────────────────────────
       │                                                  │
       │ 4. POST /oauth/token                            │
       │    (code, code_verifier, redirect_uri)          │
       │─────────────────────────────────────────────────▶
       │                                                  │
       │                                  5. access_token  │
       │                                  + refresh_token │
       │                                  + expires_in    │
       │◀─────────────────────────────────────────────────
       │                                                  │
       │ 6. GET /api/v1/...                              │
       │    Authorization: Bearer <access_token>         │
       │─────────────────────────────────────────────────▶
       │                                                  │
       │ 7. POST /oauth/token                            │
       │    (grant_type=refresh_token, refresh_token)     │
       │─────────────────────────────────────────────────▶
```

## Passo 1 — Registrar o aplicativo

Acesse `$HOST/developers/console` (autenticado como desenvolvedor da
respectiva conta de serviço master) e cadastre um **OAuth Client**:

| Campo | Descrição |
|---|---|
| `name` | Nome que aparece no consent screen. |
| `redirect_uris` | Lista de URIs válidas para `redirect_uri`. HTTPS obrigatório fora de dev. |
| `scopes` | Escopos que o app vai poder pedir. Limite-se ao mínimo necessário. |
| `token_ttl_seconds` | (Opcional) TTL do `access_token` (padrão: 3600s). |
| `refresh_ttl_seconds` | (Opcional) TTL do `refresh_token` (padrão: 30 dias). |

Após salvar, você recebe um `client_id` e um `client_secret`. Guarde o
`client_secret` em cofre — ele não é mostrado novamente.

## Passo 2 — Redirecionar o usuário

```python
import secrets
import hashlib
import base64

CLIENT_ID = "oc_xxxxxxxxxxxxxxxx"
REDIRECT_URI = "https://seu-app.com/oauth/callback"
SCOPES = "read:invoices read:customers"

# PKCE: gere o code_verifier e seu challenge (S256)
code_verifier = secrets.token_urlsafe(64)[:128]
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()

state = secrets.token_urlsafe(16)
# Persista `state` e `code_verifier` na sessão do usuário (cookie, KV, etc.)

authorize_url = (
    f"https://api.delfica.com.br/oauth/authorize"
    f"?response_type=code"
    f"&client_id={CLIENT_ID}"
    f"&redirect_uri={REDIRECT_URI}"
    f"&scope={SCOPES}"
    f"&state={state}"
    f"&code_challenge={challenge}"
    f"&code_challenge_method=S256"
)
# Redirecione o navegador para authorize_url
```

## Passo 3 — Trocar `code` por tokens

O usuário aprova no `/oauth/consent` e o navegador é redirecionado para:

```
https://seu-app.com/oauth/callback?code=ABC...&state=...
```

Valide que o `state` recebido coincide com o que você persistiu. Em seguida:

```bash
curl -X POST https://api.delfica.com.br/oauth/token \
  -u "oc_xxxxxxxxxxxxxxxx:SEU_CLIENT_SECRET" \
  -d "grant_type=authorization_code" \
  -d "code=ABC..." \
  -d "redirect_uri=https://seu-app.com/oauth/callback" \
  -d "code_verifier=$CODE_VERIFIER"
```

Resposta:

```json
{
  "access_token": "at_...",
  "refresh_token": "rt_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:invoices read:customers"
}
```

## Passo 4 — Chamar a API

```bash
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://api.delfica.com.br/api/v1/invoices
```

> O `access_token` carrega os escopos concedidos. Se você pediu
> `read:invoices` mas o usuário só aprovou `read:customers`, a chamada
> retornará `403 insufficient_scope`.

## Refresh

Quando o `access_token` expirar (ou estiver perto — recomendado: 60s antes):

```bash
curl -X POST https://api.delfica.com.br/oauth/token \
  -u "oc_xxxxxxxxxxxxxxxx:SEU_CLIENT_SECRET" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=$REFRESH_TOKEN"
```

## Revogação

O usuário pode revogar o acesso em **Configurações → Aplicativos conectados**.
Programaticamente, apps podem chamar:

```bash
curl -X POST https://api.delfica.com.br/oauth/revoke \
  -u "oc_xxxxxxxxxxxxxxxx:SEU_CLIENT_SECRET" \
  -d "token=$REFRESH_TOKEN"
```

`refresh_token` revogado invalida **toda a cadeia** (todos os
`access_token` descendentes param de funcionar).

## Segurança

- **`state`**: sempre envie e valide. Impede CSRF no fluxo.
- **PKCE**: obrigatório. Não use `code_challenge_method=plain`.
- **HTTPS**: `redirect_uri` precisa ser HTTPS, exceto `http://localhost` em dev.
- **`client_secret`**: nunca no front-end. Use-o apenas no backend.
- **Escopos sensíveis**: `write:*` e `use:ai` mostram badge **"Sensível"**
  (âmbar) no consent screen. `admin:*` exigem **Service Account** e
  não estão disponíveis via OAuth.
- **Rotação de `client_secret`**: faça a cada 90 dias. O console
  (`/developers/console`) emite novo + revoga o antigo em uma operação.

## Tabela de scopes OAuth vs Service Account

| Escopo | OAuth (com consent) | Service Account |
|---|---|---|
| `read:*` | sim | sim |
| `write:customers` | sim | sim |
| `write:products` | sim | sim |
| `write:services` | sim | sim |
| `write:nfe` | sim (sensível) | sim |
| `write:nfse` | sim (sensível) | sim |
| `write:financial` | sim (sensível) | sim |
| `write:webhooks` | sim | sim |
| `use:ai` | sim (sensível) | sim |
| `admin:markup` | **não** | sim (super-admin) |
| `admin:*` | **não** | sim (super-admin) |

> O consent screen (`/oauth/consent`) é público (não exige login prévio
> do usuário da Délfica) e mostra a lista de escopos com badge de
> sensibilidade. Scopes `write:*` e `use:ai` exigem aprovação explícita.
