Skip to content

Commit

Permalink
feat: auth status events and retry mechanism for phone and otp input
Browse files Browse the repository at this point in the history
This commit adds up retry mechanism for phone number input and otp as well
also with the 3 max trials like for 2FA password. This commit also brings
a new type of events that are supplied by AuthConversator known as "AuthStatus" events.
These events contain data for attempts left for the current event as well as
a string containing the data about on going event.

List of available auth status events:
	AuthStatusPhoneAsked        = AuthStatusEvent("phone number asked")
	AuthStatusPhoneRetrial      = AuthStatusEvent("phone number validation retrial")
	AuthStatusPhoneFailed       = AuthStatusEvent("phone number validation failed")
	AuthStatusPhoneCodeAsked    = AuthStatusEvent("phone otp asked")
	AuthStatusPhoneCodeVerified = AuthStatusEvent("phone code verified")
	AuthStatusPhoneCodeRetrial  = AuthStatusEvent("phone code verification retrial")
	AuthStatusPhoneCodeFailed   = AuthStatusEvent("phone code verification failed")
	AuthStatusPasswordAsked     = AuthStatusEvent("2fa password asked")
	AuthStatusPasswordRetrial   = AuthStatusEvent("2fa password verification retrial")
	AuthStatusPasswordFailed    = AuthStatusEvent("2fa password verification failed")
	AuthStatusSuccess           = AuthStatusEvent("authentification success")
  • Loading branch information
celestix committed Apr 21, 2024
1 parent d364b28 commit 8c779e5
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 25 deletions.
70 changes: 60 additions & 10 deletions authConversation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,41 @@ import (
"os"
)

type (
AuthStatusEvent string
AuthStatus struct {
Event AuthStatusEvent
AttemptsLeft int
}
)

func SendAuthStatus(conversator AuthConversator, event AuthStatusEvent) {
conversator.AuthStatus(AuthStatus{
Event: event,
})
}

func SendAuthStatusWithRetrials(conversator AuthConversator, event AuthStatusEvent, attemptsLeft int) {
conversator.AuthStatus(AuthStatus{
Event: event,
AttemptsLeft: attemptsLeft,
})
}

var (
AuthStatusPhoneAsked = AuthStatusEvent("phone number asked")
AuthStatusPhoneRetrial = AuthStatusEvent("phone number validation retrial")
AuthStatusPhoneFailed = AuthStatusEvent("phone number validation failed")
AuthStatusPhoneCodeAsked = AuthStatusEvent("phone otp asked")
AuthStatusPhoneCodeVerified = AuthStatusEvent("phone code verified")
AuthStatusPhoneCodeRetrial = AuthStatusEvent("phone code verification retrial")
AuthStatusPhoneCodeFailed = AuthStatusEvent("phone code verification failed")
AuthStatusPasswordAsked = AuthStatusEvent("2fa password asked")
AuthStatusPasswordRetrial = AuthStatusEvent("2fa password verification retrial")
AuthStatusPasswordFailed = AuthStatusEvent("2fa password verification failed")
AuthStatusSuccess = AuthStatusEvent("authentification success")
)

// AuthConversator is an interface for asking user for auth information.
type AuthConversator interface {
// AskPhoneNumber is called to ask user for phone number.
Expand All @@ -17,36 +52,51 @@ type AuthConversator interface {
// AskPassword is called to ask user for 2FA password.
// 2FA password should be returned.
AskPassword() (string, error)
// RetryPassword is called when the 2FA password is incorrect
// attemptsLeft is the number of attempts left.
// 2FA password should be returned.
RetryPassword(attemptsLeft int) (string, error)
// SendAuthStatus is called to inform the user about
// the status of the auth process.
// attemptsLeft is the number of attempts left for the user
// to enter the input correctly for the current auth status.
AuthStatus(authStatus AuthStatus)
}

func BasicConversator() AuthConversator {
return &basicConservator{}
}

type basicConservator struct{}
type basicConservator struct {
authStatus AuthStatus
}

func (b *basicConservator) AskPhoneNumber() (string, error) {
if b.authStatus.Event == AuthStatusPhoneRetrial {
fmt.Println("The phone number you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", b.authStatus.AttemptsLeft)
fmt.Println("Please try again....")
}
fmt.Print("Enter Phone Number: ")
return bufio.NewReader(os.Stdin).ReadString('\n')
}

func (b *basicConservator) AskPassword() (string, error) {
if b.authStatus.Event == AuthStatusPasswordRetrial {
fmt.Println("The 2FA password you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", b.authStatus.AttemptsLeft)
fmt.Println("Please try again....")
}
fmt.Print("Enter 2FA password: ")
return bufio.NewReader(os.Stdin).ReadString('\n')
}

func (b *basicConservator) AskCode() (string, error) {
if b.authStatus.Event == AuthStatusPhoneCodeRetrial {
fmt.Println("The OTP you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", b.authStatus.AttemptsLeft)
fmt.Println("Please try again....")
}
fmt.Print("Enter Code: ")
return bufio.NewReader(os.Stdin).ReadString('\n')
}

func (b *basicConservator) RetryPassword(trialsLeft int) (string, error) {
fmt.Println("The 2FA Code you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", trialsLeft)
fmt.Println("Please try again....")
return b.AskPassword()
func (b *basicConservator) AuthStatus(authStatus AuthStatus) {
b.authStatus = authStatus
}
82 changes: 72 additions & 10 deletions authHelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/gotd/td/telegram/auth"
"github.com/gotd/td/tg"
"github.com/gotd/td/tgerr"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -57,39 +58,98 @@ func authFlow(ctx context.Context, client *auth.Client, conversator AuthConversa
return errors.New("no UserAuthenticator provided")
}

phone, err := f.Auth.Phone(ctx)
if err != nil {
return errors.Wrap(err, "get phone")
var (
sentCode tg.AuthSentCodeClass
err error
)
SendAuthStatus(conversator, AuthStatusPhoneAsked)
for i := 0; i < 3; i++ {
var phone string
var err1 error
if i == 0 {
phone, err1 = f.Auth.Phone(ctx)
} else {
SendAuthStatusWithRetrials(conversator, AuthStatusPhoneAsked, 3-i)
phone, err1 = conversator.AskPhoneNumber()
}
if err1 != nil {
return errors.Wrap(err, "get phone")
}
sentCode, err = client.SendCode(ctx, phone, f.Options)
if err == nil {
SendAuthStatus(conversator, AuthStatusPhoneCodeAsked)
break
}
if tgerr.Is(err, "PHONE_NUMBER_INVALID") {
continue
}
}

sentCode, err := client.SendCode(ctx, phone, f.Options)
if err != nil {
SendAuthStatus(conversator, AuthStatusPhoneFailed)
return err
}

// phone, err := f.Auth.Phone(ctx)
// if err != nil {
// return errors.Wrap(err, "get phone")
// }

// sentCode, err := client.SendCode(ctx, phone, f.Options)
// if err != nil {
// return err
// }
switch s := sentCode.(type) {
case *tg.AuthSentCode:
hash := s.PhoneCodeHash
code, err := f.Auth.Code(ctx, s)
if err != nil {
return errors.Wrap(err, "get code")
var signInErr error
for i := 0; i < 3; i++ {
var code string
if i == 0 {
code, err = f.Auth.Code(ctx, s)
} else {
SendAuthStatusWithRetrials(conversator, AuthStatusPhoneCodeRetrial, 3-i)
code, err = conversator.AskCode()
}
if err != nil {
SendAuthStatus(conversator, AuthStatusPhoneCodeFailed)
return errors.Wrap(err, "get code")
}
_, signInErr = client.SignIn(ctx, phone, code, hash)
if signInErr == nil {
break
}
if tgerr.Is(signInErr, "PHONE_CODE_INVALID") {
continue
}
}
_, signInErr := client.SignIn(ctx, phone, code, hash)
// code, err := f.Auth.Code(ctx, s)
// if err != nil {
// return errors.Wrap(err, "get code")
// }
// _, signInErr := client.SignIn(ctx, phone, code, hash)

if errors.Is(signInErr, auth.ErrPasswordAuthNeeded) {
SendAuthStatus(conversator, AuthStatusPasswordAsked)
err = signInErr
for i := 0; err != nil && i < 3; i++ {
var password string
var err1 error
if i == 0 {
password, err1 = f.Auth.Password(ctx)
} else {
password, err1 = conversator.RetryPassword(3 - i)
SendAuthStatusWithRetrials(conversator, AuthStatusPasswordRetrial, 3-i)
password, err1 = conversator.AskPassword()
}
if err1 != nil {
return errors.Wrap(err1, "get password")
}
_, err = client.Password(ctx, password)
if err == nil {
break
}
}
if err != nil {
SendAuthStatus(conversator, AuthStatusPasswordFailed)
return errors.Wrap(err, "sign in with password")
}
return nil
Expand All @@ -100,11 +160,13 @@ func authFlow(ctx context.Context, client *auth.Client, conversator AuthConversa
}

if signInErr != nil {
SendAuthStatus(conversator, AuthStatusPhoneCodeFailed)
return errors.Wrap(signInErr, "sign in")
}
case *tg.AuthSentCodeSuccess:
switch a := s.Authorization.(type) {
case *tg.AuthAuthorization:
SendAuthStatus(conversator, AuthStatusSuccess)
// Looks that we are already authorized.
return nil
case *tg.AuthAuthorizationSignUpRequired:
Expand Down
5 changes: 5 additions & 0 deletions examples/auth-using-api-base/web/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import (
// Start a web server and wait
func Start() {
http.HandleFunc("/", setInfo)
http.HandleFunc("/getAuthStatus", getAuthStatus)
http.ListenAndServe(":9997", nil)
}

func getAuthStatus(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, authStatus)
}

// setInfo handle user info, set phone, code or passwd
func setInfo(w http.ResponseWriter, req *http.Request) {
action := req.URL.Query().Get("set")
Expand Down
24 changes: 19 additions & 5 deletions examples/auth-using-api-base/web/webauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/celestix/gotgproto"
)

var authStatus gotgproto.AuthStatus

type webAuth struct{}

var (
Expand All @@ -19,28 +21,40 @@ func GetWebAuth() gotgproto.AuthConversator {
}

func (w *webAuth) AskPhoneNumber() (string, error) {
if authStatus.Event == gotgproto.AuthStatusPhoneRetrial {
fmt.Println("The phone number you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", authStatus.AttemptsLeft)
fmt.Println("Please try again....")
}
fmt.Println("waiting for phone...")
code := <-phoneChan
return code, nil
}

func (w *webAuth) AskCode() (string, error) {
if authStatus.Event == gotgproto.AuthStatusPhoneCodeRetrial {
fmt.Println("The OTP you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", authStatus.AttemptsLeft)
fmt.Println("Please try again....")
}
fmt.Println("waiting for code...")
code := <-codeChan
return code, nil
}

func (w *webAuth) AskPassword() (string, error) {
if authStatus.Event == gotgproto.AuthStatusPasswordRetrial {
fmt.Println("The 2FA password you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", authStatus.AttemptsLeft)
fmt.Println("Please try again....")
}
fmt.Println("waiting for 2fa password...")
code := <-passwdChan
return code, nil
}

func (w *webAuth) RetryPassword(attemptsLeft int) (string, error) {
fmt.Println("The 2FA Code you just entered seems to be incorrect,")
fmt.Println("Attempts Left:", attemptsLeft)
fmt.Println("Please try again.... ")
return w.AskPassword()
func (w *webAuth) AuthStatus(authStatusIp gotgproto.AuthStatus) {
authStatus = authStatusIp
}

func ReceivePhone(phone string) {
Expand Down

0 comments on commit 8c779e5

Please sign in to comment.