etherpad-lite/doc
John McLear 487842006c
feat(gdpr): configurable privacy banner (PR4 of #6701) (#7549)
* docs: PR4 GDPR privacy banner design spec

* docs: PR4 GDPR privacy banner implementation plan

* feat(gdpr): typed privacyBanner setting block + public getter exposure

* feat(gdpr): send privacyBanner config to the browser via clientVars

* feat(gdpr): privacy banner DOM (hidden by default)

* feat(gdpr): render privacy banner on pad load when enabled

* style(gdpr): privacy banner layout

* test+fix(gdpr): privacy banner Playwright + hidden-attr CSS override

* docs(gdpr): privacyBanner configuration section

* fix(gdpr): reject unsafe learnMoreUrl schemes

Qodo review: showPrivacyBannerIfEnabled assigned config.learnMoreUrl
directly to <a href>, so a misconfigured settings.privacyBanner.
learnMoreUrl of `javascript:alert(1)` or `data:…<script>…` would run
script on click. Validate via URL parsing and allow only http(s) /
mailto; everything else yields no link. Playwright regression guards
the four cases (javascript, data, https, mailto).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(privacy-banner): drop unneeded !important on [hidden] rule

Class+attribute selector already outranks `.privacy-banner { display: flex }`
on specificity (0,2,0 vs 0,1,0), so `!important` was redundant. Adds a
comment explaining why so a future reader doesn't put it back.

Per Sam's review on #7549.

* refactor(privacy-banner): render as a persistent gritter, not custom DOM

Drops the bespoke #privacy-banner template + ~50 lines of popup.css and
delegates to $.gritter.add({sticky: true, position: 'bottom'}). The
notice now matches every other gritter on the pad (theme variables,
shadow, animation, (X) close), sits in the bottom corner instead of
above the editor, and inherits dark-mode handling for free.

The two dismissal modes survive intact:
  - dismissible: gritter closes on (X); before_close persists a flag
    in localStorage so the notice is suppressed on subsequent loads.
  - sticky: closes for the current session only; never persists; the
    next pad load shows it again.

learnMoreUrl still goes through the same safeUrl() filter so a
javascript:/data:/vbscript: URL can't smuggle a script handler into the
anchor (Qodo's review concern remains addressed).

Tests: src/tests/frontend-new/specs/privacy_banner.spec.ts now drives
the real showPrivacyBannerIfEnabled via a __etherpad_privacyBanner__
test hook and asserts against the rendered gritter, instead of the
previous tests that mutated DOM by hand and never exercised the
function under test. Coverage adds: enabled=false short-circuit,
dismissible-flag-respected on subsequent show, sticky-ignores-flag,
sticky-close-does-not-persist, javascript: rejection, data: rejection,
and mailto: allow-list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(privacy-banner): noreferrer + validate dismissal (Qodo)

Two follow-ups from Qodo's review on #7549:

1. The Learn-more link now sets `rel="noreferrer noopener"` (was just
   `noopener`). Without `noreferrer` the browser sends the pad URL as a
   Referer to the operator-configured external policy site, which leaks
   pad identifiers to a third party.  Matches the rel pattern already
   used by pad_utils.ts.

2. `privacyBanner.dismissal` is now validated in reloadSettings(): an
   unknown value falls back to 'dismissible' with a `logger.warn`, in
   the same shape as the existing ipLogging validation a few lines up.
   The client also guards defensively (treats anything other than the
   exact string 'sticky' as 'dismissible') so that hot-reload paths
   that skip the server validator can't silently degrade a typo'd
   'sticky' into "no close button persisted, no localStorage suppression".

Test added: spec asserts the rel attribute, and a new test exercises
the dismissal fallback (sets dismissal:'wat', asserts the gritter is
shown, the (X) closes it, and the dismissal flag is persisted — i.e.
the unknown value is treated like 'dismissible').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(privacy-banner): gate test hook on webdriver, align doc with sticky behavior

Two follow-ups from Qodo's second review on #7549.

Rule violation: __etherpad_privacyBanner__ was published on every pad
load even when privacyBanner.enabled was false, so the disabled-by-
default feature still added an observable global. Gate the assignment
on `navigator.webdriver` — Playwright/ChromeDriver/Selenium set this
to true; production browsers do not — so the hook is only present for
tests and the disabled path is genuinely zero-side-effect.

Bug 3 (sticky still closable): doc/privacy.md previously claimed
`dismissal: "sticky"` removes the close button, but the gritter
implementation always renders (X). Aligning the doc with reality —
sticky now means "shows on every load, but closable for the session"
— rather than adding bespoke CSS to a vanilla gritter (matches the
"don't style it differently than other gritter messages" preference
that drove the gritter migration in 906e145).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(privacy-banner): allow-list keys before sending to clientVars (Qodo)

storeSettings() merges nested objects with _.defaults() and preserves
unknown nested keys, and TypeScript's Pick<> doesn't strip at runtime.
The previous wire path forwarded settings.privacyBanner by reference
into both clientVars and getPublicSettings(), so any extra keys an
operator typed (or pasted) under privacyBanner — credentials, internal
notes, anything — would have shipped to every browser on every pad
load.

Adds getPublicPrivacyBanner() in Settings.ts that returns a literal
with only {enabled, title, body, learnMoreUrl, dismissal}, and uses it
from both leak sites (PadMessageHandler.ts clientVars and
getPublicSettings()). Single source of truth for the wire shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:59:38 +08:00
..
.vitepress chore: Rename some occurences of etherpad-lite to etherpad (#7552) 2026-04-19 16:53:57 +02:00
admin feat(updater): tier 1 — notify admin and pad users of available updates (#7601) 2026-05-01 20:02:12 +08:00
api feat(userlist): click a user to open chat with @<name> prefilled (#7660) 2026-05-03 05:40:44 +01:00
assets normalized 3rd level headings and adapted the max-width of the body (#5994) 2023-10-19 22:12:32 +02:00
public Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00
.gitignore Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00
cli.md chore: adapted documentation for migrateDB.ts (#7088) 2025-08-24 20:50:16 +02:00
cookies.adoc normalized 3rd level headings and adapted the max-width of the body (#5994) 2023-10-19 22:12:32 +02:00
cookies.md feat(gdpr): HttpOnly author-token cookie (PR3 of #6701) (#7548) 2026-05-03 05:56:56 +01:00
database.adoc Added docs as asciidoctor with cross platform support. (#5733) 2023-06-21 13:13:31 +01:00
demo.md Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00
docker.adoc feat!: replace Abiword with LibreOffice and add DOCX export (#7539) 2026-04-19 09:08:22 +01:00
docker.md fix(colors): pick WCAG-higher-contrast text for author colors (#7565) 2026-05-03 12:56:30 +08:00
documentation.adoc Added docs as asciidoctor with cross platform support. (#5733) 2023-06-21 13:13:31 +01:00
documentation.md Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00
index.adoc Added docs as asciidoctor with cross platform support. (#5733) 2023-06-21 13:13:31 +01:00
index.md Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00
localization.adoc fixed format of json (#5992) 2023-10-19 21:33:12 +02:00
localization.md Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00
npm-trusted-publishing.md ci(docs): build on PRs and pin Node 22 (Qodo follow-up to #7640) (#7645) 2026-05-01 17:12:23 +01:00
package.json fix(docs): add oxc-minify so vitepress builds with rolldown-vite (#7640) 2026-05-01 13:48:12 +01:00
PLUGIN_FEATURE_DISABLES.md test: tag rtl_url_param toggle-off specs with @feature:rtl-toggle (#7661) 2026-05-03 13:26:50 +08:00
PLUGIN_FRONTEND_TESTS.md ci(playwright): discover plugin frontend specs (closes #7622) (#7623) 2026-04-28 06:33:43 +01:00
plugins.adoc normalized 3rd level headings and adapted the max-width of the body (#5994) 2023-10-19 22:12:32 +02:00
plugins.md chore: remove dead root files (.travis.yml, .lgtm.yml, *.bat) (#7531) 2026-04-16 17:29:24 +01:00
privacy.md feat(gdpr): configurable privacy banner (PR4 of #6701) (#7549) 2026-05-03 13:59:38 +08:00
skins.adoc Added docs as asciidoctor with cross platform support. (#5733) 2023-06-21 13:13:31 +01:00
skins.md feat: first version 2025-08-01 21:42:05 +02:00
stats.adoc chore: added documentation for prometheus 2025-08-25 21:13:38 +02:00
stats.md Added vitepress for documentation. (#6270) 2024-03-23 20:58:05 +01:00