7a04f298d2
- update to latest telegram layer - remove some references to fields in tg.Entities that don't exist in the schema - originally added here: https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e - referenced here - https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3 - https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
255 lines
7.3 KiB
Go
255 lines
7.3 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"math/rand"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-faster/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
|
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
|
"go.mau.fi/mautrix-telegram/pkg/gotd/tgerr"
|
|
"go.mau.fi/mautrix-telegram/pkg/gotd/tgmock"
|
|
)
|
|
|
|
func getHex(t testing.TB, in string) []byte {
|
|
res, err := hex.DecodeString(in)
|
|
if err != nil {
|
|
t.Fatal("failed to get hex", err)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func TestClient_AuthSignIn(t *testing.T) {
|
|
const (
|
|
phone = "123123"
|
|
code = "1010"
|
|
password = "secret"
|
|
codeHash = "hash"
|
|
)
|
|
ctx := context.Background()
|
|
testUser := &tg.User{ID: 1}
|
|
invoker := tgmock.Invoker(func(body bin.Encoder) (bin.Encoder, error) {
|
|
switch req := body.(type) {
|
|
case *tg.UsersGetUsersRequest:
|
|
return nil, &tgerr.Error{
|
|
Code: 401,
|
|
Message: "AUTH_KEY_UNREGISTERED",
|
|
Type: "AUTH_KEY_UNREGISTERED",
|
|
}
|
|
case *tg.AuthSendCodeRequest:
|
|
settings := tg.CodeSettings{}
|
|
settings.SetCurrentNumber(true)
|
|
assert.Equal(t, &tg.AuthSendCodeRequest{
|
|
PhoneNumber: phone,
|
|
APIHash: testAppHash,
|
|
APIID: testAppID,
|
|
Settings: settings,
|
|
}, req)
|
|
return &tg.AuthSentCode{
|
|
Type: &tg.AuthSentCodeTypeApp{},
|
|
PhoneCodeHash: codeHash,
|
|
}, nil
|
|
case *tg.AuthSignInRequest:
|
|
assert.Equal(t, &tg.AuthSignInRequest{
|
|
PhoneNumber: phone,
|
|
PhoneCodeHash: codeHash,
|
|
PhoneCode: code,
|
|
}, req)
|
|
return nil, tgerr.New(401, "SESSION_PASSWORD_NEEDED")
|
|
case *tg.AccountGetPasswordRequest:
|
|
algo := &tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow{
|
|
Salt1: getHex(t, "4D11FB6BEC38F9D2546BB0F61E4F1C99A1BC0DB8F0D5F35B1291B37B213123D7ED48F3C6794D495B"),
|
|
Salt2: getHex(t, "A1B181AAFE88188680AE32860D60BB01"),
|
|
G: 3,
|
|
P: getHex(t, "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F"+
|
|
"48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37"+
|
|
"20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64"+
|
|
"2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4"+
|
|
"A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754"+
|
|
"FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4"+
|
|
"E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F"+
|
|
"0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B"),
|
|
}
|
|
pwd := &tg.AccountPassword{
|
|
NewAlgo: algo,
|
|
NewSecureAlgo: &tg.SecurePasswordKdfAlgoPBKDF2HMACSHA512iter100000{},
|
|
}
|
|
pwd.SetCurrentAlgo(algo)
|
|
return pwd, nil
|
|
case *tg.AuthCheckPasswordRequest:
|
|
// TODO(ernado): Check actual secure remote password here.
|
|
switch pwd := req.Password.(type) {
|
|
case *tg.InputCheckPasswordSRP:
|
|
assert.NotEmpty(t, pwd.A)
|
|
assert.NotEmpty(t, pwd.M1)
|
|
assert.NotEqual(t, pwd.SRPID, 0)
|
|
default:
|
|
t.Errorf("unexpectd pwd type %T", pwd)
|
|
}
|
|
return &tg.AuthAuthorization{
|
|
User: testUser,
|
|
}, nil
|
|
}
|
|
return nil, errors.New("unexpected")
|
|
})
|
|
|
|
t.Run("Manual", func(t *testing.T) {
|
|
// 1. Request code from server to device.
|
|
client := testClient(invoker)
|
|
sentCode, err := client.SendCode(ctx, phone, SendCodeOptions{CurrentNumber: true})
|
|
require.NoError(t, err)
|
|
h := sentCode.(*tg.AuthSentCode).PhoneCodeHash
|
|
require.Equal(t, codeHash, h)
|
|
|
|
// 2. Send code from device to server.
|
|
// Server is responding with 2FA password prompt.
|
|
_, signInErr := client.SignIn(ctx, phone, code, h)
|
|
require.ErrorIs(t, signInErr, ErrPasswordAuthNeeded)
|
|
|
|
// 3. Provide 2FA password.
|
|
result, err := client.Password(ctx, password)
|
|
require.NoError(t, err)
|
|
require.Equal(t, testUser, result.User)
|
|
})
|
|
|
|
flow := NewFlow(
|
|
Constant(phone, password, CodeAuthenticatorFunc(
|
|
func(ctx context.Context, _ *tg.AuthSentCode) (string, error) {
|
|
return code, nil
|
|
},
|
|
)),
|
|
SendCodeOptions{CurrentNumber: true},
|
|
)
|
|
t.Run("AuthFlow", func(t *testing.T) {
|
|
require.NoError(t, flow.Run(ctx, testClient(invoker)))
|
|
})
|
|
t.Run("IfNecessary", func(t *testing.T) {
|
|
require.NoError(t, testClient(invoker).IfNecessary(ctx, flow))
|
|
})
|
|
}
|
|
|
|
func TestClientTestAuth(t *testing.T) {
|
|
const (
|
|
codeHash = "hash"
|
|
dcID = 2
|
|
)
|
|
ctx := context.Background()
|
|
invoker := tgmock.Invoker(func(body bin.Encoder) (bin.Encoder, error) {
|
|
switch req := body.(type) {
|
|
case *tg.AuthSendCodeRequest:
|
|
assert.Equal(t, &tg.AuthSendCodeRequest{
|
|
PhoneNumber: req.PhoneNumber,
|
|
APIHash: testAppHash,
|
|
APIID: testAppID,
|
|
Settings: tg.CodeSettings{},
|
|
}, req)
|
|
return &tg.AuthSentCode{
|
|
Type: &tg.AuthSentCodeTypeApp{
|
|
Length: 6,
|
|
},
|
|
PhoneCodeHash: codeHash,
|
|
}, nil
|
|
case *tg.AuthSignInRequest:
|
|
if !strings.HasPrefix(req.PhoneNumber, "99966") {
|
|
t.Fatalf("unexpected phone number %s", req.PhoneNumber)
|
|
}
|
|
dcPart := req.PhoneNumber[5:6]
|
|
assert.Equal(t, strconv.Itoa(dcID), dcPart, "dc part of phone number")
|
|
assert.Equal(t, &tg.AuthSignInRequest{
|
|
PhoneNumber: req.PhoneNumber,
|
|
PhoneCodeHash: codeHash,
|
|
PhoneCode: strings.Repeat(dcPart, 6),
|
|
}, req)
|
|
return &tg.AuthAuthorization{
|
|
User: &tg.User{ID: 1},
|
|
}, nil
|
|
}
|
|
return nil, errors.New("unexpected")
|
|
})
|
|
require.NoError(t, NewFlow(
|
|
Test(rand.New(rand.NewSource(1)), dcID),
|
|
SendCodeOptions{},
|
|
).Run(ctx, testClient(invoker)))
|
|
}
|
|
|
|
func TestClientTestSignUp(t *testing.T) {
|
|
const (
|
|
dcID = 2
|
|
codeHash = "hash"
|
|
tosID = "foo"
|
|
)
|
|
ctx := context.Background()
|
|
invoker := tgmock.Invoker(func(body bin.Encoder) (bin.Encoder, error) {
|
|
switch req := body.(type) {
|
|
case *tg.AuthSendCodeRequest:
|
|
assert.Equal(t, &tg.AuthSendCodeRequest{
|
|
PhoneNumber: req.PhoneNumber,
|
|
APIHash: testAppHash,
|
|
APIID: testAppID,
|
|
Settings: tg.CodeSettings{},
|
|
}, req)
|
|
return &tg.AuthSentCode{
|
|
Type: &tg.AuthSentCodeTypeApp{
|
|
Length: 6,
|
|
},
|
|
PhoneCodeHash: codeHash,
|
|
}, nil
|
|
case *tg.AuthSignUpRequest:
|
|
assert.Equal(t, &tg.AuthSignUpRequest{
|
|
PhoneNumber: req.PhoneNumber,
|
|
PhoneCodeHash: codeHash,
|
|
FirstName: "Test",
|
|
LastName: "User",
|
|
}, req)
|
|
return &tg.AuthAuthorization{
|
|
User: &tg.User{ID: 1},
|
|
}, nil
|
|
case *tg.HelpAcceptTermsOfServiceRequest:
|
|
return &tg.BoolTrue{}, nil
|
|
case *tg.AuthSignInRequest:
|
|
if !strings.HasPrefix(req.PhoneNumber, "99966") {
|
|
t.Fatalf("unexpected phone number %s", req.PhoneNumber)
|
|
}
|
|
dcPart := req.PhoneNumber[5:6]
|
|
assert.Equal(t, strconv.Itoa(dcID), dcPart, "dc part of phone number")
|
|
assert.Equal(t, &tg.AuthSignInRequest{
|
|
PhoneNumber: req.PhoneNumber,
|
|
PhoneCodeHash: codeHash,
|
|
PhoneCode: strings.Repeat(dcPart, 6),
|
|
}, req)
|
|
|
|
res := &tg.AuthAuthorizationSignUpRequired{}
|
|
res.SetTermsOfService(tg.HelpTermsOfService{ID: tg.DataJSON{Data: tosID}})
|
|
|
|
return res, nil
|
|
}
|
|
return nil, errors.New("unexpected")
|
|
})
|
|
require.NoError(t, NewFlow(
|
|
Test(rand.New(rand.NewSource(1)), dcID),
|
|
SendCodeOptions{},
|
|
).Run(ctx, testClient(invoker)))
|
|
}
|
|
|
|
func TestClient_AcceptTOS(t *testing.T) {
|
|
ctx := context.Background()
|
|
mockTest(func(a *require.Assertions, mock *tgmock.Mock, client *Client) {
|
|
mock.Expect().ThenUnregistered()
|
|
a.Error(client.AcceptTOS(ctx, tg.DataJSON{
|
|
Data: `{"data":"data"}`,
|
|
}))
|
|
|
|
mock.Expect().ThenTrue()
|
|
a.NoError(client.AcceptTOS(ctx, tg.DataJSON{
|
|
Data: `{"data":"data"}`,
|
|
}))
|
|
})(t)
|
|
}
|