fix(realunit): confirm existing-shareholder registrations without an email that never arrives#4283
Conversation
…email that never arrives Aktionariat's registerUser answers "Existing user found, updated your address." when the signed email already belongs to a share-register shareholder: it updates that shareholder's wallet in place and sends NO confirmation email (a newly registered email instead gets "Confirmation email sent to ..."). Since #4215 gates buy quotes on a confirmed registration email, those registrations were persisted COMPLETED with requiresEmailConfirmation=true and hang forever on a confirmation link that is never sent. Persist an existing-shareholder registration with requiresEmailConfirmation=false (Aktionariat's match IS the confirmation), and backfill the rows already stuck before the fix, keyed on the durable Aktionariat/Registration log evidence.
|
The isExistingShareholderResponse guard matched "existing user found" as a free substring of Aktionariat's registerUser reply. Because the alternative "Confirmation email sent to <email>" reply echoes the raw registrant email, an address embedding those words (e.g. a quoted local part) could spoof the marker and un-gate a registration whose confirmation email was in fact sent and never confirmed. Match it as a start-anchored prefix instead (the real reply is exactly "Existing user found, updated your address.") and add a regression test pinning the substring-spoof case as still gated.
|
Reviewed this end-to-end (incl. a read-only check against prod + the RealUnit ponder) and pushed one hardening follow-up commit (e8784c7). The core fix and the backfill migration are sound — the migration predicate matches exactly the 6 stuck rows as claimed, and all 6 are on-chain REALU holders in the ponder that held shares before they registered, so the "existing shareholder = confirmation" premise holds for them (reg 143, correctly not matched, holds no REALU). What the follow-up changes and why
The commit anchors the match to the message start ( One related case this commit deliberately does NOT fix (for a separate decision)
Options if we want to close it (suggest a follow-up issue, not blocking this PR):
Not blocking; the migration and the anchored marker are safe to ship as-is. |
Problem
Since #4215 buy quotes are gated on a confirmed registration email. That gate assumes Aktionariat sends a confirmation email on every
registerUserforward. It does not: when the signed email already belongs to an existing share-register shareholder, Aktionariat answersupdates that shareholder's wallet in place, and sends no confirmation email. A newly registered email instead gets
Confirmation email sent to <email>.Those existing-shareholder registrations were persisted
COMPLETEDwithrequiresEmailConfirmation = true, so they hang forever on a confirmation link that never arrives and the customer cannot buy.Prod evidence (registration logs since 11.07): every one of the 6
Existing user foundresponses is stuck (regs 116, 119, 125, 129, 130, 141), everyConfirmation email sentresponse confirmed within minutes, zero exceptions, ~1-2 new stuck cases/day.Fix
Existing user foundmarker in theregisterUserresponse and persist the completed registration withrequiresEmailConfirmation = false. Aktionariat's authoritative match of an existing shareholder is the confirmation, so the row is un-gated exactly like a grandfathered one (emailConfirmed = requiresEmailConfirmation === false || confirmedDate != null). A normalConfirmation email sentresponse stays gated.Aktionariat/Registrationlog evidence (response.message= "Existing user found"). Only touches active,COMPLETED, still-gated, unconfirmed rows. Idempotent;down()is an intentional no-op (forward-only data correction).Verified against prod: the migration predicate matches exactly the 6 existing-shareholder rows and correctly leaves reg 143 (a genuine
Confirmation email sentcase the customer simply hasn't confirmed yet) gated.Scope / not included
emailConfirmed/confirmedDateread-back DTO is unchanged) → no app or services pair-PR needed.Test
realunit.service.spec.ts(246 pass) incl. two new cases: existing-shareholder response → un-gated;Confirmation email sentresponse → stays gated.