From 77a18a3cc1d266997f8054632988c474302b6a07 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 3 Jul 2026 05:18:24 +0200 Subject: [PATCH] fix(account): clear the guest flag on a deeplink email link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ConfirmByToken attached the confirmed email to the account but, on the link path, skipped ClearGuest — so a guest who bound an email via the one-tap deeplink stayed a guest (the code-based flow clears it in the link service). Clear the flag on a free link too, promoting the guest to a durable account; the profile live event then refreshes the open session. Integration test added. --- backend/internal/account/email.go | 5 +++++ backend/internal/inttest/email_test.go | 29 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/backend/internal/account/email.go b/backend/internal/account/email.go index ecfc967..4cfdf9a 100644 --- a/backend/internal/account/email.go +++ b/backend/internal/account/email.go @@ -319,6 +319,11 @@ func (s *EmailService) ConfirmByToken(ctx context.Context, token string) (LinkCo if err := s.store.confirmEmailIdentity(ctx, pend.id, pend.accountID, pend.email, s.now()); err != nil { return LinkConfirmation{}, err } + // Binding the first email promotes a guest to a durable account, matching the + // code-based link flow (which clears the guest flag in the link service). + if err := s.store.ClearGuest(ctx, pend.accountID); err != nil { + return LinkConfirmation{}, err + } return LinkConfirmation{Purpose: purposeLink, Account: pend.accountID}, nil default: return LinkConfirmation{}, fmt.Errorf("account: unsupported confirmation purpose %q", pend.purpose) diff --git a/backend/internal/inttest/email_test.go b/backend/internal/inttest/email_test.go index ea3940b..667edd1 100644 --- a/backend/internal/inttest/email_test.go +++ b/backend/internal/inttest/email_test.go @@ -394,3 +394,32 @@ func TestEmailAccountSeedsDisplayName(t *testing.T) { t.Errorf("display name = %q, want the email local part %q", acc.DisplayName, local) } } + +// TestConfirmByTokenLinkClearsGuest: binding an email to a guest via the deeplink +// promotes the guest to a durable account (the deeplink path must match the +// code-based link flow, which clears the guest flag). +func TestConfirmByTokenLinkClearsGuest(t *testing.T) { + ctx := context.Background() + store := account.NewStore(testDB) + mailer := &capturingMailer{} + svc := account.NewEmailService(store, mailer, "https://erudit-game.ru") + guest, err := store.ProvisionGuest(ctx, "") + if err != nil { + t.Fatalf("provision guest: %v", err) + } + email := "guest-link-" + uuid.NewString() + "@example.com" + + if err := svc.RequestLinkCode(ctx, guest.ID, email); err != nil { + t.Fatalf("request link: %v", err) + } + if _, err := svc.ConfirmByToken(ctx, tokenFromMail(t, mailer.lastBody)); err != nil { + t.Fatalf("confirm by token: %v", err) + } + acc, err := store.GetByID(ctx, guest.ID) + if err != nil { + t.Fatalf("get: %v", err) + } + if acc.IsGuest { + t.Error("linking an email via the deeplink must promote the guest to durable") + } +}