112 lines
3.6 KiB
Markdown
112 lines
3.6 KiB
Markdown
# Studio forge proxy
|
|
|
|
A stateless, host-allowlisted CORS proxy that lets a browser-based Studio read
|
|
from a Forgejo instance on another origin. It forwards both the REST API and
|
|
git smart HTTP; the target's absolute URL is carried in the path, so one scheme
|
|
serves everything. It also fronts `bimr.net` so the agent can read the BIMR
|
|
help page (a plain `GET`, no credentials):
|
|
|
|
```
|
|
/forge-proxy/https://forge.example/api/v1/repos/search?q=x
|
|
/forge-proxy/https://forge.example/owner/repo.git/info/refs?service=git-upload-pack
|
|
```
|
|
|
|
It has **no dependencies** and no state. It exists because browsers enforce the
|
|
same-origin policy, and Forgejo never sends CORS headers on git routes.
|
|
|
|
## Run
|
|
|
|
```bash
|
|
# allow one or more forges (exact hosts or *.suffix wildcards)
|
|
FORGE_ALLOWED_HOSTS="artefacts.bimr.net,bimr.net,*.example.org" node proxy/node.mjs
|
|
```
|
|
|
|
Then point Studio at it: set **Proxy URL** in Settings to
|
|
`http://localhost:8787/forge-proxy/`, or serve it same-origin at
|
|
`/forge-proxy/`.
|
|
|
|
| Env | Default | Meaning |
|
|
|-----|---------|---------|
|
|
| `FORGE_ALLOWED_HOSTS` | _(empty)_ | Comma-separated allowlist. Empty denies every target. |
|
|
| `FORGE_ALLOW_ANY` | `0` | `1` allows any host — **development only**. |
|
|
| `FORGE_PROXY_PREFIX` | `/forge-proxy` | Mount prefix, stripped to resolve scheme-less targets. |
|
|
| `PORT` | `8787` | Listen port. |
|
|
| `HOST` | `127.0.0.1` | Listen address. |
|
|
|
|
## URL scheme
|
|
|
|
The absolute target URL is appended to the mount path. The proxy accepts two
|
|
shapes:
|
|
|
|
- **with scheme** — `/forge-proxy/https://host/path` (used for the REST API);
|
|
- **scheme-less** — `/forge-proxy/host/path`, which is what `isomorphic-git`'s
|
|
`corsProxy` produces (it strips `https://`). The proxy strips the mount prefix
|
|
(`FORGE_PROXY_PREFIX`) and assumes `https`.
|
|
|
|
Both are validated against the allowlist.
|
|
|
|
## Deployment
|
|
|
|
### Behind nginx (same-origin)
|
|
|
|
`nginx` merges repeated slashes by default, which would turn `https://` in the
|
|
path into `https:/`. Disable that for the proxy location:
|
|
|
|
```nginx
|
|
location /forge-proxy/ {
|
|
merge_slashes off;
|
|
proxy_pass http://127.0.0.1:8787;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_request_buffering off;
|
|
proxy_buffering off;
|
|
client_max_body_size 256m;
|
|
}
|
|
```
|
|
|
|
### systemd
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Studio forge proxy
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
Environment=FORGE_ALLOWED_HOSTS=artefacts.bimr.net,bimr.net
|
|
Environment=PORT=8787
|
|
ExecStart=/usr/bin/node /srv/studio/proxy/node.mjs
|
|
Restart=on-failure
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
### Edge / serverless
|
|
|
|
`proxy/proxy.mjs` exports a Web `fetch` handler and a `default { fetch }`
|
|
object, so it also runs on Cloudflare Workers, Deno Deploy and Vercel Edge
|
|
without changes. Provide the allowlist via the runtime's env binding; the
|
|
handler reads `FORGE_ALLOWED_HOSTS` from `process.env` or `globalThis`.
|
|
|
|
## Development
|
|
|
|
The Vite dev server mounts the same handler at `/forge-proxy/` (see
|
|
`vite.config.ts`), so development and production share one implementation. In
|
|
dev, `FORGE_ALLOW_ANY` is enabled automatically when no allowlist is set.
|
|
|
|
## Security
|
|
|
|
- **Deny by default.** An open proxy is an SSRF and abuse vector; always set
|
|
`FORGE_ALLOWED_HOSTS` in production.
|
|
- **HTTPS only**, except `http://localhost` for local development.
|
|
- Browser credentials (`Cookie`, `Origin`, `Referer`) are stripped before
|
|
forwarding; `Set-Cookie` is stripped from responses.
|
|
- `Authorization` and request/response bodies are never logged.
|
|
- Bodies are streamed (no buffering); put rate limiting and a body-size cap in
|
|
the reverse proxy in front (see the nginx block above).
|
|
|
|
## Test
|
|
|
|
```bash
|
|
node --test proxy/proxy.test.mjs
|
|
```
|