fix(BRIDGE-107): improved human verification UX

This commit is contained in:
Atanas Janeshliev
2024-11-06 16:02:05 +01:00
parent f1aef383b7
commit 7d9753e2da
7 changed files with 34 additions and 7 deletions

View File

@ -723,3 +723,13 @@ func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.Dis
func (bridge *Bridge) ModifyObservabilityHeartbeatInterval(duration time.Duration) { func (bridge *Bridge) ModifyObservabilityHeartbeatInterval(duration time.Duration) {
bridge.observabilityService.ModifyHeartbeatInterval(duration) bridge.observabilityService.ModifyHeartbeatInterval(duration)
} }
func (bridge *Bridge) ReportMessageWithContext(message string, messageCtx reporter.Context) {
if err := bridge.reporter.ReportMessageWithContext(message, messageCtx); err != nil {
logPkg.WithFields(logrus.Fields{
"err": err,
"sentryMessage": message,
"messageCtx": messageCtx,
}).Info("Error occurred when sending Report to Sentry")
}
}

View File

@ -30,7 +30,7 @@ using namespace bridgepp;
namespace { namespace {
QString const defaultKeychain = "defaultKeychain"; ///< The default keychain. QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
QString const HV_ERROR_TEMPLATE = "failed to create new API client: 422 POST https://mail-api.proton.me/auth/v4: CAPTCHA validation failed (Code=12087, Status=422)"; QString const HV_ERROR_TEMPLATE = "Human verification failed. Please try again.";
} }

View File

@ -29,6 +29,7 @@ FocusScope {
property alias username: usernameTextField.text property alias username: usernameTextField.text
property var wizard property var wizard
property string hvLinkUrl: "" property string hvLinkUrl: ""
property bool hvLinkClicked: false
signal loginAbort(string username, bool wasSignedOut) signal loginAbort(string username, bool wasSignedOut)
@ -49,6 +50,7 @@ FocusScope {
} }
passwordTextField.hidePassword(); passwordTextField.hidePassword();
secondPasswordTextField.hidePassword(); secondPasswordTextField.hidePassword();
hvLinkClicked = false;
} }
function resetViaHv() { function resetViaHv() {
usernameTextField.enabled = false; usernameTextField.enabled = false;
@ -56,6 +58,7 @@ FocusScope {
signInButton.loading = true; signInButton.loading = true;
secondPasswordButton.loading = false; secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true; secondPasswordTextField.enabled = true;
hvLinkClicked = false;
totpLayout.reset(); totpLayout.reset();
} }
@ -562,6 +565,7 @@ FocusScope {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
Qt.openUrlExternally(hvLinkUrl); Qt.openUrlExternally(hvLinkUrl);
hvLinkClicked = true;
} }
} }
} }
@ -574,7 +578,8 @@ FocusScope {
id: hVContinueButton id: hVContinueButton
Layout.fillWidth: true Layout.fillWidth: true
colorScheme: wizard.colorScheme colorScheme: wizard.colorScheme
text: qsTr("Continue") text: qsTr("Ive completed the verification")
enabled: hvLinkClicked
function checkAndSignInHv() { function checkAndSignInHv() {
console.assert(stackLayout.currentIndex === Login.RootStack.HV || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected checkInAndSignInHv") console.assert(stackLayout.currentIndex === Login.RootStack.HV || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected checkInAndSignInHv")

View File

@ -159,7 +159,10 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) {
hvDetails, hvErr := hv.VerifyAndExtractHvRequest(err) hvDetails, hvErr := hv.VerifyAndExtractHvRequest(err)
if hvErr != nil || hvDetails != nil { if hvErr != nil || hvDetails != nil {
if hvErr != nil { if hvErr != nil {
f.printAndLogError("Cannot login", hvErr) f.printAndLogError("Cannot login:", hv.ExtractionErrorMsg)
f.bridge.ReportMessageWithContext("Unable to extract HV request details", map[string]any{
"error": err.Error(),
})
return return
} }
f.promptHvURL(hvDetails) f.promptHvURL(hvDetails)

View File

@ -465,7 +465,7 @@ func (s *Service) finishLogin() {
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Code == proton.HumanValidationInvalidToken { if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Code == proton.HumanValidationInvalidToken {
s.hvDetails = nil s.hvDetails = nil
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, err.Error())) _ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.VerificationFailedErrorMsg))
return return
} }
@ -643,7 +643,10 @@ func (s *Service) monitorParentPID() {
func (s *Service) handleHvRequest(err error) { func (s *Service) handleHvRequest(err error) {
hvDet, hvErr := hv.VerifyAndExtractHvRequest(err) hvDet, hvErr := hv.VerifyAndExtractHvRequest(err)
if hvErr != nil { if hvErr != nil {
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hvErr.Error())) _ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.ExtractionErrorMsg))
s.bridge.ReportMessageWithContext("Unable to extract HV request details", map[string]any{
"error": err.Error(),
})
return return
} }

View File

@ -30,6 +30,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme" "github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/v3/internal/hv"
"github.com/ProtonMail/proton-bridge/v3/internal/kb" "github.com/ProtonMail/proton-bridge/v3/internal/kb"
"github.com/ProtonMail/proton-bridge/v3/internal/safe" "github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/service"
@ -468,7 +469,7 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty,
case proton.HumanValidationInvalidToken: case proton.HumanValidationInvalidToken:
s.hvDetails = nil s.hvDetails = nil
_ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, err.Error())) _ = s.SendEvent(NewLoginError(LoginErrorType_HV_ERROR, hv.VerificationFailedErrorMsg))
default: default:
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error())) _ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))

View File

@ -21,6 +21,11 @@ import (
"github.com/ProtonMail/go-proton-api" "github.com/ProtonMail/go-proton-api"
) )
const (
ExtractionErrorMsg = "Human verification requested, but an issue occurred. Please try again."
VerificationFailedErrorMsg = "Human verification failed. Please try again."
)
// VerifyAndExtractHvRequest expects an error request as input // VerifyAndExtractHvRequest expects an error request as input
// determines whether the given error is a Proton human verification request; if it isn't then it returns -> nil, nil (no details, no error) // determines whether the given error is a Proton human verification request; if it isn't then it returns -> nil, nil (no details, no error)
// if it is a HV req. then it tries to parse the json data and verify that the captcha method is included; if either fails -> nil, err // if it is a HV req. then it tries to parse the json data and verify that the captcha method is included; if either fails -> nil, err
@ -34,7 +39,7 @@ func VerifyAndExtractHvRequest(err error) (*proton.APIHVDetails, error) {
if errors.As(err, &protonErr) && protonErr.IsHVError() { if errors.As(err, &protonErr) && protonErr.IsHVError() {
hvDetails, hvErr := protonErr.GetHVDetails() hvDetails, hvErr := protonErr.GetHVDetails()
if hvErr != nil { if hvErr != nil {
return nil, fmt.Errorf("received HV request, but can't decode HV details") return nil, hvErr
} }
return hvDetails, nil return hvDetails, nil
} }