Companion to website#<pr>. Records Phase 1b as built, and corrects two things
the plan got wrong before anyone builds on them.
ENGAGEMENT.md
- Phase 1b step 2 said to pin the index to a case-insensitive collation. Every
_ci collation MariaDB offers here is also accent-insensitive, so that index
would refuse jose@x.com once josé@x.com existed and the de-duplication would
have cleared a legitimate account's address. The as-built block records the
generated-column design that shipped instead, and the second-order version of
the same bug that a seeded fixture caught in the de-dupe query itself.
- §0.6 named two callers of isDuplicateUsername(). There are five, and the
three it omits fail worse than the two it names.
BACKEND_DESIGN.md — the users table (already stale: it predated the player
account work), plus email_verifications and email_dedupe_report, and the five
new routes.
UPGRADE_NOTES.md — an operator entry, because the de-duplication is the kind of
quiet change this file exists for: nothing breaks, and the affected users find
out the next time they try to reset a password.
api-route-inventory.json — regenerated from the manifest; still ungated.
Co-Authored-By: Claude <noreply@anthropic.com>
140 lines
7.6 KiB
Markdown
140 lines
7.6 KiB
Markdown
# Upgrade notes — website
|
|
|
|
**Operator-facing. Newest first.** One entry per change that requires an operator to *do* something,
|
|
or that changes behaviour they would otherwise discover by accident.
|
|
|
|
Most upgrades need nothing: the schema is replayed on every boot with `IF NOT EXISTS` migrations, and
|
|
a deployment that pulls a new image and restarts is done. This file exists for the exceptions — and
|
|
the exceptions worth writing down are almost always the ones that **fail quietly**, because a loud
|
|
failure announces itself and a quiet one does not.
|
|
|
|
**Read the entries between the version you are on and the version you are moving to.** Each names the
|
|
action, whether it is required, and what happens if you do nothing.
|
|
|
|
---
|
|
|
|
## Email addresses are now unique, and some accounts may lose theirs
|
|
|
|
**Required only if the dashboard says so.** Engagement Phase 1b.
|
|
|
|
### What changed
|
|
|
|
An account's email address is now the destination for account recovery and, in time, for notification
|
|
mail, so an address may belong to **one account only**. Until now it could repeat — SSO sign-up wrote
|
|
whatever address the provider returned, without checking — so a database that has been running with
|
|
SSO enabled may well hold duplicates.
|
|
|
|
Users can now also **set and change their own address**, under *Account → Email address*, confirming
|
|
it by opening a link. Before this there was no way for anyone to change their own address at all.
|
|
|
|
### What the upgrade does on first boot
|
|
|
|
Nothing you need to trigger, and **no account is deleted**. Where several accounts share an address:
|
|
|
|
- the **earliest-created** account keeps it — not the "verified" one, because SSO used to mark an
|
|
address verified merely for existing, so that flag cannot arbitrate anything;
|
|
- every later account has its address **cleared** (set to nothing) and is listed in a report;
|
|
- the dashboard then shows a warning naming how many accounts were affected.
|
|
|
|
Case is folded — `Foo@x.com` and `foo@x.com` are the same mailbox — but **accents are not**:
|
|
`josé@x.com` and `jose@x.com` are correctly treated as two different addresses.
|
|
|
|
### What you have to do
|
|
|
|
If the warning appears, open **Admin → Users** and read the report. It names each affected account and
|
|
the address it lost. **Those users are the reason this warning exists:** they can still sign in
|
|
normally, but they can no longer receive password-reset or notification email until they set a new
|
|
address themselves, and nothing tells them that. Contact them, and point them at *Account → Email
|
|
address*.
|
|
|
|
Dismissing the warning keeps the report — it is the record of what the upgrade did, and the only place
|
|
the lost addresses survive.
|
|
|
|
### Verification email
|
|
|
|
Confirmation links are sent through the same transport as everything else, so **if outbound email is
|
|
not configured, nobody can confirm an address.** The screen says so honestly rather than claiming a
|
|
mail was sent. If you have not configured SMTP yet, see the entry below first.
|
|
|
|
### The verification setting
|
|
|
|
A new setting, *require a confirmed address before sending notification email*, is seeded **off on an
|
|
existing deployment** and **on for a fresh install**. The asymmetry is deliberate: switching it on
|
|
retroactively would silently stop mailing every user who had already opted in, on the day you
|
|
upgraded. Nothing reads it yet — it takes effect when engagement notifications ship — so there is no
|
|
hurry to change it.
|
|
|
|
### If you do nothing
|
|
|
|
The de-duplication has already run; it runs on the first boot whether or not you read this. What you
|
|
lose by ignoring it is the chance to tell the affected users, who will otherwise discover it the next
|
|
time they try to reset a password.
|
|
|
|
### Data
|
|
|
|
Nothing is dropped and no row is deleted. `users` gains `email_norm` (a generated lowercase copy of
|
|
`email`, which carries the uniqueness index) and `email_pending`. Two tables are added:
|
|
`email_verifications` and `email_dedupe_report`. Cleared addresses survive in the report.
|
|
|
|
---
|
|
|
|
## Outbound email: the Gmail connect flow is gone; configure SMTP
|
|
|
|
**Required, if this deployment currently sends email.** Engagement Phase 1.
|
|
|
|
### What changed
|
|
|
|
The "Connect Gmail" consent flow has been **removed**. Email now sends through an ordinary mail
|
|
transport whose credentials an admin types in — SMTP is the baseline and the only transport shipped.
|
|
|
|
Removed with it: `GET /api/v1/admin/email/connect/start`, `GET /api/v1/admin/email/connect/callback`,
|
|
and email's reuse of the `google` authentication provider's client id and secret. That reuse is worth
|
|
a sentence of its own, because it was a coupling nobody could see: rotating the Google **SSO** secret
|
|
silently broke outbound **mail**, with nothing on either screen relating the two. Single sign-on is
|
|
otherwise unaffected — the `google` provider row exists for SSO in its own right and is untouched.
|
|
|
|
### What you must do
|
|
|
|
On the first boot after the upgrade, `email_config.transport` backfills to `smtp` with **no
|
|
credentials**. Until you supply some, **no mail is sent** — and nothing errors, because every sender
|
|
is written to degrade politely:
|
|
|
|
- the contact form falls back to a `mailto:` link,
|
|
- an invite returns the accept link for you to send by hand,
|
|
- a password reset still answers its usual generic success,
|
|
- Team notifications are logged and dropped.
|
|
|
|
That is the risk this note exists for: **email stops and nothing complains.** The admin dashboard
|
|
shows a warning while it is true, and the warning goes away by itself once a credential is saved.
|
|
|
|
Go to **Admin → Settings → Email**, choose the posture that fits, then use **Send test** — which is
|
|
now the real verification of the whole configuration, not a formality.
|
|
|
|
| Posture | Settings | Notes |
|
|
| --- | --- | --- |
|
|
| **A relay** — recommended | The relay's host, port `587`, *Implicit TLS* **off**, your API key as the password | Mailgun, SES, Postmark and equivalents. Reputation, DKIM and bounce handling are the relay's problem. The right answer for anything with volume |
|
|
| **Gmail over SMTP** — the shortest migration | `smtp.gmail.com`, port `587`, *Implicit TLS* **off**, your Google address as the username, and an **app password** | This is the direct replacement for the removed connect flow. It needs an [app password](https://support.google.com/accounts/answer/185833) — 2-Step Verification must be on, and your ordinary account password will not work. Google's per-day send caps apply |
|
|
| **Your own MTA** | Its host, port `25`, *Implicit TLS* **off**, username and password left blank | For an operator already running mail on the same host |
|
|
|
|
Two fields cause most failures:
|
|
|
|
- **Implicit TLS** is *on* only for port **465**. On port 587 leave it **off** — the connection still
|
|
upgrades to TLS, via STARTTLS. Port 587 with it on does not error; it hangs.
|
|
- **Send from** is now typed by you rather than read back from Google, so nothing guarantees the
|
|
server will accept it. It must be an address the account is allowed to send as, or the mail is
|
|
refused for SPF/DMARC reasons that look like nothing at all. Send test names this specifically when
|
|
it happens.
|
|
|
|
### If you do nothing
|
|
|
|
Mail stays off. Nothing breaks and no data is lost — the site, the admin panel, logins, SSO and the
|
|
shard integration all behave exactly as before. The dashboard warning stays up until you either
|
|
configure a transport or press **Clear credentials**, which is how you say "this deployment
|
|
deliberately sends no mail".
|
|
|
|
### Data
|
|
|
|
Nothing is dropped. `email_config` gains `transport`, `credential_enc` and `reply_to`; the old
|
|
`provider` and `refresh_token_enc` columns are kept but no longer read. Your stored Gmail refresh
|
|
token is not used again and is cleared the first time you press **Clear credentials**.
|