diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 041f7c1a9..f32093ac4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,7 +30,8 @@ If applicable, add screenshots to help explain your problem. - OS: [e.g., Ubuntu 20.04] - Node.js version (`node --version`): - npm version (`npm --version`): - - Is the server free of plugins: + - Is the server free of plugins: + - Are you using any abstraction IE docker? **Desktop (please complete the following information):** - OS: [e.g. iOS] diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index c4f8618bc..10562bd52 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -146,7 +146,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_readonly_guest + ep_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript @@ -289,7 +289,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_readonly_guest + ep_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d709d3a50..0d1c51a7c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -66,7 +66,13 @@ jobs: name: Test working-directory: etherpad run: | - docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }} + # ADMIN_PASSWORD provisions settings.json.docker's admin user so + # the adminSettings_7819 container spec can authenticate against + # /admin and the /settings socket. pad.js doesn't touch /admin + # so the existing API specs are unaffected. + docker run --rm -d -p 9001:9001 \ + -e ADMIN_PASSWORD=changeme1 \ + --name test ${{ env.TEST_TAG }} ./bin/installDeps.sh docker logs -f test & while true; do @@ -79,6 +85,34 @@ jobs: esac done (cd src && pnpm run test-container) + # Regression for #7819. adminSettings_7819.ts saves a marker via + # the admin /settings socket and intentionally leaves it on + # disk. We assert here that the save actually hit the file + # (mocha only sees the next `load` reply — this catches a + # scenario where the load is served from cache and the file + # never actually changed). + docker exec test grep -q persist-marker-7819 /opt/etherpad-lite/settings.json || { + echo "[#7819] socket-driven save did NOT reach /opt/etherpad-lite/settings.json on disk" + docker exec test cat /opt/etherpad-lite/settings.json | head -50 + exit 1 + } + # Now prove the on-disk file survives an in-place container + # restart. This is the scenario a docker-compose user with + # `restart: always` hits on every host reboot. + docker restart test >/dev/null + for i in $(seq 1 60); do + status=$(docker container inspect -f '{{.State.Health.Status}}' test 2>/dev/null) || { docker logs test; exit 1; } + case ${status} in + healthy) break;; + starting) sleep 2;; + *) docker logs test; exit 1;; + esac + done + docker exec test grep -q persist-marker-7819 /opt/etherpad-lite/settings.json || { + echo "[#7819 REGRESSION] settings.json was reset on docker restart — ep_oauth block vanished" + docker logs test + exit 1 + } docker rm -f test git clean -dxf . - diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 81029f00c..19fa6da3f 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -219,7 +219,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_readonly_guest + ep_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript @@ -308,7 +308,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_readonly_guest + ep_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml index 29311e093..8b1b3f3f8 100644 --- a/.github/workflows/load-test.yml +++ b/.github/workflows/load-test.yml @@ -93,7 +93,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_readonly_guest + ep_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript diff --git a/.github/workflows/upgrade-from-latest-release.yml b/.github/workflows/upgrade-from-latest-release.yml index 432e94db7..c3ef545f5 100644 --- a/.github/workflows/upgrade-from-latest-release.yml +++ b/.github/workflows/upgrade-from-latest-release.yml @@ -76,7 +76,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_readonly_guest + ep_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript diff --git a/CHANGELOG.md b/CHANGELOG.md index b92a00809..27513e9c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,42 @@ +# 3.2.0 + +3.2 adds first-class reverse-proxy / ingress support — `X-Forwarded-Prefix` and `X-Ingress-Path` are now honoured under `trustProxy`, so Etherpad can live under a subpath (Traefik, Nginx, Kubernetes Ingress) without breaking the PWA manifest, social-meta URLs, or any of the bootstrap asset links. The admin settings page learns to show *resolved* runtime values next to `${VAR:default}` placeholders, the v3.1.0 admin pad-list filter chips now apply server-side (so "show empty pads" no longer returns 0–12 of hundreds), and the v3.1.0 redesigned outdated-version gritter actually fires in production now (the session-based author lookup it shipped with always returned null for pad visitors). + +### Notable enhancements + +- **HTTP — accept `X-Forwarded-Prefix` and `X-Ingress-Path` under `trustProxy` (#7802 / #7806).** With `trustProxy: true`, Etherpad now honours `X-Forwarded-Prefix` (de-facto Traefik / Spring) and `X-Ingress-Path` (Kubernetes Ingress) in addition to the prefix it already inferred from the request path. The shared `sanitizeProxyPath` helper added in 3.1.0 (defence-in-depth: `[A-Za-z0-9_./-]` only, `//+` collapsed, `..` traversal rejected) is extended to the new headers and applied consistently across `/manifest.json`, `socialMeta` `og:url` / `og:image`, and the `index.html` / `pad.html` / `timeslider.html` / `export_html.html` templates (manifest links, jslicense links, reconnect URLs). A pre-existing `..` segment-count miscalculation in `pad.html` / `timeslider.html` that broke the manifest link when served from a deep subpath is also fixed in passing. New end-to-end suite covers the prefix-applied / prefix-ignored matrix under `trustProxy=true|false` for both header names. `settings.json.template` documents the new headers alongside the existing `trustProxy` notes. +- **Admin settings — resolved runtime values surface on env-pill chips (#7803 / #7807).** The `/admin/settings` socket payload now carries a new `resolved` field alongside the existing raw-file `results` blob, carrying the actual in-memory settings module run through a new redactor (`AdminSettingsRedact`) that replaces known-sensitive paths (`users.*.password`, `dbSettings.password`, `sso.clients[*].client_secret`, `sessionKey`, …) with `[REDACTED]`. The admin SPA's `EnvPill` renders a `→ active value` chip when the path is resolved, or `→ ••••••` with a redacted tooltip when the server returned the sentinel — so `port: ${PORT:9001}` now shows `→ 9001` (or whatever the live value is) instead of silently falling back to the template default. Old admin SPAs that don't read `resolved` continue to work; the save round-trip is unchanged so `${VAR:default}` literals are still preserved verbatim on disk. The admin test script glob picks up `.test.tsx` alongside `.test.ts` so the new `EnvPill` and `resolveByPath` tests run under `tsx --test`. + +### Notable fixes + +- **Admin pads — filter chip now applies server-side, before pagination (#7798).** The 3.1.0 admin pad-list filter chips (`active` / `recent` / `empty` / `stale`) ran on the client *after* the 12-row page slice had already arrived. On a deployment with hundreds of pads, clicking "empty pads" on page 1 only matched the 0–12 empties that happened to land in the current page, with the pagination footer reporting nonsense totals (reported on a 3.1.0 deployment). The filter is now part of the `padLoad` socket query — pattern filter on names runs first (cheap), metadata hydration for the matching pad universe is gated on a non-`all` filter or a non-`padName` sort and runs under a 16-way concurrency cap (was unbounded `Promise.all`, which fanned out to thousands of in-flight `padManager.getPad()` reads on busy deployments), then the filter chip, then sort + slice. `total` reflects the filtered universe so the footer makes sense. Older admin clients that don't send `filter` keep working — the server defaults to `all`. The `if/else if` ladder that duplicated the hydrate-and-sort loop per `sortBy` is folded into one pipeline with a single comparator switch. +- **Pad outdated notice — author now resolved from token cookie, not session (Qodo #7804 / #7805).** The 3.1.0 redesigned outdated-version gritter never fired in production. `resolveRequestAuthor()` looked for an `authorID` on `req.session.user`, which Etherpad does not populate for pad visitors (express-session only carries the admin-login user), so `computeOutdated()` always returned EMPTY. The lookup now mirrors how the socket.io handshake resolves pad-visitor identity — read the HttpOnly `token` (or `token`) cookie and call `authorManager.getAuthorId(token, user)` via a dynamic import (same circular-init guard pattern the file already uses for `PadManager`). The admin OpenAPI document gains a `description` note clarifying that `/api/version-status` is a public pad-side endpoint that lives in the admin doc only because it shares the same internal route registration. +- **Localisation — silence spurious "could not translate element content" warning (#7797).** ``'s text is its `