package auth import ( "bytes" "context" "errors" "strings" "testing" "time" mtcrypto "github.com/gotd/td/crypto" "telesrv/internal/domain" "telesrv/internal/store" "telesrv/internal/store/memory" ) func TestBindTempAuthKeyValidatesEncryptedMessage(t *testing.T) { ctx := context.Background() keys := memory.NewAuthKeyStore() tempBindings := memory.NewTempAuthKeyBindingStore() permKey := testAuthKey(0x11) tempKey := testAuthKey(0x55) saveAuthKey(t, keys, permKey) saveAuthKey(t, keys, tempKey) svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), keys, tempBindings, "12345") const ( nonce = int64(0x12345678) sessionID = int64(0x1020304050) msgID = int64(0x0102030405060708) ) expiresAt := int(time.Now().Add(time.Hour).Unix()) encrypted, err := mtcrypto.EncryptBindMessage( bytes.NewReader(bytes.Repeat([]byte{0xCD}, 128)), permKey, msgID, &mtcrypto.BindAuthKeyInner{ Nonce: nonce, TempAuthKeyID: tempKey.IntID(), PermAuthKeyID: permKey.IntID(), TempSessionID: sessionID, ExpiresAt: expiresAt, }, ) if err != nil { t.Fatalf("encrypt bind message: %v", err) } err = svc.BindTempAuthKey(ctx, sessionID, domain.TempAuthKeyBinding{ TempAuthKeyID: tempKey.ID, PermAuthKeyID: permKey.IntID(), Nonce: nonce, ExpiresAt: expiresAt, EncryptedMessage: encrypted, }) if err != nil { t.Fatalf("BindTempAuthKey valid message: %v", err) } err = svc.BindTempAuthKey(ctx, sessionID+1, domain.TempAuthKeyBinding{ TempAuthKeyID: tempKey.ID, PermAuthKeyID: permKey.IntID(), Nonce: nonce, ExpiresAt: expiresAt, EncryptedMessage: encrypted, }) if !errors.Is(err, ErrEncryptedMessageInvalid) { t.Fatalf("BindTempAuthKey wrong session err = %v, want ErrEncryptedMessageInvalid", err) } } func TestResolveAuthKeyUsesValidTempBinding(t *testing.T) { ctx := context.Background() tempBindings := memory.NewTempAuthKeyBindingStore() permKey := testAuthKey(0x11) tempKey := testAuthKey(0x55) svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), nil, tempBindings, "12345") if err := tempBindings.Save(ctx, domain.TempAuthKeyBinding{ TempAuthKeyID: tempKey.ID, PermAuthKeyID: permKey.IntID(), ExpiresAt: int(time.Now().Add(time.Hour).Unix()), }); err != nil { t.Fatalf("save temp binding: %v", err) } got, ok, err := svc.ResolveAuthKey(ctx, tempKey.ID) if err != nil { t.Fatalf("ResolveAuthKey: %v", err) } if !ok || got != permKey.ID { t.Fatalf("resolved = %x ok=%v, want perm %x", got, ok, permKey.ID) } } func TestPhoneCodeAcceptsTDesktopDigitsOnlySignIn(t *testing.T) { ctx := context.Background() users := memory.NewUserStore() authz := memory.NewAuthorizationStore() svc := NewService(users, authz, memory.NewCodeStore(), nil, nil, "12345") hash, err := svc.SendCode(ctx, "+15550004310") if err != nil { t.Fatalf("SendCode: %v", err) } _, _, needSignUp, err := svc.SignIn(ctx, domain.Authorization{}, "15550004310", hash, "12345") if err != nil { t.Fatalf("SignIn with digits-only phone: %v", err) } if !needSignUp { t.Fatal("SignIn needSignUp = false, want true") } u, _, err := svc.SignUp(ctx, domain.Authorization{}, "+1 555 000 4310", hash, "Test", "User") if err != nil { t.Fatalf("SignUp with formatted phone: %v", err) } if u.Phone != "15550004310" { t.Fatalf("created phone = %q, want normalized digits", u.Phone) } if u.ID != domain.UserIDSequenceBase { t.Fatalf("created user id = %d, want base %d", u.ID, domain.UserIDSequenceBase) } } func TestMultipleAuthKeysKeepSeparateUsers(t *testing.T) { ctx := context.Background() authz := memory.NewAuthorizationStore() svc := NewService(memory.NewUserStore(), authz, memory.NewCodeStore(), nil, nil, "12345") var key1, key2 [8]byte key1[0] = 1 key2[0] = 2 hash1, err := svc.SendCode(ctx, "+15550005001") if err != nil { t.Fatalf("SendCode user1: %v", err) } user1, _, err := svc.SignUp(ctx, domain.Authorization{AuthKeyID: key1}, "+15550005001", hash1, "One", "") if err != nil { t.Fatalf("SignUp user1: %v", err) } hash2, err := svc.SendCode(ctx, "+15550005002") if err != nil { t.Fatalf("SendCode user2: %v", err) } user2, _, err := svc.SignUp(ctx, domain.Authorization{AuthKeyID: key2}, "+15550005002", hash2, "Two", "") if err != nil { t.Fatalf("SignUp user2: %v", err) } got1, found, err := svc.UserID(ctx, key1) if err != nil || !found || got1 != user1.ID { t.Fatalf("key1 user = %d found=%v err=%v, want %d", got1, found, err, user1.ID) } got2, found, err := svc.UserID(ctx, key2) if err != nil || !found || got2 != user2.ID { t.Fatalf("key2 user = %d found=%v err=%v, want %d", got2, found, err, user2.ID) } if got1 == got2 { t.Fatalf("auth keys mapped to same user id %d", got1) } } func TestLogOutThenSignInSameAuthKeySwitchesUser(t *testing.T) { ctx := context.Background() authz := memory.NewAuthorizationStore() svc := NewService(memory.NewUserStore(), authz, memory.NewCodeStore(), nil, nil, "12345") var key [8]byte key[0] = 9 hash1, err := svc.SendCode(ctx, "+15550006001") if err != nil { t.Fatalf("SendCode user1: %v", err) } user1, _, err := svc.SignUp(ctx, domain.Authorization{AuthKeyID: key}, "+15550006001", hash1, "One", "") if err != nil { t.Fatalf("SignUp user1: %v", err) } if got, found, err := svc.UserID(ctx, key); err != nil || !found || got != user1.ID { t.Fatalf("initial auth user = %d found=%v err=%v, want %d", got, found, err, user1.ID) } if err := svc.LogOut(ctx, key); err != nil { t.Fatalf("LogOut: %v", err) } if got, found, err := svc.UserID(ctx, key); err != nil || found || got != 0 { t.Fatalf("after logout user = %d found=%v err=%v, want none", got, found, err) } hash2, err := svc.SendCode(ctx, "+15550006002") if err != nil { t.Fatalf("SendCode user2: %v", err) } user2, _, err := svc.SignUp(ctx, domain.Authorization{AuthKeyID: key}, "+15550006002", hash2, "Two", "") if err != nil { t.Fatalf("SignUp user2: %v", err) } if got, found, err := svc.UserID(ctx, key); err != nil || !found || got != user2.ID { t.Fatalf("after switch user = %d found=%v err=%v, want %d", got, found, err, user2.ID) } if user1.ID == user2.ID { t.Fatalf("user ids did not change after switch: %d", user1.ID) } } func TestSignUpWritesOfficialLoginMessage(t *testing.T) { ctx := context.Background() dialogs := memory.NewDialogStore() messages := memory.NewMessageStore(dialogs) svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), nil, nil, "12345", WithLoginMessages(messages, dialogs)) hash, err := svc.SendCode(ctx, "+15550004311") if err != nil { t.Fatalf("SendCode: %v", err) } u, msg, err := svc.SignUp(ctx, domain.Authorization{}, "+15550004311", hash, "Test", "User") if err != nil { t.Fatalf("SignUp: %v", err) } list, err := dialogs.ListByUser(ctx, u.ID, domain.DialogFilter{Limit: 10}) if err != nil { t.Fatalf("ListByUser: %v", err) } if len(list.Dialogs) != 1 || list.Dialogs[0].Peer.ID != domain.OfficialSystemUserID { t.Fatalf("dialogs = %+v, want official system dialog", list.Dialogs) } if len(list.Users) != 1 || list.Users[0].ID != domain.OfficialSystemUserID || !list.Users[0].Verified || !list.Users[0].Support { t.Fatalf("users = %+v, want verified support system user", list.Users) } if msg.ID == 0 || !strings.Contains(msg.Body, "Login code: 12345") { t.Fatalf("login message = %+v, want returned official login code message", msg) } if len(list.Messages) != 1 || !strings.Contains(list.Messages[0].Body, "Login code: 12345") { t.Fatalf("messages = %+v, want login code message", list.Messages) } if list.Dialogs[0].TopMessage != list.Messages[0].ID || list.Dialogs[0].UnreadCount != 1 { t.Fatalf("dialog top/unread = %+v, message = %+v", list.Dialogs[0], list.Messages[0]) } } func TestSignInExistingTwoFactorAccountNeedsPassword(t *testing.T) { ctx := context.Background() passwords := memory.NewPasswordStore() svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), nil, nil, "12345", WithPasswords(passwords)) var key [8]byte key[0] = 7 hash, err := svc.SendCode(ctx, "+15550004312") if err != nil { t.Fatalf("SendCode signup: %v", err) } u, _, err := svc.SignUp(ctx, domain.Authorization{AuthKeyID: key}, "+15550004312", hash, "Two", "Factor") if err != nil { t.Fatalf("SignUp: %v", err) } if err := svc.LogOut(ctx, key); err != nil { t.Fatalf("LogOut: %v", err) } if err := passwords.Save(ctx, u.ID, domain.PasswordSettings{HasPassword: true}); err != nil { t.Fatalf("save password settings: %v", err) } hash, err = svc.SendCode(ctx, "+15550004312") if err != nil { t.Fatalf("SendCode signin: %v", err) } got, _, needSignUp, err := svc.SignIn(ctx, domain.Authorization{AuthKeyID: key}, "+15550004312", hash, "12345") if !errors.Is(err, domain.ErrSessionPasswordNeeded) { t.Fatalf("SignIn err = %v, want ErrSessionPasswordNeeded", err) } if needSignUp || got.ID != u.ID { t.Fatalf("SignIn user=%+v needSignUp=%v, want existing 2FA user", got, needSignUp) } bound, found, err := svc.UserID(ctx, key) if err != nil || !found || bound != u.ID { t.Fatalf("UserID after password-needed = %d found=%v err=%v, want %d", bound, found, err, u.ID) } } func testAuthKey(seed byte) mtcrypto.AuthKey { var raw mtcrypto.Key for i := range raw { raw[i] = seed + byte(i) } return raw.WithID() } func saveAuthKey(t *testing.T, keys store.AuthKeyStore, key mtcrypto.AuthKey) { t.Helper() var value [256]byte copy(value[:], key.Value[:]) if err := keys.Save(context.Background(), store.AuthKeyData{ID: key.ID, Value: value}); err != nil { t.Fatalf("save auth key: %v", err) } }