rewrite Gitea's URLs so the frame stays inside the proxy
Gitea believes it lives at /. Its pages are full of root-absolute links, and inside the frame those resolve against OFFICER'S origin — the page renders perfectly and falls apart on the first click. Four places a URL hides, each failing differently: href/src/action attributes, window.config.appSubUrl, Location headers, and absolute URLs built from ROOT_URL. appSubUrl is the highest-value line. Gitea already supports being hosted under a subpath and its client JS prefixes every URL it builds with that value, so filling it in makes Gitea correct ITSELF rather than by interception. The ROOT_URL case needs Gitea's public origin, which the sidecar does not have — it fetches from loopback. Rather than add a second setting that can disagree with Gitea's own, appUrlOrigin reads it out of the page: Gitea emits "appUrl" in the same config blob as appSubUrl. Not rewritten, deliberately: JavaScript bodies, because a blanket rewrite of absolute paths inside a script corrupts strings that are not URLs; and clone URLs, which must keep working for whoever copies them. /_web/* now forwards by hand rather than through createSidecarProxy. That forwarder passes three headers and nothing else, on purpose — it is shared by every sidecar and its restraint is what makes it safe to reuse. It strips cookies, and this path is nothing but cookies. Loosening it for gitea would loosen it for the wallet. Eighteen tests, the only part of this plugin verifiable without a running Gitea. One caught a real bug: rewriteAbsolute turns a full URL into <base>/path and the attribute pass then prefixed it again.
This commit is contained in:
@@ -61,6 +61,45 @@ What that protects is narrow, and worth stating plainly: Gitea authenticates the
|
||||
unauthenticated caller reaching `/_web/` sees a login page. This stops Officer being used as an open
|
||||
proxy to it. A manners problem, cheaply fixed.
|
||||
|
||||
## The rewriter
|
||||
|
||||
Gitea believes it lives at `/`. Its pages are full of root-absolute links — `/user/login`,
|
||||
`/assets/index.js` — and inside the frame those resolve against **Officer's** origin. The page renders
|
||||
perfectly and falls apart on the first click.
|
||||
|
||||
`sidecar/rewrite.ts` handles four places a URL hides, and they fail differently:
|
||||
|
||||
| | why it matters |
|
||||
| --- | --- |
|
||||
| `href` / `src` / `action` attributes | the ordinary case: clicks and assets |
|
||||
| `window.config.appSubUrl` | the highest-value line here — Gitea's own client JS prefixes every URL it builds with this, so filling it in makes Gitea correct **itself** rather than by interception |
|
||||
| `Location` headers | the most visible failure: Gitea redirects after every login and form post, and an unrewritten one navigates the frame out of the proxy |
|
||||
| absolute URLs built from `ROOT_URL` | the most damaging: they leave for a third-party origin where the session cookie is dropped, so the user is silently signed out |
|
||||
|
||||
That last one needs to know Gitea's public origin, which the sidecar does not have — it fetches from
|
||||
loopback. Rather than add a second setting that can disagree with Gitea's own, `appUrlOrigin` reads it
|
||||
out of the page: Gitea emits `"appUrl"` in the same config blob as `appSubUrl`.
|
||||
|
||||
**Not rewritten, deliberately:** JavaScript bodies, because a blanket rewrite of absolute paths inside a
|
||||
script corrupts strings that are not URLs — `appSubUrl` removes the need. And clone URLs, which must keep
|
||||
working for whoever copies them.
|
||||
|
||||
Cookies get their `Path` narrowed to the frame so Gitea's session stays out of the platform's API.
|
||||
`Secure` is left exactly as Gitea set it: adding it breaks a plain-http install, removing it weakens one
|
||||
that meant it.
|
||||
|
||||
Eighteen tests cover it, and they are the only part of this plugin that can be verified without a running
|
||||
Gitea. One caught a real bug: `rewriteAbsolute` turns a full URL into `<base>/path`, and the attribute
|
||||
pass then prefixed it **again**.
|
||||
|
||||
## The frame is forwarded by hand
|
||||
|
||||
`/_web/*` does NOT go through `createSidecarProxy`. That forwarder passes exactly three headers —
|
||||
`content-type`, `range`, `if-none-match` — and its restraint is what makes it safe to share between every
|
||||
sidecar. **It strips cookies**, and this path is nothing but cookies. Loosening it for gitea would loosen
|
||||
it for the wallet, so this one route forwards the request itself. Everything else still uses the shared
|
||||
proxy.
|
||||
|
||||
## What moved
|
||||
|
||||
| Was | Is now |
|
||||
|
||||
Reference in New Issue
Block a user