Files
proton-bridge/internal/frontend/bridge-gui/qml/SignIn.qml
Xavier Michelon 72708d6e2c GODT-1667: bridge-gui spawns bridge process. [skip-ci]
Other: renaming of bridge-gui.
WIP: locate bridge exe.
WIP: bridge process launch.
WIP: cleaner closure of bridge.
WIP: grpcClient connection retries.
WIP: clean exit when bridge process is killed.

Fixed issues from MR review. [skip-ci].

WIP: Fixed gRPC case in CMakelists.txt [skip-ci]

It caused issues on Debian.

WIP: update gRPC/protobuf and tweaked CMakeLists.txt. [skip-ci]

WIP: Fixed a bug where splash screen could not be dismissed. [skip-ci]
2022-09-01 13:21:31 +02:00

458 lines
14 KiB
QML

// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import Proton 4.0
Item {
id: root
property ColorScheme colorScheme
function reset() {
stackLayout.currentIndex = 0
loginNormalLayout.reset()
login2FALayout.reset()
login2PasswordLayout.reset()
}
function abort() {
root.reset()
root.backend.loginAbort(usernameTextField.text)
}
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
property var backend
property alias username: usernameTextField.text
state: "Page 1"
property alias currentIndex: stackLayout.currentIndex
StackLayout {
id: stackLayout
anchors.fill: parent
function loginFailed() {
signInButton.loading = false
usernameTextField.enabled = true
usernameTextField.error = true
passwordTextField.enabled = true
passwordTextField.error = true
}
Connections {
target: root.backend
onLoginUsernamePasswordError: {
console.assert(stackLayout.currentIndex == 0, "Unexpected loginUsernamePasswordError")
console.assert(signInButton.loading == true, "Unexpected loginUsernamePasswordError")
stackLayout.loginFailed()
if (errorMsg!="") errorLabel.text = errorMsg
else errorLabel.text = qsTr("Incorrect login credentials")
}
onLoginFreeUserError: {
console.assert(stackLayout.currentIndex == 0, "Unexpected loginFreeUserError")
stackLayout.loginFailed()
}
onLoginConnectionError: {
if (stackLayout.currentIndex == 0 ) {
stackLayout.loginFailed()
}
}
onLogin2FARequested: {
console.assert(stackLayout.currentIndex == 0, "Unexpected login2FARequested")
twoFactorUsernameLabel.text = username
stackLayout.currentIndex = 1
}
onLogin2FAError: {
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAError")
twoFAButton.loading = false
twoFactorPasswordTextField.enabled = true
twoFactorPasswordTextField.error = true
twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect")
}
onLogin2FAErrorAbort: {
console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort")
root.reset()
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
}
onLogin2PasswordRequested: {
console.assert(stackLayout.currentIndex == 0 || stackLayout.currentIndex == 1, "Unexpected login2PasswordRequested")
stackLayout.currentIndex = 2
}
onLogin2PasswordError: {
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordError")
secondPasswordButton.loading = false
secondPasswordTextField.enabled = true
secondPasswordTextField.error = true
secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect")
}
onLogin2PasswordErrorAbort: {
console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort")
root.reset()
errorLabel.text = qsTr("Incorrect login credentials. Please try again.")
}
onLoginFinished: {
stackLayout.currentIndex = 0
root.reset()
}
}
ColumnLayout {
id: loginNormalLayout
function reset() {
signInButton.loading = false
errorLabel.text = ""
usernameTextField.enabled = true
usernameTextField.error = false
usernameTextField.errorString = ""
passwordTextField.enabled = true
passwordTextField.error = false
passwordTextField.errorString = ""
passwordTextField.text = ""
}
spacing: 0
Label {
colorScheme: root.colorScheme
text: qsTr("Sign in")
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
type: Label.LabelType.Title
}
Label {
colorScheme: root.colorScheme
id: subTitle
text: qsTr("Enter your Proton Account details.")
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8
color: root.colorScheme.text_weak
type: Label.LabelType.Body
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: 36
spacing: 0
visible: errorLabel.text.length > 0
ColorImage {
color: root.colorScheme.signal_danger
source: "./icons/ic-exclamation-circle-filled.svg"
height: errorLabel.height
sourceSize.height: errorLabel.height
}
Label {
colorScheme: root.colorScheme
id: errorLabel
Layout.leftMargin: 4
color: root.colorScheme.signal_danger
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
}
}
TextField {
colorScheme: root.colorScheme
id: usernameTextField
label: qsTr("Username or email")
Layout.fillWidth: true
Layout.topMargin: 24
onTextChanged: {
// remove "invalid username / password error"
if (error || errorLabel.text.length > 0) {
errorLabel.text = ""
usernameTextField.error = false
passwordTextField.error = false
}
}
validator: function(str) {
if (str.length === 0) {
return qsTr("Enter username or email")
}
return
}
onAccepted: passwordTextField.forceActiveFocus()
}
TextField {
colorScheme: root.colorScheme
id: passwordTextField
label: qsTr("Password")
Layout.fillWidth: true
Layout.topMargin: 8
echoMode: TextInput.Password
onTextChanged: {
// remove "invalid username / password error"
if (error || errorLabel.text.length > 0) {
errorLabel.text = ""
usernameTextField.error = false
passwordTextField.error = false
}
}
validator: function(str) {
if (str.length === 0) {
return qsTr("Enter password")
}
return
}
onAccepted: signInButton.checkAndSignIn()
}
Button {
colorScheme: root.colorScheme
id: signInButton
text: qsTr("Sign in")
Layout.fillWidth: true
Layout.topMargin: 24
onClicked: checkAndSignIn()
function checkAndSignIn() {
usernameTextField.validate()
passwordTextField.validate()
if (usernameTextField.error || passwordTextField.error) {
return
}
usernameTextField.enabled = false
passwordTextField.enabled = false
enabled = false
loading = true
root.backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text))
}
}
Label {
colorScheme: root.colorScheme
textFormat: Text.StyledText
text: link("https://protonmail.com/signup", qsTr("Create or upgrade your account"))
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 24
type: Label.LabelType.Body
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
ColumnLayout {
id: login2FALayout
function reset() {
twoFAButton.loading = false
twoFactorPasswordTextField.enabled = true
twoFactorPasswordTextField.error = false
twoFactorPasswordTextField.errorString = ""
twoFactorPasswordTextField.text = ""
}
spacing: 0
Label {
colorScheme: root.colorScheme
text: qsTr("Two-factor authentication")
Layout.topMargin: 16
Layout.alignment: Qt.AlignCenter
type: Label.LabelType.Heading
}
Label {
colorScheme: root.colorScheme
id: twoFactorUsernameLabel
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 8
type: Label.LabelType.Lead
color: root.colorScheme.text_weak
}
TextField {
colorScheme: root.colorScheme
id: twoFactorPasswordTextField
label: qsTr("Two-factor code")
assistiveText: qsTr("Enter the 6-digit code")
Layout.fillWidth: true
Layout.topMargin: 32
validator: function(str) {
if (str.length === 0) {
return qsTr("Enter username or email")
}
return
}
}
Button {
colorScheme: root.colorScheme
id: twoFAButton
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
Layout.fillWidth: true
Layout.topMargin: 24
onClicked: {
twoFactorPasswordTextField.validate()
if (twoFactorPasswordTextField.error) {
return
}
twoFactorPasswordTextField.enabled = false
enabled = false
loading = true
root.backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text))
}
}
}
ColumnLayout {
id: login2PasswordLayout
function reset() {
secondPasswordButton.loading = false
secondPasswordTextField.enabled = true
secondPasswordTextField.error = false
secondPasswordTextField.errorString = ""
secondPasswordTextField.text = ""
}
spacing: 0
Label {
colorScheme: root.colorScheme
text: qsTr("Unlock your mailbox")
Layout.topMargin: 16
Layout.alignment: Qt.AlignCenter
type: Label.LabelType.Heading
}
TextField {
colorScheme: root.colorScheme
id: secondPasswordTextField
label: qsTr("Mailbox password")
Layout.fillWidth: true
Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight
echoMode: TextInput.Password
validator: function(str) {
if (str.length === 0) {
return qsTr("Enter username or email")
}
return
}
}
Button {
colorScheme: root.colorScheme
id: secondPasswordButton
text: loading ? qsTr("Unlocking") : qsTr("Unlock")
Layout.fillWidth: true
Layout.topMargin: 24
onClicked: {
secondPasswordTextField.validate()
if (secondPasswordTextField.error) {
return
}
secondPasswordTextField.enabled = false
enabled = false
loading = true
root.backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text))
}
}
}
}
states: [
State {
name: "Page 1"
PropertyChanges {
target: stackLayout
currentIndex: 0
}
},
State {
name: "Page 2"
PropertyChanges {
target: stackLayout
currentIndex: 1
}
},
State {
name: "Page 3"
PropertyChanges {
target: stackLayout
currentIndex: 2
}
}
]
}