owpengram-server/internal/app/auth/service_test.go
A 6dc42942c8 auth: complete account authorization flows
(cherry picked from commit 04f4527df32ad5c35720cccc41d27fe51549612f)
2026-06-16 01:43:31 +08:00

298 lines
9.6 KiB
Go

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)
}
}