From b1f976fd7a5a221da03ab626503e6ffa161a69b0 Mon Sep 17 00:00:00 2001 From: smail Date: Tue, 6 Oct 2026 03:00:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E7=95=99=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E9=99=90=E6=B5=81=E5=AD=98=E5=82=A8=E6=95=85=E9=9A=9C=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0=E4=BE=BF=E4=BA=8E=E6=8E=92=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/services/verification/service.go | 2 +- backend/services/verification/service_test.go | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/services/verification/service.go b/backend/services/verification/service.go index ebfdd25..5ed7bfe 100644 --- a/backend/services/verification/service.go +++ b/backend/services/verification/service.go @@ -70,7 +70,7 @@ func (s *Service) Issue(request IssueRequest, remoteIP string) (Challenge, error allowed, release, limitErr := s.allow(request, remoteIP) if limitErr != nil { s.record(EventInput{Purpose: request.Purpose, Action: models.VerificationActionIssue, Outcome: models.VerificationOutcomeFailure, Account: request.Account, Email: request.Email, RemoteIP: remoteIP}) - return Challenge{}, ErrServiceDown + return Challenge{}, fmt.Errorf("%w: %w", ErrServiceDown, limitErr) } if !allowed { s.record(EventInput{Purpose: request.Purpose, Action: models.VerificationActionIssue, Outcome: models.VerificationOutcomeBlocked, Account: request.Account, Email: request.Email, RemoteIP: remoteIP}) diff --git a/backend/services/verification/service_test.go b/backend/services/verification/service_test.go index deff19d..2030686 100644 --- a/backend/services/verification/service_test.go +++ b/backend/services/verification/service_test.go @@ -44,6 +44,33 @@ func TestServiceRequiresDelivery(t *testing.T) { } } +func TestServicePreservesRateLimitFailureWithoutSending(t *testing.T) { + store, err := NewStore(4) + if err != nil { + t.Fatal(err) + } + sent := false + service, err := NewService(store, func(_ Challenge, _ string) error { + sent = true + return nil + }, nil) + if err != nil { + t.Fatal(err) + } + service.SetRateLimitStore(&PersistentRateLimiter{}) + _, err = service.Issue(IssueRequest{ + Purpose: PurposeExternalEmailBind, + Account: "user", + Email: "user@example.com", + }, "192.0.2.10") + if !errors.Is(err, ErrServiceDown) || !errors.Is(err, ErrRateLimitStoreUnavailable) { + t.Fatalf("error chain lost: %v", err) + } + if sent { + t.Fatal("delivery ran after rate limit storage failed") + } +} + func TestServiceAppliesIndependentAccountEmailAndIPLimits(t *testing.T) { store, err := NewStore(64) if err != nil {