Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ CA_CLIENT_SECRET=your-client-secret-here
# CA_SCOPE="openid profile aws.cognito.signin.user.admin"

# URI de redirecionamento OAuth2
# Sem TLS (padrão, se Conta Azul aceitar localhost):
# CA_REDIRECT_URI=http://localhost:9876/callback
# Com TLS via mkcert (necessário: a Conta Azul exige HTTPS e domínio, e recusa localhost):
# O default já é o valor que o portal da Conta Azul aceita (HTTPS + domínio;
# localhost em http:// é recusado). "ca auth login" emite o certificado TLS
# em ~/.config/conta-azul-cli/certs/ e instala a CA no trust store do usuário.
# CA_REDIRECT_URI=https://conta-azul-cli.ddev.site:9876/callback
#
# NÃO troque o domínio: apesar do nome, isto NÃO depende de DDEV — *.ddev.site é
# só um wildcard DNS público que aponta para 127.0.0.1. Tentamos um nome mais
# neutro (conta-azul-cli.localtest.me, mesma propriedade de DNS) e o portal da
# Conta Azul respondeu erro interno de servidor ao cadastrar. Com ddev.site aceita.

# Certificado TLS local para o servidor de callback
# Gere com: mkcert -cert-file .certs/cert.pem -key-file .certs/key.pem conta-azul-cli.ddev.site
# Resolve para 127.0.0.1 via DNS público, sem /etc/hosts e sem container
# CA_CALLBACK_CERT=/path/to/.certs/cert.pem
# CA_CALLBACK_KEY=/path/to/.certs/key.pem
# Override do certificado TLS do callback. Sem isto, o CLI gera o par sozinho.
# CA_CALLBACK_CERT=/path/to/cert.pem
# CA_CALLBACK_KEY=/path/to/key.pem

# Endpoint de autorização (padrão: {CA_AUTH_BASE_URL}/oauth2/authorize)
# O default serve produção e sandbox — normalmente não há o que mexer aqui.
Expand Down
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ no [contrato de saída](docs/guia/contrato-de-saida.md).

## [Unreleased]

## [0.19.3] - 2026-08-26

### Fixed

- **`auth login` deixou de depender de `.certs/` num checkout local.** O
Homebrew instala o PHAR, não o repositório, então numa máquina nova
`brew install` não bastava para reautenticar: o callback HTTPS procurava
certificados que só existiam ao lado de `bin/`. O comando agora emite uma
CA e um certificado em `~/.config/conta-azul-cli/certs/` e instala a CA no
trust store do usuário. O default compilado de `CA_REDIRECT_URI` passa a
ser `https://conta-azul-cli.ddev.site:9876/callback`, que é o valor que o
portal da Conta Azul aceita. `CA_CALLBACK_CERT` / `CA_CALLBACK_KEY`
continuam como override; caminhos apontando para um checkout que não
existe nesta máquina são ignorados.

## [0.19.2] - 2026-08-26

### Fixed
Expand Down Expand Up @@ -1021,7 +1036,8 @@ Primeira versão tagueada.
- Pacote renomeado de `contaazul-cli/cli` para `heitoralthmann/conta-azul-cli`,
com aviso de não-oficialidade adicionado ao README.

[Unreleased]: https://github.com/heitoralthmann/conta-azul-cli/compare/v0.19.2...HEAD
[Unreleased]: https://github.com/heitoralthmann/conta-azul-cli/compare/v0.19.3...HEAD
[0.19.3]: https://github.com/heitoralthmann/conta-azul-cli/compare/v0.19.2...v0.19.3
[0.19.2]: https://github.com/heitoralthmann/conta-azul-cli/compare/v0.19.1...v0.19.2
[0.19.1]: https://github.com/heitoralthmann/conta-azul-cli/compare/v0.19.0...v0.19.1
[0.19.0]: https://github.com/heitoralthmann/conta-azul-cli/compare/v0.18.0...v0.19.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ O `.env` e o `tokens.json` nascem `0600`, em diretório `0700` — em Unix. **No

O arquivo de ambiente é procurado nesta ordem, e **o primeiro que existir vence, sem mesclagem**: `CA_CLI_ENV_FILE` → `<raiz do repo>/.env` → `~/.config/conta-azul-cli/.env`. `ca config path` responde qual está valendo e por quê. A precedência geral é: **flag de CLI → variável de ambiente → arquivo → default compilado**. Em produção, use variáveis de ambiente. `.env` e `tokens.json` **nunca** devem ser versionados.

O provedor da Conta Azul **recusa `redirect_uri` em `http://localhost`**: exige HTTPS e um domínio real. A receita completa — incluindo por que o domínio precisa ser `*.ddev.site` e por que `CA_AUTHORIZE_URL` e `CA_TOKEN_URL` andam em par — está em [Configuração](docs/guia/configuracao.md).
O provedor da Conta Azul **recusa `redirect_uri` em `http://localhost`**: exige HTTPS e um domínio real. O default compilado já é `https://conta-azul-cli.ddev.site:9876/callback`, e `ca auth login` emite o certificado TLS em `~/.config/conta-azul-cli/certs/` — sem checkout e sem `mkcert`. A receita completa — incluindo por que o domínio precisa ser `*.ddev.site` e por que `CA_AUTHORIZE_URL` e `CA_TOKEN_URL` andam em par — está em [Configuração](docs/guia/configuracao.md).

## Autenticação

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.19.2
0.19.3
9 changes: 5 additions & 4 deletions composer-dependency-analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
// ci.yml's Windows extensions comment).
$config->ignoreErrorsOnExtension('ext-posix', [ErrorType::SHADOW_DEPENDENCY]);

// ext-mbstring and ext-openssl are required by Symfony components internally
// (Console formatting, HTTPS transport) without this codebase calling their
// functions directly, so static usage scanning can't see the need.
$config->ignoreErrorsOnExtensions(['ext-mbstring', 'ext-openssl'], [ErrorType::UNUSED_DEPENDENCY]);
// ext-mbstring is required by Symfony components internally (Console
// formatting) without this codebase calling its functions directly, so
// static usage scanning can't see the need. ext-openssl is called from
// LocalCertificateAuthority for the login callback certs.
$config->ignoreErrorsOnExtensions(['ext-mbstring'], [ErrorType::UNUSED_DEPENDENCY]);

return $config;
2 changes: 1 addition & 1 deletion docs/_data/commands/auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sections:
- 'auth login'
status: verified
body: |
Sem parâmetros. Imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Tokens vão para `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.
Sem parâmetros. Imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Na primeira vez emite o certificado em `~/.config/conta-azul-cli/certs/` e instala a CA no trust store do usuário. Tokens vão para `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.

O refresh é automático e invisível — não existe comando para isso. Renovação preventiva a menos de 60 s da expiração, e reativa uma vez em caso de `401`.
-
Expand Down
2 changes: 1 addition & 1 deletion docs/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -2273,5 +2273,5 @@
"version"
],
"output_default": "toon",
"version": "0.19.2"
"version": "0.19.3"
}
6 changes: 3 additions & 3 deletions docs/desenvolvimento/especificacao.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ CLI em PHP/Symfony que expõe endpoints das famílias Financeiro (Finanças, Bai
### 11.1. Fluxo inicial — `ca auth login`

1. CLI gera `state` aleatório.
2. CLI inicia listener HTTP local em `http://localhost:9876/callback` (porta fixa, pré-registrada no app na Conta Azul).
2. CLI inicia listener HTTPS local em `https://conta-azul-cli.ddev.site:9876/callback` (porta fixa, pré-registrada no app na Conta Azul). O certificado TLS é emitido em `~/.config/conta-azul-cli/certs/` na primeira vez, e a CA entra no trust store do usuário.
3. CLI imprime a URL de autorização e pede ao usuário que abra em um navegador.
4. Usuário completa login no IdP (AWS Cognito, por trás da Conta Azul).
5. O redirect captura `code`; CLI valida `state` e troca por tokens via `POST https://auth.contaazul.com/oauth2/token` com `Authorization: Basic base64(client_id:client_secret)`.
6. CLI persiste tokens; listener encerra; processo sai com `0`.

**Justificativa.** Loopback local é o padrão estabelecido para OAuth em CLIs e funciona em qualquer máquina dev com navegador. PHP tem suporte nativo via `stream_socket_server`, dispensando dependências adicionais.
**Justificativa.** Loopback local é o padrão estabelecido para OAuth em CLIs e funciona em qualquer máquina com navegador. O provedor recusa `http://localhost` e exige HTTPS com domínio; `*.ddev.site` já resolve para `127.0.0.1` via DNS público. PHP gera o par TLS com a extensão `openssl` (já requisito) e instala a CA no trust store do usuário, então um `brew install` numa máquina nova basta para reautenticar — sem checkout, sem `.certs/`, sem `mkcert`.

**Trade-off aceito.** Porta fixa (`9876`). Se já estiver ocupada, o login falha com mensagem clara em pt-BR. Aceitável.

Expand Down Expand Up @@ -273,7 +273,7 @@ Operador roda `ca auth login` uma vez em máquina dev, depois copia o refresh to
|---|---|---|
| `CA_CLIENT_ID` | sim | client_id do app Conta Azul |
| `CA_CLIENT_SECRET` | sim | client_secret do app |
| `CA_REDIRECT_URI` | não | default `http://localhost:9876/callback` |
| `CA_REDIRECT_URI` | não | default `https://conta-azul-cli.ddev.site:9876/callback` |
| `CA_API_BASE_URL` | não | default `https://api-v2.contaazul.com` |
| `CA_AUTH_BASE_URL` | não | default `https://auth.contaazul.com` |
| `CA_CLI_TOKEN_PATH` | não | default `~/.config/conta-azul-cli/tokens.json` |
Expand Down
2 changes: 1 addition & 1 deletion docs/guia/autenticacao.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
ca auth login
```

O comando imprime uma URL, sobe um listener local na porta **9876** e aguarda o redirect. Abra a URL no navegador, complete o login, e os tokens são gravados em `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.
O comando imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Na primeira vez, emite um certificado em `~/.config/conta-azul-cli/certs/` e instala a CA no trust store do usuário — no macOS isso pode pedir a senha da conta, uma vez. Abra a URL no navegador, complete o login, e os tokens são gravados em `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.

O arquivo guarda o **refresh token**, que é a credencial de longa duração. O `0600` vale em Unix; no Windows o PHP não escreve bits de permissão e a proteção fica por conta das ACLs do perfil do usuário — o detalhe está em [Permissões dos arquivos](configuracao.md#permissoes).

Expand Down
30 changes: 12 additions & 18 deletions docs/guia/configuracao.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ cp .env.example .env
|---|---|---|
| `CA_CLIENT_ID` | sim | — |
| `CA_CLIENT_SECRET` | sim | — |
| `CA_REDIRECT_URI` | não | `http://localhost:9876/callback` |
| `CA_REDIRECT_URI` | não | `https://conta-azul-cli.ddev.site:9876/callback` |
| `CA_SCOPE` | não | omitido da requisição |
| `CA_CALLBACK_CERT` | não | — |
| `CA_CALLBACK_KEY` | não | — |
Expand All @@ -74,14 +74,15 @@ cp .env.example .env

## Permissões dos arquivos { #permissoes }

O CLI grava dois arquivos que carregam credencial:
O CLI grava arquivos que carregam credencial:

| Arquivo | O que guarda |
|---|---|
| `~/.config/conta-azul-cli/.env` | `CA_CLIENT_SECRET` — e o `CA_BOOTSTRAP_REFRESH_TOKEN`, se você o definir ali |
| `~/.config/conta-azul-cli/tokens.json` | O access token e o **refresh token** do OAuth, a credencial de longa duração |
| `~/.config/conta-azul-cli/certs/` | A CA e a chave privada do certificado TLS do callback, gerados por `ca auth login` |

Nos dois casos o CLI cria o diretório com `0700` e o arquivo com `0600`, e **reaplica a permissão a cada escrita**, não só na criação. Em Unix isso significa o que promete: nenhum outro usuário da máquina lê esses arquivos.
Em todos esses caminhos o CLI cria o diretório com `0700` e o arquivo com `0600`, e **reaplica a permissão a cada escrita**, não só na criação. Em Unix isso significa o que promete: nenhum outro usuário da máquina lê esses arquivos.

> **No Windows essa garantia não existe.** O `chmod` do PHP naquela plataforma só liga e desliga o atributo de somente-leitura — ele não escreve bits de modo POSIX, porque o sistema de arquivos não os tem. O código chama `chmod` em todas as plataformas e está correto; o que não existe no Windows é o efeito. Quem protege os arquivos ali são as ACLs do próprio perfil do usuário (`C:\Users\<você>`), que por padrão já barram os demais usuários locais.
>
Expand Down Expand Up @@ -118,23 +119,16 @@ Na prática, só mexa nessas variáveis se a Conta Azul mudar os endpoints — e

## Callback OAuth com HTTPS

O provedor da Conta Azul **recusa `redirect_uri` em `http://localhost`**: exige HTTPS e um domínio real. A saída é usar `mkcert` com um domínio que resolve para `127.0.0.1` via DNS público — `*.ddev.site` — sem mexer em `/etc/hosts`.
O provedor da Conta Azul **recusa `redirect_uri` em `http://localhost`**: exige HTTPS e um domínio real. O default compilado é esse valor:

> **Não altere esse domínio.** O nome sugere uma dependência de DDEV que **não existe**: o projeto não usa DDEV, e `*.ddev.site` é apenas um wildcard DNS público apontando para `127.0.0.1`. A escolha é imposta pelo provedor — já tentamos trocar por um nome mais neutro e não funcionou. Ao registrar a aplicação com `conta-azul-cli.localtest.me`, que tem exatamente a mesma propriedade de DNS, o portal da Conta Azul respondeu **erro interno de servidor** e recusou o cadastro; com `ddev.site` aceitou. O critério de validação de domínio deles não é documentado, então vale o valor que funciona.

```bash
brew install mkcert
mkcert -install
mkdir -p .certs
mkcert -cert-file .certs/cert.pem -key-file .certs/key.pem conta-azul-cli.ddev.site
```
https://conta-azul-cli.ddev.site:9876/callback
```

E no `.env`:
`ca auth login` emite sozinho o certificado TLS em `~/.config/conta-azul-cli/certs/` e instala a CA no trust store do usuário (no macOS, o login keychain — pode pedir a senha da conta uma vez). Não depende de um checkout, de `.certs/` na raiz do repositório, nem de `mkcert`. É o que torna `brew install` + `ca auth login` suficiente numa máquina nova.

```bash
CA_REDIRECT_URI=https://conta-azul-cli.ddev.site:9876/callback
CA_CALLBACK_CERT=/caminho/absoluto/.certs/cert.pem
CA_CALLBACK_KEY=/caminho/absoluto/.certs/key.pem
```
> **Não altere esse domínio.** O nome sugere uma dependência de DDEV que **não existe**: o projeto não usa DDEV, e `*.ddev.site` é apenas um wildcard DNS público apontando para `127.0.0.1`. A escolha é imposta pelo provedor — já tentamos trocar por um nome mais neutro e não funcionou. Ao registrar a aplicação com `conta-azul-cli.localtest.me`, que tem exatamente a mesma propriedade de DNS, o portal da Conta Azul respondeu **erro interno de servidor** e recusou o cadastro; com `ddev.site` aceitou. O critério de validação de domínio deles não é documentado, então vale o valor que funciona.

Registre exatamente esse `redirect_uri` no painel do app na Conta Azul.

Registre exatamente esse `redirect_uri` no painel do app na Conta Azul. Quando `CA_CALLBACK_CERT` e `CA_CALLBACK_KEY` estão presentes, o servidor de callback abre um socket TLS; sem elas, ele cai no modo `http://` simples.
`CA_CALLBACK_CERT` e `CA_CALLBACK_KEY` continuam valendo como override: se os dois arquivos existirem, o CLI usa-os e não mexe no trust store. Caminhos copiados de um checkout antigo que não existem nesta máquina são ignorados, e o CLI cai no par gerado. Sem as duas variáveis, o servidor de callback abre um socket TLS com o certificado gerado; um `CA_REDIRECT_URI` em `http://` (não o default) cai no modo `http://` simples.
8 changes: 5 additions & 3 deletions docs/guia/instalacao.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,11 @@ ca auth login
```

O `ca auth login` só completa depois de o `redirect_uri` estar registrado no
painel do app — e o provedor da Conta Azul **recusa `http://localhost`**, o
default compilado. A receita com `mkcert`, e o detalhe de cada variável, estão
em [Configuração](configuracao.md).
painel do app — o default compilado já é
`https://conta-azul-cli.ddev.site:9876/callback`, que é o valor que o provedor
aceita. O próprio login emite o certificado TLS e instala a CA no trust store
do usuário; não é preciso clonar o repositório nem rodar `mkcert`. O detalhe
de cada variável está em [Configuração](configuracao.md).

## Clone + Composer

Expand Down
2 changes: 1 addition & 1 deletion docs/guia/solucao-de-problemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ Não é um erro do CLI: a autenticação funcionou e a chamada chegou à API. A

**Erro ao subir o servidor de callback** — a porta 9876 está ocupada. Libere-a; ela é fixa porque precisa bater com o `redirect_uri` registrado no app.

**Navegador acusa certificado inválido no callback** — rode `mkcert -install` para instalar a CA local no trust store do sistema.
**Navegador acusa certificado inválido no callback** — a CA local ainda não está no trust store. Rode `ca auth login` de novo e aceite o diálogo do sistema; se isso não aparecer, confie manualmente em `~/.config/conta-azul-cli/certs/ca.pem` (no macOS, Keychain Access; no Windows, `certutil -user -addstore Root`).
2 changes: 1 addition & 1 deletion docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ O arquivo é reinterpretado aqui em vez de lido de volta por `getenv()`: a essa

## `auth login` ✅ { #auth-login }

Sem parâmetros. Imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Tokens vão para `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.
Sem parâmetros. Imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Na primeira vez emite o certificado em `~/.config/conta-azul-cli/certs/` e instala a CA no trust store do usuário. Tokens vão para `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.

O refresh é automático e invisível — não existe comando para isso. Renovação preventiva a menos de 60 s da expiração, e reativa uma vez em caso de `401`.

Expand Down
2 changes: 1 addition & 1 deletion docs/referencia/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## `auth login` ✅ { #auth-login }

Sem parâmetros. Imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Tokens vão para `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.
Sem parâmetros. Imprime uma URL, sobe um listener HTTPS local na porta **9876** e aguarda o redirect. Na primeira vez emite o certificado em `~/.config/conta-azul-cli/certs/` e instala a CA no trust store do usuário. Tokens vão para `~/.config/conta-azul-cli/tokens.json` com permissão `0600`.

O refresh é automático e invisível — não existe comando para isso. Renovação preventiva a menos de 60 s da expiração, e reativa uma vez em caso de `401`.

Expand Down
Loading
Loading