From 87a2a2b0e28df070dab8c0caec5d4f51cb3056da Mon Sep 17 00:00:00 2001 From: onysd Date: Fri, 31 Jul 2026 16:31:06 +0300 Subject: [PATCH] fix --- internal/app/auth/service.go | 9 +++++++++ internal/app/auth/signup_state_test.go | 11 +++++++---- internal/mtprotoedge/login_email_e2e_test.go | 14 ++++++++------ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/internal/app/auth/service.go b/internal/app/auth/service.go index 1dad95c9..3797fce8 100644 --- a/internal/app/auth/service.go +++ b/internal/app/auth/service.go @@ -867,6 +867,15 @@ func (s *Service) CancelCodeForAuthKey(ctx context.Context, authKeyID [8]byte, p // email-login hash previously issued for this phone owner. Possession of only // a phone number is never sufficient to remove an authentication factor. func (s *Service) ConsumeLoginEmailReset(ctx context.Context, phone, phoneCodeHash string) (int64, error) { + // This flow exists to fall back to an SMS code when the login email is + // unreachable. Without a real phoneCodeSender configured, that "SMS code" + // is always the well-known TELESRV_DEV_AUTH_CODE (see createPhoneCode), + // so anyone who can call sendCode for a phone (no email access required) + // could strip the login-email requirement with a publicly known code. + // Refuse up front, before ClearLoginEmail runs, so nothing is mutated. + if s.phoneCodeSender == nil { + return 0, ErrCodeInvalid + } phone = normalizePhone(phone) rec, found, err := s.codes.Get(ctx, phoneCodeHash) if err != nil { diff --git a/internal/app/auth/signup_state_test.go b/internal/app/auth/signup_state_test.go index c3d539bc..99e58b31 100644 --- a/internal/app/auth/signup_state_test.go +++ b/internal/app/auth/signup_state_test.go @@ -405,7 +405,8 @@ func TestConsumeLoginEmailResetRequiresExactIssuedHash(t *testing.T) { users := &switchablePhoneOwnerStore{UserStore: baseUsers} codes := memory.NewCodeStore() delivery := &captureLoginCodeDelivery{} - svc := NewService(users, memory.NewAuthorizationStore(), codes, nil, nil, "12345", WithLoginCodeDelivery(delivery)) + svc := NewService(users, memory.NewAuthorizationStore(), codes, nil, nil, "12345", + WithLoginCodeDelivery(delivery), WithPhoneCodeDelivery(&captureOTPSender{}, 5)) seed := func(hash, channel string) { t.Helper() if err := codes.Set(ctx, hash, store.PhoneCode{ @@ -457,7 +458,7 @@ func TestConsumeLoginEmailResetRequiresExactIssuedHash(t *testing.T) { if len(delivery.requests) != 1 || delivery.requests[0].UserID != owner.ID || delivery.requests[0].PhoneCodeHash != replacementHash { t.Fatalf("replacement delivery=%+v", delivery.requests) } - if rec, found, err := codes.Get(ctx, replacementHash); err != nil || !found || rec.Version != store.PhoneCodeVersionCurrent || rec.IssuedUserID != owner.ID || rec.Channel != codeChannelPhone { + if rec, found, err := codes.Get(ctx, replacementHash); err != nil || !found || rec.Version != store.PhoneCodeVersionCurrent || rec.IssuedUserID != owner.ID || rec.Channel != codeChannelSMS { t.Fatalf("replacement code=%+v found=%v err=%v", rec, found, err) } } @@ -481,7 +482,8 @@ func TestConcurrentLoginEmailResetHasSingleConsumer(t *testing.T) { }, time.Minute); err != nil { t.Fatalf("seed code: %v", err) } - svc := NewService(users, memory.NewAuthorizationStore(), codes, nil, nil, "12345") + svc := NewService(users, memory.NewAuthorizationStore(), codes, nil, nil, "12345", + WithPhoneCodeDelivery(&captureOTPSender{}, 5)) const workers = 24 start := make(chan struct{}) errs := make(chan error, workers) @@ -542,7 +544,8 @@ func TestLoginEmailResetLocksUserAcrossOwnerTransfer(t *testing.T) { t.Fatalf("seed reset code: %v", err) } delivery := &captureLoginCodeDelivery{} - authSvc := NewService(users, memory.NewAuthorizationStore(), codes, nil, nil, "12345", WithLoginCodeDelivery(delivery)) + authSvc := NewService(users, memory.NewAuthorizationStore(), codes, nil, nil, "12345", + WithLoginCodeDelivery(delivery), WithPhoneCodeDelivery(&captureOTPSender{}, 5)) resetUserID, err := authSvc.ConsumeLoginEmailReset(ctx, ownerA.Phone, hash) if err != nil || resetUserID != ownerA.ID { t.Fatalf("ConsumeLoginEmailReset uid=%d err=%v", resetUserID, err) diff --git a/internal/mtprotoedge/login_email_e2e_test.go b/internal/mtprotoedge/login_email_e2e_test.go index f694a327..f7826427 100644 --- a/internal/mtprotoedge/login_email_e2e_test.go +++ b/internal/mtprotoedge/login_email_e2e_test.go @@ -76,6 +76,7 @@ func TestLoginEmailEndToEnd(t *testing.T) { messageStore := memory.NewMessageStore(dialogStore) updateEventStore := memory.NewUpdateEventStore() emailSender := &loginEmailTestSender{} + phoneSender := &loginEmailTestSender{} accountService := account.NewService(passwordStore, account.WithUsers(userStore), account.WithLoginEmailVerification(codeStore, emailSender, 5*time.Minute, 5, 6)) @@ -88,7 +89,8 @@ func TestLoginEmailEndToEnd(t *testing.T) { CodeLength: 6, Store: accountService, Sender: emailSender, - })) + }), + auth.WithPhoneCodeDelivery(phoneSender, 5)) deps := rpc.Deps{ Auth: authService, @@ -135,7 +137,7 @@ func TestLoginEmailEndToEnd(t *testing.T) { return err } hash := sent.(*tg.AuthSentCode).PhoneCodeHash - if _, err := raw.AuthSignIn(ctx, &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash, PhoneCode: code}); err != nil { + if _, err := raw.AuthSignIn(ctx, &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash, PhoneCode: phoneSender.code}); err != nil { return err } if _, err := raw.AuthSignUp(ctx, &tg.AuthSignUpRequest{PhoneNumber: phone, PhoneCodeHash: hash, FirstName: "Owner"}); err != nil { @@ -247,12 +249,12 @@ func TestLoginEmailEndToEnd(t *testing.T) { if !ok { return fmt.Errorf("resetLoginEmail result = %T, want *tg.AuthSentCode", resetRes) } - if _, ok := resetSent.Type.(*tg.AuthSentCodeTypeApp); !ok { - return fmt.Errorf("resetLoginEmail sentCode type = %T, want *tg.AuthSentCodeTypeApp (back to phone)", resetSent.Type) + if _, ok := resetSent.Type.(*tg.AuthSentCodeTypeSMS); !ok { + return fmt.Errorf("resetLoginEmail sentCode type = %T, want *tg.AuthSentCodeTypeSMS (back to phone, real sender configured)", resetSent.Type) } - // 用手机验证码完成登录。 - signInRes, err := raw.AuthSignIn(ctx, &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: resetSent.PhoneCodeHash, PhoneCode: code}) + // 用手机验证码完成登录(真实投递的随机码,不是固定 dev code)。 + signInRes, err := raw.AuthSignIn(ctx, &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: resetSent.PhoneCodeHash, PhoneCode: phoneSender.code}) if err != nil { return err }