From f72c9e7986efa51014f4516e984d52173e743125 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 19:31:51 +0200 Subject: [PATCH] Finish confirming an email address on our own site The verification email still pointed at pb.petruzalekr.cz/_/#/auth/..., the PocketBase admin console. The previous pass left it there reasoning that nobody had reached it yet; the accounts say otherwise. Registration fires a verification email every time (auth_service.dart, fire and forget) and four of the nine accounts are marked verified, so four family members have already landed in the admin console and clicked a button in it. ?verify= now confirms in Czech beside ?reset=. It confirms on page load rather than behind a button: a provider that prefetches links only issues a GET, the POST comes from the page's own script, and asking someone to press a second button on a page they reached by pressing one is friction with nothing behind it. Nothing is gated on verified. The users collection has an empty authRule and five accounts, the owner's included, work fine without it. The flag stays because it is the only evidence an address was typed correctly, which is exactly the question that comes up when somebody reports a missing email. The failure copy says so plainly, so an expired link reads as harmless rather than as a lockout. Email-change is deliberately untouched: no surface can trigger it today. Co-Authored-By: Claude Opus 5 --- .../1794500000_verify_link_to_web.js | 55 ++++++++++++++++++ pb_public/index.html | 56 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 pb_migrations/1794500000_verify_link_to_web.js diff --git a/pb_migrations/1794500000_verify_link_to_web.js b/pb_migrations/1794500000_verify_link_to_web.js new file mode 100644 index 0000000..4f1c4d5 --- /dev/null +++ b/pb_migrations/1794500000_verify_link_to_web.js @@ -0,0 +1,55 @@ +/// + +// Point the verification email at the site instead of the admin console. +// +// 1794000000 translated all three account emails but deliberately left +// verification and email-change aimed at `{APP_URL}/_/#/auth/…`, on the grounds +// that "nobody has hit those yet". That was wrong on the facts: the app fires a +// verification email on every registration (auth_service.dart, fire and forget), +// and four of the nine accounts carry `verified: 1` — four family members opened +// PocketBase's admin console and clicked a button in it. +// +// Nothing is gated on `verified`: the users collection has an empty authRule, +// and five accounts including the owner's work fine unverified. The flag is kept +// because it is the only evidence an address was typed correctly, which is +// exactly the question that comes up when somebody reports a missing email. +// +// amber.petruzalekr.cz/?verify= now hosts the confirm step in Czech, the +// same way `?reset=` does. +// +// Email-change is left alone on purpose: no surface in the app or the site can +// trigger it today, so pointing it at a page nobody reaches would be motion +// without a reader. +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 +

+

Není to povinné. Bez potvrzení se přihlásíš úplně stejně, jen Richard +uvidí, že adresa opravdu funguje.

+

Amber

`; + +// What 1794000000 set, so the down migration lands where it started rather than +// on PocketBase's English default. +const PREVIOUS_BODY = `

Ahoj,

+

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

+

+ Potvrdit e-mail +

+

Amber

`; + +migrate( + (app) => { + const users = app.findCollectionByNameOrId("users"); + users.verificationTemplate.subject = VERIFY_SUBJECT; + users.verificationTemplate.body = VERIFY_BODY; + app.save(users); + }, + (app) => { + const users = app.findCollectionByNameOrId("users"); + users.verificationTemplate.subject = VERIFY_SUBJECT; + users.verificationTemplate.body = PREVIOUS_BODY; + app.save(users); + }, +); diff --git a/pb_public/index.html b/pb_public/index.html index f41c9af..4964616 100644 --- a/pb_public/index.html +++ b/pb_public/index.html @@ -131,6 +131,26 @@ + + + +
@@ -1417,6 +1437,41 @@ $("rSave").onclick=async function(){ setTimeout(function(){ $("reset").hidden=true; $("anon").hidden=false; }, 2500); }; +// ── confirming an email address from an emailed link ───────────────────────── + +/** The token the verification email put in the address bar, or null. */ +function verifyToken(){ + try { return new URLSearchParams(location.search).get("verify"); } + catch(_){ return null; } +} + +async function showVerify(){ + $("anon").hidden = true; + $("verify").hidden = false; + var r = await api("POST","/api/collections/users/confirm-verification", + { token: verifyToken() }, {anon:true}); + // Spend the token out of the address bar either way: it is single use, and a + // URL carrying one is a URL that ends up in a bookmark or a screenshot. + try { history.replaceState(null,"",location.pathname); } catch(_){} + if(!r.ok){ + // Worth saying plainly that nothing is broken: verification gates nothing, + // so a burnt or expired link costs the person exactly nothing. + $("vSub").textContent="Odkaz nezabral."; + setMsg($("vMsg"),"Nejspíš už vypršel. Nevadí, k přihlášení ověření "+ + "potřeba není.","err"); + } else { + $("vSub").textContent="Hotovo, e-mail je ověřený."; + setMsg($("vMsg"),"Můžeš se přihlásit.","ok"); + } + $("vDone").hidden=false; +} + +$("vDone").onclick=async function(){ + $("verify").hidden=true; + if (await restore()) { await enter(); return; } + $("anon").hidden=false; +}; + // ── boot ───────────────────────────────────────────────────────────────────── (async function(){ fillLangs(); @@ -1425,6 +1480,7 @@ $("rSave").onclick=async function(){ // 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 (verifyToken()) { await showVerify(); return; } if (await restore()) await enter(); })();