forked from Silverfish/proton-bridge
feat(BRIDGE-14): HV3 implementation - GUI & CLI; ownership verification & CAPTCHA are supported
This commit is contained in:
@ -810,6 +810,18 @@ void QMLBackend::login(QString const &username, QString const &password) const {
|
||||
)
|
||||
}
|
||||
|
||||
void QMLBackend::loginHv(QString const &username, QString const &password) const {
|
||||
HANDLE_EXCEPTION(
|
||||
if (username.compare("coco@bandicoot", Qt::CaseInsensitive) == 0) {
|
||||
throw Exception("User requested bridge-gui to crash by trying to log as coco@bandicoot",
|
||||
"This error exists for test purposes and should be ignored.", __func__, tailOfLatestBridgeLog(app().sessionID()));
|
||||
}
|
||||
app().grpc().loginHv(username, password);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] username The username.
|
||||
@ -1334,6 +1346,8 @@ void QMLBackend::connectGrpcEvents() {
|
||||
connect(client, &GRPCClient::login2PasswordErrorAbort, this, &QMLBackend::login2PasswordErrorAbort);
|
||||
connect(client, &GRPCClient::loginFinished, this, &QMLBackend::onLoginFinished);
|
||||
connect(client, &GRPCClient::loginAlreadyLoggedIn, this, &QMLBackend::onLoginAlreadyLoggedIn);
|
||||
connect(client, &GRPCClient::loginHvRequested, this, &QMLBackend::loginHvRequested);
|
||||
connect(client, &GRPCClient::loginHvError, this, &QMLBackend::loginHvError);
|
||||
|
||||
// update events
|
||||
connect(client, &GRPCClient::updateManualError, this, &QMLBackend::updateManualError);
|
||||
|
||||
@ -183,6 +183,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
||||
void changeColorScheme(QString const &scheme); ///< Slot for the change of the theme.
|
||||
void setDiskCachePath(QUrl const &path) const; ///< Slot for the change of the disk cache path.
|
||||
void login(QString const &username, QString const &password) const; ///< Slot for the login button (initial login).
|
||||
void loginHv(QString const &username, QString const &password) const; ///< Slot for the login button (after HV challenge completed).
|
||||
void login2FA(QString const &username, QString const &code) const; ///< Slot for the login button (2FA login).
|
||||
void login2Password(QString const &username, QString const &password) const; ///< Slot for the login button (mailbox password login).
|
||||
void loginAbort(QString const &username) const; ///< Slot for the login abort procedure.
|
||||
@ -238,6 +239,8 @@ signals: // Signals received from the Go backend, to be forwarded to QML
|
||||
void login2PasswordErrorAbort(QString const &errorMsg); ///< Signal for the 'login2PasswordErrorAbort' gRPC stream event.
|
||||
void loginFinished(int index, bool wasSignedOut); ///< Signal for the 'loginFinished' gRPC stream event.
|
||||
void loginAlreadyLoggedIn(int index); ///< Signal for the 'loginAlreadyLoggedIn' gRPC stream event.
|
||||
void loginHvRequested(QString const &hvUrl); ///< Signal for the 'loginHvRequested' gRPC stream event.
|
||||
void loginHvError(QString const &errorMsg); ///< Signal for the 'loginHvError' gRPC stream event.
|
||||
void updateManualReady(QString const &version); ///< Signal for the 'updateManualReady' gRPC stream event.
|
||||
void updateManualRestartNeeded(); ///< Signal for the 'updateManualRestartNeeded' gRPC stream event.
|
||||
void updateManualError(); ///< Signal for the 'updateManualError' gRPC stream event.
|
||||
|
||||
@ -60,7 +60,7 @@ QtObject {
|
||||
target: Backend
|
||||
}
|
||||
}
|
||||
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion]
|
||||
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent]
|
||||
property Notification alreadyLoggedIn: Notification {
|
||||
brief: qsTr("Already signed in")
|
||||
description: qsTr("This account is already signed in.")
|
||||
@ -1130,6 +1130,27 @@ QtObject {
|
||||
target: Backend
|
||||
}
|
||||
}
|
||||
property Notification hvErrorEvent: Notification {
|
||||
group: Notifications.Group.Configuration
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
|
||||
action: Action {
|
||||
text: qsTr("OK")
|
||||
onTriggered: {
|
||||
root.hvErrorEvent.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onLoginHvError(errorMsg) {
|
||||
root.hvErrorEvent.active = true;
|
||||
root.hvErrorEvent.description = errorMsg;
|
||||
}
|
||||
target: Backend
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
signal askChangeAllMailVisibility(var isVisibleNow)
|
||||
signal askDeleteAccount(var user)
|
||||
|
||||
@ -20,12 +20,14 @@ FocusScope {
|
||||
enum RootStack {
|
||||
Login,
|
||||
TOTP,
|
||||
MailboxPassword
|
||||
MailboxPassword,
|
||||
HV
|
||||
}
|
||||
|
||||
property alias currentIndex: stackLayout.currentIndex
|
||||
property alias username: usernameTextField.text
|
||||
property var wizard
|
||||
property string hvLinkUrl: ""
|
||||
|
||||
signal loginAbort(string username, bool wasSignedOut)
|
||||
|
||||
@ -47,6 +49,14 @@ FocusScope {
|
||||
passwordTextField.hidePassword();
|
||||
secondPasswordTextField.hidePassword();
|
||||
}
|
||||
function resetViaHv() {
|
||||
usernameTextField.enabled = false;
|
||||
passwordTextField.enabled = false;
|
||||
signInButton.loading = true;
|
||||
secondPasswordButton.loading = false;
|
||||
secondPasswordTextField.enabled = true;
|
||||
totpLayout.reset();
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
@ -124,6 +134,18 @@ FocusScope {
|
||||
else
|
||||
errorLabel.text = qsTr("Incorrect login credentials");
|
||||
}
|
||||
function onLoginHvRequested(hvUrl) {
|
||||
console.assert(stackLayout.currentIndex === Login.RootStack.Login || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected loginHvRequested");
|
||||
stackLayout.currentIndex = Login.RootStack.HV;
|
||||
hvUsernameLabel.text = usernameTextField.text;
|
||||
hvLinkUrl = hvUrl;
|
||||
}
|
||||
function onLoginHvError(_) {
|
||||
console.assert(stackLayout.currentIndex === Login.RootStack.Login || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected onLoginHvInvalidTokenError");
|
||||
stackLayout.currentIndex = Login.RootStack.Login;
|
||||
root.resetViaHv();
|
||||
root.reset()
|
||||
}
|
||||
|
||||
target: Backend
|
||||
}
|
||||
@ -475,5 +497,112 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: hvLayout
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: ProtonStyle.wizard_spacing_extra_large
|
||||
|
||||
ColumnLayout {
|
||||
spacing: ProtonStyle.wizard_spacing_medium
|
||||
|
||||
ColumnLayout {
|
||||
spacing: ProtonStyle.wizard_spacing_small
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
colorScheme: wizard.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("Human verification")
|
||||
type: Label.LabelType.Title
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Label {
|
||||
id: hvUsernameLabel
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
color: wizard.colorScheme.text_weak
|
||||
colorScheme: wizard.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
colorScheme: wizard.colorScheme
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("Please open the following link in your favourite web browser to verify you are human.")
|
||||
type: Label.LabelType.Body
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Label {
|
||||
id: hvRequestedUrlText
|
||||
type: Label.LabelType.Lead
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
colorScheme: wizard.colorScheme
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: "<a href='" + hvLinkUrl + "'>" + hvLinkUrl.replace("&", "&")+ "</a>"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Qt.openUrlExternally(hvLinkUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: ProtonStyle.wizard_spacing_medium
|
||||
|
||||
Button {
|
||||
id: hVContinueButton
|
||||
Layout.fillWidth: true
|
||||
colorScheme: wizard.colorScheme
|
||||
text: qsTr("Continue")
|
||||
|
||||
function checkAndSignInHv() {
|
||||
console.assert(stackLayout.currentIndex === Login.RootStack.HV || stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected checkInAndSignInHv")
|
||||
stackLayout.currentIndex = Login.RootStack.Login
|
||||
usernameTextField.validate();
|
||||
passwordTextField.validate();
|
||||
if (usernameTextField.error || passwordTextField.error) {
|
||||
return;
|
||||
}
|
||||
root.resetViaHv();
|
||||
Backend.loginHv(usernameTextField.text, Qt.btoa(passwordTextField.text));
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
checkAndSignInHv()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: wizard.colorScheme
|
||||
secondary: true
|
||||
secondaryIsOpaque: true
|
||||
text: qsTr("Cancel")
|
||||
onClicked: {
|
||||
root.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user