fix(gallery): block password form in Instagram in-app browser and unmask login errors (#863)

* fix(gallery): block password form in Instagram in-app browser (#654)

Field reports show gallery password login still failing inside
Instagram's IAB after the #656 input-attribute/trim defenses. Three
changes:

- Replace the advisory amber banner with a red blocking state: the
  password form is hidden in the Instagram IAB and replaced with
  platform-specific "open in external browser" instructions plus a
  copy-link button (clipboard API with execCommand fallback). A
  "try anyway" link restores the form as an escape hatch.
- Stop masking non-password failures as "incorrect password": a request
  that never got a response (offline, webview killed it) now reports a
  connection error, and a reCAPTCHA 400 reports a verification failure —
  both previously fell through to the wrong-password message and sent
  guests chasing the wrong cause.
- Strip invisible Unicode (zero-width chars, word joiner, BOM, soft
  hyphen) from the submitted password in addition to trimming — these
  ride along when the password is copy-pasted out of a chat app and fail
  byte-exact bcrypt compare server-side.

* fix(gallery): retry login with typed password + honor execCommand result (#654)

Codex review round 1:
- Stored passwords can legitimately contain the invisible code points the
  sanitizer strips (e.g. ZWJ emoji sequences) — creation paths don't
  normalize. On a 401 where the sanitized form differs from the typed
  (trimmed) input, retry once with the typed value. Skipped when a
  reCAPTCHA token is in play (single-use).
- document.execCommand('copy') signals failure via its return value, not
  by throwing — only show "Link copied" when it returns true.

* fix(gallery): move invisible-char password fallback server-side (#654)

Codex review round 2: the client-side retry either burned the single-use
reCAPTCHA token (making exotic-but-valid passwords impossible to enter
with reCAPTCHA on) or burned failed-attempt lockout quota on every
rescued login. Doing the fallback as a second bcrypt compare inside the
same gallery/verify request eliminates both: exact bytes are compared
first (stored passwords containing e.g. ZWJ emoji keep working), the
sanitized form only on mismatch, and trackFailedAttempt only fires when
both fail. Frontend goes back to plain trim-on-submit; the client-side
sanitizer util and retry are removed. 7 integration tests pin the
contract.

---------

Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
Paul Nothaft
2026-07-23 21:45:07 +02:00
committed by GitHub
co-authored by Paul Nothaft
parent 9ac23e5fe1
commit 323dcae917
6 changed files with 279 additions and 39 deletions
+8 -3
View File
@@ -804,11 +804,16 @@
"tooManyAttempts": "Zu viele fehlgeschlagene Anmeldeversuche. Bitte versuchen Sie es später erneut.",
"pleaseEnterPassword": "Bitte geben Sie ein Passwort ein",
"passwordHint": "Das Passwort wurde vom Veranstalter bereitgestellt. Kontaktieren Sie ihn, wenn Sie es nicht haben.",
"networkError": "Verbindung fehlgeschlagen. Bitte prüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.",
"recaptchaFailed": "Sicherheitsprüfung fehlgeschlagen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.",
"iab": {
"instagram": {
"title": "Diesen Link im Browser öffnen",
"ios": "Der eingebaute Browser von Instagram blockiert das Passwort-Login manchmal. Oben rechts auf das ⋯-Menü tippen und „Im externen Browser öffnen“ wählen (oder den Link kopieren und in Safari einfügen).",
"android": "Der eingebaute Browser von Instagram blockiert das Passwort-Login manchmal. Oben rechts auf das ⋮-Menü tippen und „Im externen Browser öffnen“ wählen (oder den Link kopieren und in Chrome einfügen)."
"blockedTitle": "Der Instagram-Browser kann diese Galerie nicht öffnen",
"blockedIos": "Das Passwort-Login funktioniert im eingebauten Browser von Instagram nicht zuverlässig. Oben rechts auf das ⋯-Menü tippen und „Im externen Browser öffnen“ wählen oder den Link kopieren und in Safari einfügen.",
"blockedAndroid": "Das Passwort-Login funktioniert im eingebauten Browser von Instagram nicht zuverlässig. Oben rechts auf das ⋮-Menü tippen und „Im externen Browser öffnen“ wählen oder den Link kopieren und in Chrome einfügen.",
"copyLink": "Link kopieren",
"linkCopied": "Link kopiert",
"tryAnyway": "Passwort trotzdem hier eingeben"
}
}
},
+8 -3
View File
@@ -351,11 +351,16 @@
"tooManyAttempts": "Too many failed login attempts. Please try again later.",
"pleaseEnterPassword": "Please enter a password",
"passwordHint": "The password was provided by the event organizer. Contact them if you don't have it.",
"networkError": "Connection failed. Please check your internet connection and try again.",
"recaptchaFailed": "Security verification failed. Please reload the page and try again.",
"iab": {
"instagram": {
"title": "Open this link in your browser",
"ios": "Instagram's built-in browser sometimes blocks the password login. Tap the ⋯ menu in the top right, then \"Open in external browser\" (or copy the link and paste into Safari).",
"android": "Instagram's built-in browser sometimes blocks the password login. Tap the ⋮ menu in the top right, then \"Open in external browser\" (or copy the link and paste into Chrome)."
"blockedTitle": "Instagram's browser can't open this gallery",
"blockedIos": "Password login does not work reliably in Instagram's built-in browser. Tap the ⋯ menu in the top right and choose \"Open in external browser\", or copy the link and paste it into Safari.",
"blockedAndroid": "Password login does not work reliably in Instagram's built-in browser. Tap the ⋮ menu in the top right and choose \"Open in external browser\", or copy the link and paste it into Chrome.",
"copyLink": "Copy link",
"linkCopied": "Link copied",
"tryAnyway": "Try entering the password here anyway"
}
}
},