@dutodev/cli 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -11
- package/dist/index.js +3 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,25 +51,47 @@ Todos os túneis sobem juntos, numa única tela. A CLI procura o arquivo na past
|
|
|
51
51
|
|
|
52
52
|
### Host e cabeçalhos
|
|
53
53
|
|
|
54
|
-
Por padrão, o serviço local recebe
|
|
54
|
+
Por padrão, o serviço local recebe a requisição como ela chegou da internet: `Host: web-<usuário>.duto.dev`, `X-Forwarded-Proto: https` e os cabeçalhos da Cloudflare (`CF-Connecting-IP` e outros). Na maioria dos apps isso basta. Quando o app decide algo pelo domínio, há duas ferramentas por túnel:
|
|
55
55
|
|
|
56
56
|
```yaml
|
|
57
57
|
tunnels:
|
|
58
58
|
loja:
|
|
59
59
|
port: 8080
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
port: 8080 # mesma porta: vale quando cada túnel tem um host
|
|
63
|
-
host: painel.meuapp.test
|
|
64
|
-
headers:
|
|
65
|
-
X-Ambiente: tunel # acrescenta ou sobrescreve
|
|
60
|
+
headers: # acrescenta ou sobrescreve cabeçalhos
|
|
61
|
+
X-Forwarded-Host: loja.meuapp.test
|
|
66
62
|
X-Debug: "" # valor vazio remove o cabeçalho
|
|
63
|
+
painel:
|
|
64
|
+
port: 8081
|
|
65
|
+
host: painel.meuapp.test # troca o Host que o serviço recebe
|
|
67
66
|
```
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
**Qual usar:**
|
|
69
|
+
|
|
70
|
+
| Situação | Use | Por quê |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| Um proxy reverso local (Traefik, Nginx, Caddy) escolhe o serviço pelo Host | `host:` | O proxy só conhece os domínios locais |
|
|
73
|
+
| Vários túneis na mesma porta, um por domínio | `host:` ou `headers:` diferentes em cada um | A porta pode se repetir; cada túnel tem o próprio endereço |
|
|
74
|
+
| O app identifica o domínio ou o cliente pelo `X-Forwarded-Host`, mas monta URLs a partir do `Host` | `headers:` com `X-Forwarded-Host` | O app vê o domínio local e continua gerando links públicos |
|
|
75
|
+
| Login OAuth em que o app calcula o `redirect_uri` sozinho | evite `host:`; tente `headers:` | Veja abaixo |
|
|
76
|
+
|
|
77
|
+
**O que o `host:` faz:**
|
|
78
|
+
|
|
79
|
+
- O serviço recebe o Host informado, e o `X-Forwarded-Host` não é enviado. Para enviá-lo assim mesmo, coloque-o em `headers:`.
|
|
80
|
+
- Os redirecionamentos voltam com o endereço público. Um `Location: http://painel.meuapp.test:8081/login` chega ao navegador como `https://painel-<usuário>.duto.dev/login`. Isso vale também dentro de parâmetros codificados no `Location`. Para desligar, use `rewrite_redirects: false` no túnel.
|
|
81
|
+
- Links no corpo das respostas (HTML, JSON) não são reescritos. URLs que o app montar a partir do Host só abrem na sua máquina.
|
|
82
|
+
- Na linha de comando: `duto 8081 --host painel.meuapp.test`, e `--no-rewrite-redirects` para desligar a troca.
|
|
83
|
+
|
|
84
|
+
**OAuth e `host:`.** No fluxo de autorização por código, o app manda um `redirect_uri` duas vezes: na ida do navegador ao provedor de login e depois, servidor a servidor, ao trocar o código por token. O provedor exige que as duas sejam idênticas. Com `host:`, o app gera as duas com o domínio local. O Duto corrige a primeira, que passa pelo navegador, mas não alcança a segunda, que não passa pelo túnel. O provedor recusa a troca, em geral com `invalid_request` ou `invalid_grant`, e o app costuma voltar ao login em loop (`ERR_TOO_MANY_REDIRECTS`).
|
|
85
|
+
|
|
86
|
+
Saídas, da mais simples para a mais trabalhosa:
|
|
87
|
+
|
|
88
|
+
1. Não troque o Host. O app gera o `redirect_uri` com o endereço público nas duas vezes.
|
|
89
|
+
2. Se o app precisa ver o domínio local para outras decisões e lê o `X-Forwarded-Host`, passe o domínio por `headers:` e mantenha o Host público.
|
|
90
|
+
3. Configure no app a URL pública de retorno, se ele permitir.
|
|
91
|
+
|
|
92
|
+
Em todos os casos, cadastre `https://<nome>-<usuário>.duto.dev/<caminho de retorno>` como URL de retorno no provedor. A comparação costuma ser exata, sem curingas: cada pessoa do time tem o próprio endereço.
|
|
93
|
+
|
|
94
|
+
**Sobre o `X-Forwarded-*`.** `X-Forwarded-Host`, `X-Forwarded-Proto` e `X-Forwarded-For` são uma convenção antiga entre proxies reversos para contar ao app o que o cliente pediu de verdade. A versão padronizada é o cabeçalho `Forwarded` (RFC 7239), menos usado na prática. Frameworks costumam ler esses cabeçalhos só quando o proxy é marcado como confiável (`trusted proxies`, `trust proxy`, `USE_X_FORWARDED_HOST`), então o efeito de `headers:` depende de como o app está configurado. Por outro lado, se um app lê o `X-Forwarded-Host` sem essa checagem, qualquer cliente pode forjá-lo em produção. Vale garantir que o proxy da frente sobrescreva ou remova esse cabeçalho.
|
|
73
95
|
|
|
74
96
|
## A tela
|
|
75
97
|
|
package/dist/index.js
CHANGED
|
@@ -263,7 +263,6 @@ function parseConfig(text, file = CONFIG_FILES[0]) {
|
|
|
263
263
|
throw fail3('declare os t\xFAneis em "tunnels:", um por linha (ex.: web: 3000).');
|
|
264
264
|
}
|
|
265
265
|
const specs = [];
|
|
266
|
-
const seen = /* @__PURE__ */ new Map();
|
|
267
266
|
for (const pair of tunnels.items) {
|
|
268
267
|
const raw = isScalar(pair.key) ? String(pair.key.value) : "";
|
|
269
268
|
const line = lineAt(pair.key?.range?.[0]);
|
|
@@ -317,16 +316,7 @@ function parseConfig(text, file = CONFIG_FILES[0]) {
|
|
|
317
316
|
if (value === false) spec.rewriteRedirects = false;
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
|
-
const users = seen.get(port) ?? [];
|
|
321
|
-
const clash = users.find((u) => !u.host || !spec.host || u.host === spec.host);
|
|
322
|
-
if (clash) {
|
|
323
|
-
throw fail3(
|
|
324
|
-
clash.host && clash.host === spec.host ? `a porta ${port} com host ${spec.host} aparece em "${clash.raw}" e em "${raw}"` : `a porta ${port} aparece em "${clash.raw}" e em "${raw}". Para usar a mesma porta, d\xEA um host a cada um.`,
|
|
325
|
-
line
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
319
|
if (specs.some((s) => s.name === name)) throw fail3(`o nome "${raw}" aparece duas vezes`, line);
|
|
329
|
-
seen.set(port, [...users, { raw, host: spec.host }]);
|
|
330
320
|
specs.push(spec);
|
|
331
321
|
}
|
|
332
322
|
return specs;
|
|
@@ -920,9 +910,9 @@ async function runSession(specs, source, flags) {
|
|
|
920
910
|
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname(), cloudflaredPath, source });
|
|
921
911
|
if (wantsUi(flags)) {
|
|
922
912
|
const { runFullscreen } = await import("./fullscreen-5437X7MP.js");
|
|
923
|
-
process.exit(await runFullscreen(session, specs, "0.4.
|
|
913
|
+
process.exit(await runFullscreen(session, specs, "0.4.2"));
|
|
924
914
|
}
|
|
925
|
-
console.log(`${mark()} ${paint("muted", `${"0.4.
|
|
915
|
+
console.log(`${mark()} ${paint("muted", `${"0.4.2"} \xB7 ${source} \xB7 Ctrl+C para encerrar`)}`);
|
|
926
916
|
const stopLog = runPlain(session, (l) => console.log(l));
|
|
927
917
|
const stopped = new Promise((resolve) => {
|
|
928
918
|
const onSignal = () => {
|
|
@@ -1009,7 +999,7 @@ cli.help(
|
|
|
1009
999
|
body: s.body.replace(/^\s*For more info, run any command with the `--help` flag:/m, " Mais detalhes de um comando: duto <comando> --help").replace("Display this message", "Mostra esta ajuda").replace("Display version number", "Mostra a vers\xE3o")
|
|
1010
1000
|
}))
|
|
1011
1001
|
);
|
|
1012
|
-
cli.version("0.4.
|
|
1002
|
+
cli.version("0.4.2");
|
|
1013
1003
|
try {
|
|
1014
1004
|
cli.parse(process.argv, { run: false });
|
|
1015
1005
|
await cli.runMatchedCommand();
|