168 lines
5.5 KiB
Go
168 lines
5.5 KiB
Go
// mautrix-telegram - A Matrix-Telegram puppeting bridge.
|
|
// Copyright (C) 2025 Sumner Evans
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package connector
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/rs/zerolog"
|
|
"maunium.net/go/mautrix/bridgev2"
|
|
|
|
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/auth"
|
|
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
|
)
|
|
|
|
const (
|
|
LoginStepIDPhoneNumber = "fi.mau.telegram.login.phone_number"
|
|
LoginStepIDCode = "fi.mau.telegram.login.code"
|
|
LoginStepIDCodeIncorrect = "fi.mau.telegram.login.code.incorrect"
|
|
LoginStepIDPassword = "fi.mau.telegram.login.password"
|
|
LoginStepIDPasswordIncorrect = "fi.mau.telegram.login.password.incorrect"
|
|
)
|
|
|
|
type PhoneLogin struct {
|
|
*baseLogin
|
|
phone string
|
|
hash string
|
|
codeSubmitted bool
|
|
}
|
|
|
|
var (
|
|
_ bridgev2.LoginProcessUserInput = (*PhoneLogin)(nil)
|
|
_ bridgev2.LoginProcessWithOverride = (*PhoneLogin)(nil)
|
|
)
|
|
|
|
func (pl *PhoneLogin) StartWithOverride(ctx context.Context, override *bridgev2.UserLogin) (*bridgev2.LoginStep, error) {
|
|
meta := override.Metadata.(*UserLoginMetadata)
|
|
if meta.IsBot {
|
|
return nil, fmt.Errorf("can't re-login to a bot account with phone login")
|
|
}
|
|
phone := cmp.Or(meta.LoginPhone, override.RemoteProfile.Phone)
|
|
if phone != "" {
|
|
zerolog.Ctx(ctx).Debug().Str("phone_number", phone).Msg("Using existing phone number for relogin")
|
|
return pl.submitNumber(ctx, phone)
|
|
}
|
|
zerolog.Ctx(ctx).Debug().Msg("No existing phone number for relogin, re-prompting")
|
|
return pl.Start(ctx)
|
|
}
|
|
|
|
func (pl *PhoneLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
|
return &bridgev2.LoginStep{
|
|
Type: bridgev2.LoginStepTypeUserInput,
|
|
StepID: LoginStepIDPhoneNumber,
|
|
UserInputParams: &bridgev2.LoginUserInputParams{
|
|
Fields: []bridgev2.LoginInputDataField{{
|
|
Type: bridgev2.LoginInputFieldTypePhoneNumber,
|
|
ID: LoginStepIDPhoneNumber,
|
|
Name: "Phone number",
|
|
Description: "Include the country code with +",
|
|
}},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (pl *PhoneLogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
|
|
if pl.client == nil {
|
|
return pl.submitNumber(ctx, input[LoginStepIDPhoneNumber])
|
|
} else if pl.codeSubmitted {
|
|
return pl.submitPassword(ctx, input[LoginStepIDPassword], pl.phone)
|
|
} else {
|
|
return pl.submitCode(ctx, input[LoginStepIDCode])
|
|
}
|
|
}
|
|
|
|
var phoneLoginStep = &bridgev2.LoginStep{
|
|
Type: bridgev2.LoginStepTypeUserInput,
|
|
StepID: LoginStepIDCode,
|
|
UserInputParams: &bridgev2.LoginUserInputParams{
|
|
Fields: []bridgev2.LoginInputDataField{{
|
|
Type: bridgev2.LoginInputFieldType2FACode,
|
|
ID: LoginStepIDCode,
|
|
Name: "Code",
|
|
Description: "The code was sent to the Telegram app on your phone",
|
|
}},
|
|
},
|
|
}
|
|
|
|
var phoneCodeIncorrectStep = &bridgev2.LoginStep{
|
|
Type: bridgev2.LoginStepTypeUserInput,
|
|
StepID: LoginStepIDCodeIncorrect,
|
|
Instructions: "Incorrect code",
|
|
UserInputParams: &bridgev2.LoginUserInputParams{
|
|
Fields: []bridgev2.LoginInputDataField{{
|
|
Type: bridgev2.LoginInputFieldType2FACode,
|
|
ID: LoginStepIDCode,
|
|
Name: "Code",
|
|
}},
|
|
},
|
|
}
|
|
|
|
func (pl *PhoneLogin) submitNumber(ctx context.Context, phone string) (*bridgev2.LoginStep, error) {
|
|
if phone == "" {
|
|
return nil, fmt.Errorf("phone number is empty")
|
|
}
|
|
log := zerolog.Ctx(ctx).With().Str("component", "phone login").Logger()
|
|
ctx = log.WithContext(ctx)
|
|
pl.phone = phone
|
|
err := pl.makeClient(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sentCode, err := pl.client.Auth().SendCode(ctx, pl.phone, auth.SendCodeOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch s := sentCode.(type) {
|
|
case *tg.AuthSentCode:
|
|
pl.hash = s.PhoneCodeHash
|
|
return phoneLoginStep, nil
|
|
case *tg.AuthSentCodeSuccess:
|
|
switch authorization := s.Authorization.(type) {
|
|
case *tg.AuthAuthorization:
|
|
return pl.finalizeLogin(ctx, authorization, &UserLoginMetadata{LoginPhone: pl.phone})
|
|
case *tg.AuthAuthorizationSignUpRequired:
|
|
return nil, ErrSignUpNotSupported
|
|
default:
|
|
return nil, fmt.Errorf("unexpected authorization type: %T", sentCode)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unexpected sent code type: %T", sentCode)
|
|
}
|
|
}
|
|
|
|
func (pl *PhoneLogin) submitCode(ctx context.Context, code string) (*bridgev2.LoginStep, error) {
|
|
if pl.client == nil {
|
|
return nil, fmt.Errorf("unexpected state: client is nil when submitting phone code")
|
|
}
|
|
authorization, err := pl.client.Auth().SignIn(ctx, pl.phone, code, pl.hash)
|
|
if errors.Is(err, auth.ErrPasswordAuthNeeded) {
|
|
pl.codeSubmitted = true
|
|
return passwordLoginStep, nil
|
|
} else if errors.Is(err, auth.ErrPhoneCodeInvalid) {
|
|
return phoneCodeIncorrectStep, nil
|
|
} else if errors.Is(err, &auth.SignUpRequired{}) {
|
|
return nil, ErrSignUpNotSupported
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("failed to submit code: %w", err)
|
|
}
|
|
return pl.finalizeLogin(ctx, authorization, &UserLoginMetadata{LoginPhone: pl.phone})
|
|
}
|