gotd: handle all login token response types in QR login
Cherry-picked from https://github.com/gotd/td/commit/4c22747e9a0299b457f45084c3dbcbcbb5a7a5e7
This commit is contained in:
committed by
Tulir Asokan
parent
097211cba1
commit
09185e8e53
@@ -47,11 +47,21 @@ func (q QR) Export(ctx context.Context, exceptIDs ...int64) (Token, error) {
|
|||||||
return Token{}, errors.Wrap(err, "export")
|
return Token{}, errors.Wrap(err, "export")
|
||||||
}
|
}
|
||||||
|
|
||||||
t, ok := result.(*tg.AuthLoginToken)
|
switch t := result.(type) {
|
||||||
if !ok {
|
case *tg.AuthLoginToken:
|
||||||
|
return NewToken(t.Token, t.Expires), nil
|
||||||
|
case *tg.AuthLoginTokenSuccess:
|
||||||
|
// Token was already accepted, authentication successful
|
||||||
|
// Return empty token since no new token is needed
|
||||||
|
return Token{}, nil
|
||||||
|
case *tg.AuthLoginTokenMigrateTo:
|
||||||
|
// Migration needed
|
||||||
|
return Token{}, &MigrationNeededError{
|
||||||
|
MigrateTo: t,
|
||||||
|
}
|
||||||
|
default:
|
||||||
return Token{}, errors.Errorf("unexpected type %T", result)
|
return Token{}, errors.Errorf("unexpected type %T", result)
|
||||||
}
|
}
|
||||||
return NewToken(t.Token, t.Expires), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept accepts given token.
|
// Accept accepts given token.
|
||||||
@@ -147,6 +157,18 @@ func (q QR) Auth(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If token is empty, it means AuthLoginTokenSuccess was returned
|
||||||
|
// and authentication is already complete, but we should wait for the signal.
|
||||||
|
if token.Empty() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-loggedIn:
|
||||||
|
return q.Import(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
timer := q.clock.Timer(until(token))
|
timer := q.clock.Timer(until(token))
|
||||||
defer clock.StopTimer(timer)
|
defer clock.StopTimer(timer)
|
||||||
|
|
||||||
@@ -163,6 +185,13 @@ func (q QR) Auth(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.Empty() {
|
||||||
|
// If empty token, it means AuthLoginTokenSuccess was returned.
|
||||||
|
// QR was scanned and accepted, break to import.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
token = t
|
token = t
|
||||||
timer.Reset(until(token))
|
timer.Reset(until(token))
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package qrlogin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/gotd/neo"
|
"github.com/gotd/neo"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"rsc.io/qr"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-telegram/pkg/gotd/constant"
|
"go.mau.fi/mautrix-telegram/pkg/gotd/constant"
|
||||||
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
|
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
|
||||||
@@ -185,3 +186,140 @@ func TestQR_Auth(t *testing.T) {
|
|||||||
|
|
||||||
a.NoError(<-done)
|
a.NoError(<-done)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMigrationNeededError_Error(t *testing.T) {
|
||||||
|
a := require.New(t)
|
||||||
|
err := &MigrationNeededError{
|
||||||
|
MigrateTo: &tg.AuthLoginTokenMigrateTo{
|
||||||
|
DCID: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Equal("migration to 2 needed", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock dispatcher that implements the required interface.
|
||||||
|
type mockDispatcher struct {
|
||||||
|
handler tg.LoginTokenHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDispatcher) OnLoginToken(h tg.LoginTokenHandler) {
|
||||||
|
m.handler = h
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnLoginToken(t *testing.T) {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
t.Skip("skipping on macOS")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := require.New(t)
|
||||||
|
|
||||||
|
dispatcher := &mockDispatcher{}
|
||||||
|
loggedIn := OnLoginToken(dispatcher)
|
||||||
|
|
||||||
|
// Verify that handler was set.
|
||||||
|
a.NotNil(dispatcher.handler)
|
||||||
|
|
||||||
|
// Test the handler
|
||||||
|
ctx := context.Background()
|
||||||
|
entities := tg.Entities{}
|
||||||
|
update := &tg.UpdateLoginToken{}
|
||||||
|
|
||||||
|
// First call should send to channel.
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- dispatcher.handler(ctx, entities, update)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Should receive signal.
|
||||||
|
select {
|
||||||
|
case <-loggedIn:
|
||||||
|
// Good
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatal("should receive signal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler should return nil.
|
||||||
|
a.NoError(<-done)
|
||||||
|
|
||||||
|
// Second call when channel is full should not block.
|
||||||
|
err := dispatcher.handler(ctx, entities, update)
|
||||||
|
a.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToken_Image(t *testing.T) {
|
||||||
|
a := require.New(t)
|
||||||
|
token := NewToken([]byte("test_token"), int(time.Now().Unix()))
|
||||||
|
|
||||||
|
// Test with valid QR level.
|
||||||
|
img, err := token.Image(qr.L)
|
||||||
|
a.NoError(err)
|
||||||
|
a.NotNil(img)
|
||||||
|
|
||||||
|
// Test with different QR levels.
|
||||||
|
levels := []qr.Level{qr.L, qr.M, qr.Q, qr.H}
|
||||||
|
|
||||||
|
for _, level := range levels {
|
||||||
|
img, err := token.Image(level)
|
||||||
|
a.NoError(err)
|
||||||
|
a.NotNil(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQR_Import_WithMigration(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
a := require.New(t)
|
||||||
|
|
||||||
|
// Test with migration function.
|
||||||
|
migrateCalled := false
|
||||||
|
migrate := func(ctx context.Context, dcID int) error {
|
||||||
|
migrateCalled = true
|
||||||
|
a.Equal(2, dcID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mock, qr := testQR(t, migrate)
|
||||||
|
|
||||||
|
auth := &tg.AuthAuthorization{
|
||||||
|
User: &tg.User{ID: 10},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First call returns migration needed.
|
||||||
|
mock.ExpectCall(&tg.AuthExportLoginTokenRequest{
|
||||||
|
APIID: constant.TestAppID,
|
||||||
|
APIHash: constant.TestAppHash,
|
||||||
|
}).ThenResult(&tg.AuthLoginTokenMigrateTo{
|
||||||
|
DCID: 2,
|
||||||
|
Token: testToken.token,
|
||||||
|
}).ExpectCall(&tg.AuthImportLoginTokenRequest{
|
||||||
|
Token: testToken.token,
|
||||||
|
}).ThenResult(&tg.AuthLoginTokenSuccess{
|
||||||
|
Authorization: auth,
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := qr.Import(ctx)
|
||||||
|
a.NoError(err)
|
||||||
|
a.Equal(auth, result)
|
||||||
|
a.True(migrateCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQR_Import_MigrationError(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
a := require.New(t)
|
||||||
|
|
||||||
|
// Test with migration function that returns error,
|
||||||
|
migrate := func(ctx context.Context, dcID int) error {
|
||||||
|
return testutil.TestError()
|
||||||
|
}
|
||||||
|
|
||||||
|
mock, qr := testQR(t, migrate)
|
||||||
|
|
||||||
|
mock.ExpectCall(&tg.AuthExportLoginTokenRequest{
|
||||||
|
APIID: constant.TestAppID,
|
||||||
|
APIHash: constant.TestAppHash,
|
||||||
|
}).ThenResult(&tg.AuthLoginTokenMigrateTo{
|
||||||
|
DCID: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := qr.Import(ctx)
|
||||||
|
a.ErrorIs(err, testutil.TestError())
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ func (t Token) String() string {
|
|||||||
return base64.URLEncoding.EncodeToString(t.token)
|
return base64.URLEncoding.EncodeToString(t.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty reports whether token is empty.
|
||||||
|
func (t Token) Empty() bool {
|
||||||
|
return len(t.token) == 0
|
||||||
|
}
|
||||||
|
|
||||||
// URL returns login URL.
|
// URL returns login URL.
|
||||||
//
|
//
|
||||||
// See https://core.telegram.org/api/qr-login#exporting-a-login-token.
|
// See https://core.telegram.org/api/qr-login#exporting-a-login-token.
|
||||||
|
|||||||
Reference in New Issue
Block a user