fix(events): publish-from-draft email carries the real password (#627)

Previously, publishing a password-protected DRAFT gallery sent the
gallery_created email with the literal sentinel "(set at creation)",
which the email processor localised to "The password you set when
creating the gallery" / "Das bei der Erstellung der Galerie gesetzte
Passwort". Root cause: at draft creation only the bcrypt hash is stored
(no plaintext column, by design); the publish endpoint had nowhere to
pull the actual password from. Create-and-publish-in-one-step worked
because the plaintext is still in memory at email-queue time.

Fix: the Publish action now opens a small PublishGalleryDialog that
prompts the admin to (re-)type the gallery password. The publish
endpoint accepts an optional `password` body, re-hashes + writes
`password_hash` so the stored hash matches what was just emailed (admins
who mistype at creation get a self-healing publish flow), and puts the
plaintext into the gallery_password email field. When the publish call
is made without a password (API-only consumers), behaviour falls back
to the legacy sentinel — no breaking change.

The window.confirm() publish flow is gone; the dialog handles the no-
password case too (plain confirm + Publish button).

I18n: EN + DE entries for the dialog. Other locales fall through to
the EN defaults via the t() default-value pattern.

No schema changes. No plaintext at rest.
This commit is contained in:
Paul Nothaft
2026-06-17 22:58:17 +02:00
parent ea6245cfde
commit 83b568ee2d
7 changed files with 228 additions and 20 deletions
+9
View File
@@ -1018,6 +1018,15 @@
"publishAndNotify": "Veröffentlichen & Kunden benachrichtigen",
"publishConfirm": "Dadurch wird die Galerie zugänglich und die Benachrichtigungs-E-Mail an den Kunden gesendet. Fortfahren?",
"publishSuccess": "Galerie veröffentlicht und Kunde benachrichtigt!",
"publishDialog": {
"title": "Galerie veröffentlichen",
"descriptionWithEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht und die Benachrichtigungs-E-Mail an {{customerEmail}} gesendet.",
"descriptionNoEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht. Es ist keine Kunden-E-Mail hinterlegt es wird keine Benachrichtigung gesendet.",
"passwordLabel": "Galerie-Passwort",
"passwordPlaceholder": "Galerie-Passwort eingeben",
"passwordHelp": "Gib das bei der Erstellung gesetzte Passwort erneut ein (oder wähle ein neues). Die E-Mail enthält genau diesen Text; das Backend hasht es erneut, sodass die Galerie-Anmeldung weiterhin funktioniert.",
"errorMinLength": "Das Passwort muss mindestens 6 Zeichen lang sein."
},
"draftBanner": "Diese Galerie befindet sich im Entwurfsmodus. Laden Sie Ihre Fotos hoch und veröffentlichen Sie, wenn Sie bereit sind.",
"subtitle": "Verwalten Sie Ihre Fotogalerien und Veranstaltungen",
"failedToLoadEvents": "Veranstaltungen konnten nicht geladen werden",
+9
View File
@@ -565,6 +565,15 @@
"publishAndNotify": "Publish & Notify Client",
"publishConfirm": "This will make the gallery accessible and send the notification email to the client. Continue?",
"publishSuccess": "Gallery published and client notified!",
"publishDialog": {
"title": "Publish gallery",
"descriptionWithEmail": "Publishing \"{{eventName}}\" makes the gallery accessible and sends the notification email to {{customerEmail}}.",
"descriptionNoEmail": "Publishing \"{{eventName}}\" makes the gallery accessible. No customer email is set, so no notification will be sent.",
"passwordLabel": "Gallery password",
"passwordPlaceholder": "Enter the gallery password",
"passwordHelp": "Re-type the password set at creation (or pick a new one). The email includes this exact text; the backend re-hashes it so the gallery login still works.",
"errorMinLength": "Password must be at least 6 characters long."
},
"draftBanner": "This gallery is in draft mode. Upload your photos, then publish when ready.",
"subtitle": "Manage your photo galleries and events",
"failedToLoadEvents": "Failed to load events",