Files
picpeak/frontend/src/i18n
Paul Nothaft a21c4d3bf5 fix(guests): keep guest identity across a tab close
Closes #1265.

The guest JWT and profile lived in sessionStorage, so the practical lifetime
of an identity was "until this tab closes". GUEST_TOKEN_TTL was raised to 30
days in #1216 specifically to stop identity churn, but it governs how long the
token stays valid, not how long the browser keeps it -- so it was almost never
reached.

A guest who closed the tab and came back through the same emailed link got the
registration prompt again, and the ?invite= token in that link is single-use
and already redeemed, so it could not put them back. Typing the same name
inserted a second gallery_guests row: their earlier likes then belonged to an
identity they could no longer act as, and could not be removed.

Moved to localStorage, which is the reporter's suggestion and the one that
lines up with the TTL that already exists.

This does not reopen the objection #1216 raised. Deduplicating on a typed
email was rejected there because anyone knowing an address could claim that
person's identity, and answering differently for a known address leaks which
addresses are in the gallery. This grants nothing to anyone -- it only stops
the browser discarding a token it was already given. Gallery ACCESS stays in
sessionStorage (galleryAuthStorage.ts) and is untouched, so a returning
visitor still has to pass the gallery password before a stored identity means
anything.

Two things the storage swap alone would have got wrong:

- Anyone with a gallery open at upgrade time would be treated as a new guest
  on their next reload -- the exact duplicate-row bug this fixes, fired once
  per in-flight guest. getGuestToken/getGuestIdentity now move a pre-#1265
  sessionStorage entry across on first read. It moves rather than copies, and
  a fresh registration in the current tab always wins over a stale copy.
  clearGuestIdentity clears both stores, so "forget me" cannot be undone by a
  leftover being migrated back.

- Identity now surviving a tab close means a second person on a shared device
  can be greeted by the previous visitor's name. Their only exit was "Forget
  me", which soft-deletes the guest row and anonymizes their feedback -- it
  would erase the wrong person's selections. Added a non-destructive
  signOut() and a "Not you?" control next to it, which only clears the
  identity on this device.

Storage access already funnelled through one getStorage() accessor, so the
swap is a one-line change there; it falls back to sessionStorage when
localStorage throws (Safari private mode, blocked by policy) rather than
dropping identity entirely.

6 tests. 3 fail against the old implementation, including the core "survives a
tab close" case; the other 3 pin the migration and the both-stores clear.

Note: the new "Not you?" string is added to en and de. i18n:ci is already red
on main (11,405 missing keys) because the extractor there manages six locales
while only en/de are kept at parity; this adds 4 entries of that same class.
PR #1267 fixes the check itself.
2026-09-02 10:22:55 +02:00
..