Identify the forge before gating, read logs by token only, support Basic-auth proxies #285

Merged
stephen merged 3 commits from feat/spec-parity-compat into feat/spec-parity-pr 2026-10-08 20:22:43 +00:00
Owner

Four fjord-client-spec requirements: two under gitea-compatibility, one
under api-contract (#209) and one under authentication.

Flavor before floor. fj warned "older than Forgejo 7" on any Gitea 1.x
host because it compared the version before knowing what it was talking
to. client::capability now probes /api/forgejo/v1/version and then
/api/v1/version: the first answering is Forgejo, only the second is
Gitea, anything else stays unidentified with its raw version. The floor
applies to Forgejo only. Flavor and version are stored in hosts.toml at
login and refresh and shown by fj auth status.

Capability gates. One table decides whether a route is offered, by
flavor and version, and replaces the ad hoc Forgejo version check.
fj run rerun uses the Gitea 1.26+ routes and is withheld elsewhere;
fj run cancel uses the Forgejo 16 route and is withheld on Gitea and
older Forgejo. A withheld action sends nothing and names the run URL.
On Forgejo, fj run rerun now reports that limitation: the web POST it
used to attempt could never succeed with a token.

Token-only log reads. Job logs are read with Range requests in 1 MiB
chunks up to a 64 MiB ceiling. A server without the log routes gets a
typed limitation with the run URL, or a Loki the user configured. The
token no longer reaches any web route: fj run view, fj run watch and
artifact downloads use the API where the server has it and fall back to
an anonymous web request elsewhere, so on older servers they work for
publicly readable runs only.

Reverse proxies. fj auth proxy stores a separate HTTP Basic credential
for a proxy in front of the forge. A 407 proxy takes it in
Proxy-Authorization. A 401 Basic proxy takes it in Authorization, and
the forge token moves to X-Forge-Authorization, which the proxy has to
forward (one line in nginx or Caddy). --pat-transport query works with
an unmodified proxy at the cost of the token appearing in access logs.
The credential is sent to the configured host only, over HTTPS or
loopback, and neither secret appears in --debug output or errors. Git
over HTTPS behind such a proxy is out of scope; the docs recommend SSH.

Covered by unit and wiremock tests only. Nothing was run against a real Gitea, an older Forgejo, or an nginx or Caddy Basic-auth proxy, and the Gitea version gates come second-hand from the fjord-ios audit document.

Two behavior changes to weigh. Private repositories on Forgejo older than 16 lose run view, run watch and artifact downloads, because those used to work by sending the token to a web route. And fj run rerun on Forgejo now reports a limitation instead of attempting a POST that could not succeed.

DEFAULT_LOKI_URL is still used in one case, when a server that has the log routes reports a single job's log as missing and no Loki variable is set. docs/architecture.md says so.

Part 8 of 8 in the spec-parity stack. Based on feat/spec-parity-browse, so the diff here is this slice alone; it needs a rebase onto main once the PR below it is squash-merged.

Closes #209
Refs #103

Verified on macOS: cargo fmt --all clean, cargo clippy --all-targets --all-features -- -D warnings clean, cargo test --all green (1298 + 5 + 1), pre-push hook passed.

Four fjord-client-spec requirements: two under gitea-compatibility, one under api-contract (#209) and one under authentication. Flavor before floor. fj warned "older than Forgejo 7" on any Gitea 1.x host because it compared the version before knowing what it was talking to. `client::capability` now probes /api/forgejo/v1/version and then /api/v1/version: the first answering is Forgejo, only the second is Gitea, anything else stays unidentified with its raw version. The floor applies to Forgejo only. Flavor and version are stored in hosts.toml at login and refresh and shown by `fj auth status`. Capability gates. One table decides whether a route is offered, by flavor and version, and replaces the ad hoc Forgejo version check. `fj run rerun` uses the Gitea 1.26+ routes and is withheld elsewhere; `fj run cancel` uses the Forgejo 16 route and is withheld on Gitea and older Forgejo. A withheld action sends nothing and names the run URL. On Forgejo, `fj run rerun` now reports that limitation: the web POST it used to attempt could never succeed with a token. Token-only log reads. Job logs are read with Range requests in 1 MiB chunks up to a 64 MiB ceiling. A server without the log routes gets a typed limitation with the run URL, or a Loki the user configured. The token no longer reaches any web route: `fj run view`, `fj run watch` and artifact downloads use the API where the server has it and fall back to an anonymous web request elsewhere, so on older servers they work for publicly readable runs only. Reverse proxies. `fj auth proxy` stores a separate HTTP Basic credential for a proxy in front of the forge. A 407 proxy takes it in Proxy-Authorization. A 401 Basic proxy takes it in Authorization, and the forge token moves to X-Forge-Authorization, which the proxy has to forward (one line in nginx or Caddy). `--pat-transport query` works with an unmodified proxy at the cost of the token appearing in access logs. The credential is sent to the configured host only, over HTTPS or loopback, and neither secret appears in --debug output or errors. Git over HTTPS behind such a proxy is out of scope; the docs recommend SSH. Covered by unit and wiremock tests only. Nothing was run against a real Gitea, an older Forgejo, or an nginx or Caddy Basic-auth proxy, and the Gitea version gates come second-hand from the fjord-ios audit document. Two behavior changes to weigh. Private repositories on Forgejo older than 16 lose `run view`, `run watch` and artifact downloads, because those used to work by sending the token to a web route. And `fj run rerun` on Forgejo now reports a limitation instead of attempting a POST that could not succeed. `DEFAULT_LOKI_URL` is still used in one case, when a server that has the log routes reports a single job's log as missing and no Loki variable is set. `docs/architecture.md` says so. Part 8 of 8 in the spec-parity stack. Based on `feat/spec-parity-browse`, so the diff here is this slice alone; it needs a rebase onto main once the PR below it is squash-merged. Closes #209 Refs #103 Verified on macOS: `cargo fmt --all` clean, `cargo clippy --all-targets --all-features -- -D warnings` clean, `cargo test --all` green (1298 + 5 + 1), pre-push hook passed.
Four fjord-client-spec requirements: two under gitea-compatibility, one
under api-contract (#209) and one under authentication.

Flavor before floor. fj warned "older than Forgejo 7" on any Gitea 1.x
host because it compared the version before knowing what it was talking
to. `client::capability` now probes /api/forgejo/v1/version and then
/api/v1/version: the first answering is Forgejo, only the second is
Gitea, anything else stays unidentified with its raw version. The floor
applies to Forgejo only. Flavor and version are stored in hosts.toml at
login and refresh and shown by `fj auth status`.

Capability gates. One table decides whether a route is offered, by
flavor and version, and replaces the ad hoc Forgejo version check.
`fj run rerun` uses the Gitea 1.26+ routes and is withheld elsewhere;
`fj run cancel` uses the Forgejo 16 route and is withheld on Gitea and
older Forgejo. A withheld action sends nothing and names the run URL.
On Forgejo, `fj run rerun` now reports that limitation: the web POST it
used to attempt could never succeed with a token.

Token-only log reads. Job logs are read with Range requests in 1 MiB
chunks up to a 64 MiB ceiling. A server without the log routes gets a
typed limitation with the run URL, or a Loki the user configured. The
token no longer reaches any web route: `fj run view`, `fj run watch` and
artifact downloads use the API where the server has it and fall back to
an anonymous web request elsewhere, so on older servers they work for
publicly readable runs only.

Reverse proxies. `fj auth proxy` stores a separate HTTP Basic credential
for a proxy in front of the forge. A 407 proxy takes it in
Proxy-Authorization. A 401 Basic proxy takes it in Authorization, and
the forge token moves to X-Forge-Authorization, which the proxy has to
forward (one line in nginx or Caddy). `--pat-transport query` works with
an unmodified proxy at the cost of the token appearing in access logs.
The credential is sent to the configured host only, over HTTPS or
loopback, and neither secret appears in --debug output or errors. Git
over HTTPS behind such a proxy is out of scope; the docs recommend SSH.

Closes #209
Refs #103
stephen changed target branch from feat/spec-parity-browse to feat/spec-parity-pr 2026-10-08 19:42:58 +00:00
stephen merged commit 4ab2c1398a into feat/spec-parity-pr 2026-10-08 20:22:43 +00:00
stephen deleted branch feat/spec-parity-compat 2026-10-08 20:22:43 +00:00
Sign in to join this conversation.
No description provided.