feat(email): PWA login sends code only; drop admin link from alert emails
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m40s

Two email changes, per the owner:

1. Code-only login from an installed PWA. A login requested while running as a
   standalone PWA now omits the one-tap confirm link from the email — the link
   would open in a separate browser whose minted session cannot reach the PWA,
   stranding the login. The code is typed in the same window instead. The client
   sends a `pwa` flag (isStandalone) on the email-code request (a new FBS field,
   threaded through the gateway); the backend omits the deeplink when it is set,
   reusing the existing deletion-code link-omission path. Non-PWA browser logins
   keep the one-tap link. No polling, no migration.

2. Security: the operator alert digest no longer embeds the admin-console (/_gm)
   URL. An admin link must never travel in an email, where a mail provider could
   cache or index it; the operator opens the console directly.

Tests: an inttest asserting the PWA login email omits the link (and a browser one
keeps it); a codec round-trip of the new pwa field; the alert-digest test flipped
to guard the admin link is absent. Docs: ARCHITECTURE + FUNCTIONAL (+ru).
This commit is contained in:
Ilia Denisov
2026-07-05 22:13:21 +02:00
parent d19609f87d
commit 061366da5a
21 changed files with 139 additions and 57 deletions
+6 -2
View File
@@ -244,7 +244,9 @@ arrive from a platform rather than completing a mandatory registration).
256-bit token stored only as its SHA-256, 12-hour TTL): a login mints a session in the
browser that opens it (magic-link), a link confirms the identity and emits a `notify`
profile-refresh to the in-app session, a change switches the account's email in place,
and a would-be merge is deferred to the interactive flow. The confirm runs on load; the token rides the URL fragment (never
and a would-be merge is deferred to the interactive flow. A login requested from an installed **PWA** omits the deeplink
entirely — its email carries the code only, typed in the same window, since the link would open
in a separate browser whose minted session cannot reach the PWA. The confirm runs on load; the token rides the URL fragment (never
sent to the server), so a plain link prefetch cannot reach it, and the manual
six-digit code is the fallback if an aggressive scanner runs the page. An
**email-login** account is created flagged `is_guest` and stays reapable until the
@@ -1041,7 +1043,9 @@ link — misses the event; while an add-email confirmation is pending the client
(`internal/adminalert`, started from `main`) emails the operator (`ADMIN_EMAIL`, from a
distinct `SMTP_RELAY_ADMIN_FROM`) when new player feedback or word complaints arrive,
**coalescing** a burst into one digest per interval; both recipients may be several
comma-separated addresses. Both paths are inert unless configured.
comma-separated addresses. The digest carries **no admin-console link** — an admin URL must
never travel in an email, where a mail provider could cache or index it; the operator opens the
console directly. Both paths are inert unless configured.
- Per-request server-side timing via gin middleware from day one (the access log
carries method, route, status, latency and the active trace id). A
client-measured RTT piggybacked on the next request is a later enhancement.