diff --git a/go.mod b/go.mod index 88e3bc43..fbd5eba3 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( golang.org/x/net v0.58.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260828211758-e9466a65f64c + maunium.net/go/mautrix v0.30.1-0.20260831151715-fe59fad51761 ) require ( diff --git a/go.sum b/go.sum index 78c83c9a..9aee822f 100644 --- a/go.sum +++ b/go.sum @@ -132,5 +132,5 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260828211758-e9466a65f64c h1:AakW6nCwwg1c3/XKK+M+ikCkt6IGTrzlgrZrVHNBHUA= -maunium.net/go/mautrix v0.30.1-0.20260828211758-e9466a65f64c/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260831151715-fe59fad51761 h1:hCXtZ9QLncjZnVAL0ZJ83sFWb7hQhc0qoBEf7DERYnA= +maunium.net/go/mautrix v0.30.1-0.20260831151715-fe59fad51761/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index eae2f06e..42270cae 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -332,6 +332,13 @@ func (m *MetaNativeLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) return m.proceed(ctx, nil) } +func (m *MetaNativeLogin) CancelStep(ctx context.Context) (*bridgev2.LoginStep, error) { + if err := m.SavedClient.MessengerLite.CancelLoginStep(ctx); err != nil { + return nil, err + } + return m.proceed(ctx, nil) +} + func (m *MetaNativeLogin) proceed(ctx context.Context, userInput map[string]string) (*bridgev2.LoginStep, error) { log := zerolog.Ctx(ctx).With().Str("component", "messagix").Logger() @@ -370,3 +377,4 @@ func (m *MetaNativeLogin) proceed(ctx context.Context, userInput map[string]stri var _ bridgev2.LoginProcessUserInput = (*MetaNativeLogin)(nil) var _ bridgev2.LoginProcessCookies = (*MetaNativeLogin)(nil) var _ bridgev2.LoginProcessDisplayAndWait = (*MetaNativeLogin)(nil) +var _ bridgev2.LoginProcessStepCancel = (*MetaNativeLogin)(nil) diff --git a/pkg/messagix/bloks/selenium.go b/pkg/messagix/bloks/selenium.go index 700a36c1..e3db3ae7 100644 --- a/pkg/messagix/bloks/selenium.go +++ b/pkg/messagix/bloks/selenium.go @@ -339,6 +339,7 @@ type Browser struct { AFADNotification string AFADInterval time.Duration AFADCallback func() error + MFACanGoBack bool LoginData string DisplayedURL string @@ -1450,6 +1451,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) StepID: b.stepID("otp_code"), Instructions: instructions, UserInputParams: &bridgev2.LoginUserInputParams{ + CanCancel: b.MFACanGoBack, Fields: []bridgev2.LoginInputDataField{ { ID: "otp_code", @@ -1517,6 +1519,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) StepID: b.stepID("backup_code"), Instructions: instructions, UserInputParams: &bridgev2.LoginUserInputParams{ + CanCancel: b.MFACanGoBack, Fields: []bridgev2.LoginInputDataField{ {ID: "backup_code", Name: "Backup code", Type: bridgev2.LoginInputFieldType2FACode}, }, @@ -1790,6 +1793,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) case StateChooseMFAPage: foundMethods, methodNames, numIgnored := b.profile.findMFAMethods(b.CurrentPage, log) + b.MFACanGoBack = false if len(foundMethods) == 0 { if numIgnored == 0 { @@ -1830,6 +1834,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) if err != nil { return nil, b.profile.mfaMethodTapError(chosenMethod, err) } + b.MFACanGoBack = len(foundMethods) > 1 if !b.profile.shouldContinueAfterMFAMethod(b.State) { break } @@ -1857,6 +1862,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) StepID: b.stepID("totp"), Instructions: instructions, UserInputParams: &bridgev2.LoginUserInputParams{ + CanCancel: b.MFACanGoBack, Fields: []bridgev2.LoginInputDataField{ {ID: "totp_code", Name: "Six-digit code", Type: bridgev2.LoginInputFieldType2FACode}, }, @@ -1957,7 +1963,8 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) StepID: b.stepID("afad_wait"), Instructions: b.AFADNotification, DisplayAndWaitParams: &bridgev2.LoginDisplayAndWaitParams{ - Type: bridgev2.LoginDisplayTypeNothing, + Type: bridgev2.LoginDisplayTypeNothing, + CanCancel: b.MFACanGoBack, }, } b.State = StateAFADPageWaiting @@ -1967,10 +1974,19 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) if b.AFADCallback == nil { return nil, loginerrors.AFADStopped } - time.Sleep(b.AFADInterval) + select { + case <-time.After(b.AFADInterval): + case <-ctx.Done(): + if errors.Is(context.Cause(ctx), bridgev2.ErrLoginStepCancelled) { + return nil, bridgev2.ErrLoginStepCancelled + } + return nil, fmt.Errorf("login cancelled while waiting for approval: %w", ctx.Err()) + } err := b.AFADCallback() if err != nil { - if ctxErr := ctx.Err(); ctxErr != nil { + if errors.Is(context.Cause(ctx), bridgev2.ErrLoginStepCancelled) { + return nil, bridgev2.ErrLoginStepCancelled + } else if ctxErr := ctx.Err(); ctxErr != nil { return nil, fmt.Errorf("login cancelled while waiting for approval: %w", ctxErr) } return nil, fmt.Errorf("AFAD callback: %w", err) @@ -2078,6 +2094,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) StepID: b.stepID("sms"), Instructions: instructions, UserInputParams: &bridgev2.LoginUserInputParams{ + CanCancel: b.MFACanGoBack, Fields: []bridgev2.LoginInputDataField{ {ID: "sms_code", Name: "Six-digit code", Type: bridgev2.LoginInputFieldType2FACode}, }, @@ -2220,6 +2237,7 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) StepID: b.stepID("whatsapp"), Instructions: instructions, UserInputParams: &bridgev2.LoginUserInputParams{ + CanCancel: b.MFACanGoBack, Fields: []bridgev2.LoginInputDataField{ {ID: "whatsapp_code", Name: "Six-digit code", Type: bridgev2.LoginInputFieldType2FACode}, }, @@ -2344,6 +2362,29 @@ func (b *Browser) DoLoginStep(ctx context.Context, userInput map[string]string) return step, nil } +func (b *Browser) CancelLoginStep(ctx context.Context) error { + if !b.MFACanGoBack { + return fmt.Errorf("current login step cannot be cancelled") + } + switch b.State { + case StateCodeEntryPage, StateBackupCodePage, StateTOTPPage, + StateSMSPageAfterSend, StateWhatsAppPageAfterSend, StateAFADPageWaiting: + default: + return fmt.Errorf("current login step cannot be cancelled") + } + btn := b.CurrentPage. + FindDescendant(FilterByAttribute("bk.data.TextSpan", "text", "Try another way")). + FindContainingButton() + if btn == nil { + return fmt.Errorf("couldn't find try another way button") + } + if err := btn.TapButton(ctx, b.CurrentPage.Interpreter); err != nil { + return fmt.Errorf("tapping try another way button: %w", err) + } + b.MFACanGoBack = false + return nil +} + func authenticationConfirmationPageState(page *BloksBundle) BrowserState { if page != nil && page.FindDescendant(FilterByComponent("bk.components.TextInput")) != nil { return StateAccountRecoveryPage diff --git a/pkg/messagix/messengerlite.go b/pkg/messagix/messengerlite.go index ae26bdea..457c0f55 100644 --- a/pkg/messagix/messengerlite.go +++ b/pkg/messagix/messengerlite.go @@ -398,3 +398,10 @@ func (m *MessengerLiteMethods) DoLoginSteps(ctx context.Context, userInput map[s return nil, m.convertCookies(loginRespPayload.SessionCookies), nil } + +func (m *MessengerLiteMethods) CancelLoginStep(ctx context.Context) error { + if m.browser == nil { + return fmt.Errorf("login browser is not initialized") + } + return m.browser.CancelLoginStep(ctx) +}