diff --git a/pb_migrations/1794000000_czech_mail_templates.js b/pb_migrations/1794000000_czech_mail_templates.js new file mode 100644 index 0000000..dd98ac7 --- /dev/null +++ b/pb_migrations/1794000000_czech_mail_templates.js @@ -0,0 +1,83 @@ +/// + +// The account emails were in English, and pointed at PocketBase's admin console. +// +// **Why it matters here.** Every other surface of this app is Czech, on purpose: +// the people using it are the owner's parents and his children. The one moment +// they are most likely to be stuck — locked out, asking for a new password — was +// the one moment the product started speaking English and sent them to +// `pb.petruzalekr.cz/_/#/auth/confirm-password-reset/…`, which is the admin UI's +// own page and reads to anybody's parent like a developer tool opened by +// mistake. +// +// Found by looking at a delivered message rather than at the settings: the +// templates were simply PocketBase's defaults, and defaults are invisible until +// somebody receives one. +// +// The links now point at amber.petruzalekr.cz, which hosts the confirm form in +// Czech (see pb_public/index.html, `?reset=`). Verification and email-change +// keep PocketBase's own confirm pages: nobody has hit those yet, they are not +// part of the reported problem, and pointing them somewhere that does not exist +// would be worse than English. +const RESET_SUBJECT = "Nové heslo k Amberu"; +const RESET_BODY = `

Ahoj,

+

Někdo (snad ty) požádal o nové heslo k Amberu. Nastavíš si ho tímhle odkazem:

+

+ Nastavit nové heslo +

+

Pokud jsi o nic nežádal, tenhle e-mail klidně smaž. Nic se nestalo a heslo +zůstává, jaké bylo.

+

Amber

`; + +const VERIFY_SUBJECT = "Ověření e-mailu pro Amber"; +const VERIFY_BODY = `

Ahoj,

+

Potvrď prosím tímhle odkazem, že tenhle e-mail patří tobě:

+

+ Potvrdit e-mail +

+

Amber

`; + +const CHANGE_SUBJECT = "Změna e-mailu u Amberu"; +const CHANGE_BODY = `

Ahoj,

+

Potvrď prosím změnu e-mailu u svého účtu:

+

+ Potvrdit změnu +

+

Pokud jsi o změnu nežádal, ozvi se Richardovi — někdo se ti hrabe v účtu.

+

Amber

`; + +// The defaults, so the down migration restores exactly what was there. +const EN = { + reset: { + subject: "Reset your {APP_NAME} password", + body: `

Hello,

+

Click on the button below to reset your password.

+

+ Reset password +

+

If you didn't ask to reset your password, please ignore this email.

+

+ Thanks,
+ {APP_NAME} team +

`, + }, +}; + +migrate( + (app) => { + const users = app.findCollectionByNameOrId("users"); + users.resetPasswordTemplate.subject = RESET_SUBJECT; + users.resetPasswordTemplate.body = RESET_BODY; + users.verificationTemplate.subject = VERIFY_SUBJECT; + users.verificationTemplate.body = VERIFY_BODY; + users.confirmEmailChangeTemplate.subject = CHANGE_SUBJECT; + users.confirmEmailChangeTemplate.body = CHANGE_BODY; + app.save(users); + }, + (app) => { + const users = app.findCollectionByNameOrId("users"); + users.resetPasswordTemplate.subject = EN.reset.subject; + users.resetPasswordTemplate.body = EN.reset.body; + app.save(users); + }, +); diff --git a/pb_public/index.html b/pb_public/index.html index 1e30eea..efd4070 100644 --- a/pb_public/index.html +++ b/pb_public/index.html @@ -106,6 +106,25 @@
+ + + +
@@ -1354,10 +1373,52 @@ $("acChange").onclick=async function(){ await loadAddons(); }; +// ── setting a new password from an emailed link ────────────────────────────── + +/** The token the reset email put in the address bar, or null. */ +function resetToken(){ + try { return new URLSearchParams(location.search).get("reset"); } + catch(_){ return null; } +} + +function showReset(){ + $("anon").hidden = true; + $("reset").hidden = false; + $("rNew").focus(); +} + +$("rSave").onclick=async function(){ + var tok=resetToken(); + var a=$("rNew").value, b=$("rNew2").value; + if(a.length<8){ setMsg($("rMsg"),"Heslo musí mít aspoň 8 znaků.","err"); return; } + if(a!==b){ setMsg($("rMsg"),"Hesla se neshodují.","err"); return; } + setMsg($("rMsg"),""); busy($("rSave"),true,"Nastavuji…"); + var r=await api("POST","/api/collections/users/confirm-password-reset", + { token:tok, password:a, passwordConfirm:b }, {anon:true}); + busy($("rSave"),false); + if(!r.ok){ + // The overwhelmingly likely cause, and the one worth naming: these links + // expire, and a person who tried yesterday's email has no way to know that + // from "something went wrong". + setMsg($("rMsg"),"Nepovedlo se. Odkaz nejspíš vypršel — požádej o nový.","err"); + return; + } + $("rNew").value=$("rNew2").value=""; + setMsg($("rMsg"),"Hotovo. Přihlas se novým heslem.","ok"); + // Drop the token from the address bar: it is spent, and a URL with a + // credential in it is one that ends up in a bookmark or a shared screenshot. + try { history.replaceState(null,"",location.pathname); } catch(_){} + setTimeout(function(){ $("reset").hidden=true; $("anon").hidden=false; }, 2500); +}; + // ── boot ───────────────────────────────────────────────────────────────────── (async function(){ fillLangs(); $("tvUrl").textContent=location.host; + // A reset link wins over a stored session: somebody arriving with one is + // trying to fix their account, and dropping them into a signed-in page they + // did not ask for hides the very thing they came to do. + if (resetToken()) { showReset(); return; } if (await restore()) await enter(); })();