From 3d46e62345bb1a84e1cbf3fe1f104b670098e633 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Fri, 24 Sep 2021 15:03:57 +0300 Subject: [PATCH] draft authentication dialogs --- src/app/keycard/core.nim | 24 +++++ src/app/keycard/view.nim | 18 ++++ src/nim_status_client.nim | 13 ++- ui/imports/utils/Utils.qml | 25 ++++++ ui/main.qml | 30 +++++++ ui/nim-status-client.pro | 2 + ui/onboarding/Keycard.qml | 34 +++++++ ui/onboarding/Keycard/CreatePINModal.qml | 108 +++++++++++++++++++++++ ui/onboarding/Keycard/qmldir | 1 + ui/onboarding/KeysMain.qml | 12 +++ ui/onboarding/qmldir | 1 + ui/shared/keycard/InsertCard.qml | 4 + ui/shared/keycard/PINModal.qml | 65 ++++++++++++++ ui/shared/keycard/PairingModal.qml | 64 ++++++++++++++ ui/shared/keycard/qmldir | 3 + 15 files changed, 400 insertions(+), 4 deletions(-) create mode 100644 src/app/keycard/core.nim create mode 100644 src/app/keycard/view.nim create mode 100644 ui/onboarding/Keycard.qml create mode 100644 ui/onboarding/Keycard/CreatePINModal.qml create mode 100644 ui/onboarding/Keycard/qmldir create mode 100644 ui/shared/keycard/InsertCard.qml create mode 100644 ui/shared/keycard/PINModal.qml create mode 100644 ui/shared/keycard/PairingModal.qml create mode 100644 ui/shared/keycard/qmldir diff --git a/src/app/keycard/core.nim b/src/app/keycard/core.nim new file mode 100644 index 0000000000..a851ddf602 --- /dev/null +++ b/src/app/keycard/core.nim @@ -0,0 +1,24 @@ +import NimQml, chronicles, std/wrapnils +import status/status +import view + +type KeycardController* = ref object + view*: KeycardView + variant*: QVariant + status: Status + +proc newController*(status: Status): KeycardController = + result = KeycardController() + result.status = status + result.view = newKeycardView(status) + result.variant = newQVariant(result.view) + +proc delete*(self: KeycardController) = + delete self.variant + delete self.view + +proc reset*(self: KeycardController) = + discard + +proc init*(self: KeycardController) = + discard \ No newline at end of file diff --git a/src/app/keycard/view.nim b/src/app/keycard/view.nim new file mode 100644 index 0000000000..80f615e622 --- /dev/null +++ b/src/app/keycard/view.nim @@ -0,0 +1,18 @@ +import NimQml +import status/status + +QtObject: + type KeycardView* = ref object of QObject + status*: Status + + proc setup(self: KeycardView) = + self.QObject.setup + + proc delete*(self: KeycardView) = + self.QObject.delete + + proc newKeycardView*(status: Status): KeycardView = + new(result, delete) + result.status = status + result.setup + diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index bd4dd26f72..2814688f74 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -11,6 +11,7 @@ import app/profile/view import app/onboarding/core as onboarding import app/login/core as login import app/provider/core as provider +import app/keycard/core as keycard import status/types/[account] import status_go import status/status as statuslib @@ -108,7 +109,7 @@ proc mainProc() = engine.addImportPath("qrc:/./imports") engine.setNetworkAccessManagerFactory(networkAccessFactory) engine.setRootContextProperty("uiScaleFilePath", newQVariant(uiScaleFilePath)) - + # Register events objects let dockShowAppEvent = newStatusDockShowAppEventObject(engine) defer: dockShowAppEvent.delete() @@ -145,7 +146,7 @@ proc mainProc() = signal_handler(signalsQObjPointer, ($(%* {"type": "chronicles-log", "event": msg})).cstring, "receiveSignal") except: logLoggingFailure(cstring(msg), getCurrentException()) - + let logFile = fmt"app_{getTime().toUnix}.log" discard defaultChroniclesStream.outputs[1].open(LOGDIR & logFile, fmAppend) @@ -192,6 +193,8 @@ proc mainProc() = defer: login.delete() var onboarding = onboarding.newController(status) defer: onboarding.delete() + var keycard = keycard.newController(status) + defer: keycard.delete() proc onAccountChanged(account: Account) = profile.view.setAccountSettingsFile(account.name) @@ -234,17 +237,19 @@ proc mainProc() = engine.setRootContextProperty("loginModel", login.variant) engine.setRootContextProperty("onboardingModel", onboarding.variant) + engine.setRootContextProperty("keycardModel", keycard.variant) engine.setRootContextProperty("singleInstance", newQVariant(singleInstance)) let isExperimental = if getEnv("EXPERIMENTAL") == "1": "1" else: "0" # value explicity passed to avoid trusting input let experimentalFlag = newQVariant(isExperimental) engine.setRootContextProperty("isExperimental", experimentalFlag) - + # Initialize only controllers whose init functions # do not need a running node proc initControllers() = login.init() onboarding.init() + keycard.init() initControllers() @@ -272,7 +277,7 @@ proc mainProc() = var prValue = newQVariant(if defined(production): true else: false) engine.setRootContextProperty("production", prValue) - # We're applying default language before we load qml. Also we're aware that + # We're applying default language before we load qml. Also we're aware that # switch language at runtime will have some impact to cpu usage. # https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-linguist-programmers.html changeLanguage("en") diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index 4139f6a8b6..3f691048be 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -598,6 +598,31 @@ QtObject { } } + function validatePINs(item, firstPINField, repeatPINField) { + switch (item) { + case "first": + if (firstPINField.text === "") { + return [false, qsTr("You need to enter a PIN")]; + } else if (!/^\d+$/.test(firstPINField.text)) { + return [false, qsTr("The PIN must contain only digits")]; + } else if (firstPINField.text.length != 6) { + return [false, qsTr("The PIN must be exactly 6 digits")]; + } + return [true, ""]; + + case "repeat": + if (repeatPINField.text === "") { + return [false, qsTr("You need to repeat your PIN")]; + } else if (repeatPINField.text !== firstPINField.text) { + return [false, qsTr("PIN don't match")]; + } + return [true, ""]; + + default: + return [false, ""]; + } + } + function getHostname(url) { const rgx = /\:\/\/(?:[a-zA-Z0-9\-]*\.{1,}){1,}[a-zA-Z0-9]*/i const matches = rgx.exec(url) diff --git a/ui/main.qml b/ui/main.qml index ad655a2284..65e58e0915 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -373,6 +373,16 @@ StatusWindow { } } + DSM.State { + id: keycardState + onEntered: loader.sourceComponent = keycard + + DSM.SignalTransition { + targetState: appState + signal: onboardingModel.moveToAppState + } + } + DSM.State { id: stateLogin onEntered: loader.sourceComponent = login @@ -407,6 +417,12 @@ StatusWindow { guard: path === "KeysMain" } + DSM.SignalTransition { + targetState: keycardState + signal: applicationWindow.navigateTo + guard: path === "Keycard" + } + DSM.FinalState { id: onboardingDoneState } @@ -526,6 +542,7 @@ StatusWindow { KeysMain { btnGenKey.onClicked: applicationWindow.navigateTo("GenKey") btnExistingKey.onClicked: applicationWindow.navigateTo("ExistingKey") + btnKeycard.onClicked: applicationWindow.navigateTo("Keycard") } } @@ -556,6 +573,19 @@ StatusWindow { } } + Component { + id: keycard + Keycard { + onClosed: function () { + if (hasAccounts) { + applicationWindow.navigateTo("InitialState") + } else { + applicationWindow.navigateTo("KeysMain") + } + } + } + } + Component { id: login Login { diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index f335c9751e..e90592e00c 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -18,8 +18,10 @@ SOURCES = *.qml \ shared/*.qml \ imports/*.qml \ shared/status/*.qml \ + shared/keycard/*.qml \ onboarding/*.qml \ onboarding/Login/*.qml \ + onboarding/Keycard/*.qml \ app/AppLayouts/*.qml \ app/AppLayouts/Browser/*.qml \ app/AppLayouts/Chat/*.qml \ diff --git a/ui/onboarding/Keycard.qml b/ui/onboarding/Keycard.qml new file mode 100644 index 0000000000..2218b88e00 --- /dev/null +++ b/ui/onboarding/Keycard.qml @@ -0,0 +1,34 @@ +import QtQuick 2.13 +import "./Keycard" +import "../shared/keycard" + +// this will be the entry point. for now it opens all keycard-related dialogs in sequence for test +Item { + property var onClosed: function () {} + id: keycardView + anchors.fill: parent + Component.onCompleted: { + createPinModal.open() + } + + CreatePINModal { + id: createPinModal + onClosed: function () { + pairingModal.open() + } + } + + PairingModal { + id: pairingModal + onClosed: function () { + pinModal.open() + } + } + + PINModal { + id: pinModal + onClosed: function () { + keycardView.onClosed() + } + } +} \ No newline at end of file diff --git a/ui/onboarding/Keycard/CreatePINModal.qml b/ui/onboarding/Keycard/CreatePINModal.qml new file mode 100644 index 0000000000..2c4df60a1f --- /dev/null +++ b/ui/onboarding/Keycard/CreatePINModal.qml @@ -0,0 +1,108 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Dialogs 1.3 +import StatusQ.Controls 0.1 +import "../../imports" +import "../../shared" + +ModalPopup { + property bool firstPINFieldValid: false + property bool repeatPINFieldValid: false + property string pinValidationError: "" + property string repeatPINValidationError: "" + + id: popup + title: qsTr("Create PIN") + height: 500 + + onOpened: { + firstPINField.text = ""; + firstPINField.forceActiveFocus(Qt.MouseFocusReason) + } + + Input { + id: firstPINField + anchors.rightMargin: 56 + anchors.leftMargin: 56 + anchors.top: parent.top + anchors.topMargin: 88 + placeholderText: qsTr("New PIN") + textField.echoMode: TextInput.PIN + onTextChanged: { + [firstPINFieldValid, pinValidationError] = + Utils.validatePINs("first", firstPINField, repeatPINField); + } + } + + Input { + id: repeatPINField + enabled: firstPINFieldValid + anchors.rightMargin: 0 + anchors.leftMargin: 0 + anchors.right: firstPINField.right + anchors.left: firstPINField.left + anchors.top: firstPINField.bottom + anchors.topMargin: Style.current.xlPadding + placeholderText: qsTr("Confirm PIN") + textField.echoMode: TextInput.PIN + Keys.onReturnPressed: function(event) { + if (submitBtn.enabled) { + submitBtn.clicked(event) + } + } + onTextChanged: { + [repeatPINFieldValid, repeatPINValidationError] = + Utils.validatePINs("repeat", firstPINField, repeatPINField); + } + } + + StyledText { + id: validationError + text: { + if (pinValidationError !== "") return pinValidationError; + if (repeatPINValidationError !== "") return repeatPINValidationError; + return ""; + } + anchors.top: repeatPINField.bottom + anchors.topMargin: 20 + anchors.right: parent.right + anchors.rightMargin: Style.current.xlPadding + anchors.left: parent.left + anchors.leftMargin: Style.current.xlPadding + horizontalAlignment: Text.AlignHCenter + color: Style.current.danger + font.pixelSize: 11 + } + + StyledText { + text: qsTr("Create a 6 digit long PIN") + wrapMode: Text.WordWrap + anchors.right: parent.right + anchors.rightMargin: Style.current.xlPadding + anchors.left: parent.left + anchors.leftMargin: Style.current.xlPadding + horizontalAlignment: Text.AlignHCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + color: Style.current.secondaryText + font.pixelSize: 12 + } + + footer: Item { + width: parent.width + height: submitBtn.height + + StatusButton { + id: submitBtn + anchors.bottom: parent.bottom + anchors.topMargin: Style.current.padding + anchors.right: parent.right + text: qsTr("Create PIN") + enabled: firstPINFieldValid && repeatPINFieldValid + + onClicked: { + + } + } + } +} diff --git a/ui/onboarding/Keycard/qmldir b/ui/onboarding/Keycard/qmldir new file mode 100644 index 0000000000..a898c4833e --- /dev/null +++ b/ui/onboarding/Keycard/qmldir @@ -0,0 +1 @@ +CreatePINModal 1.0 CreatePINModal.qml diff --git a/ui/onboarding/KeysMain.qml b/ui/onboarding/KeysMain.qml index a6247d4e56..9106cfd305 100644 --- a/ui/onboarding/KeysMain.qml +++ b/ui/onboarding/KeysMain.qml @@ -10,6 +10,7 @@ Page { id: page property alias btnExistingKey: btnExistingKey property alias btnGenKey: btnGenKey + property alias btnKeycard: btnKeycard background: Rectangle { color: Style.current.background @@ -98,6 +99,17 @@ Page { anchors.horizontalCenter: parent.horizontalCenter type: "secondary" } + + StatusButton { + id: btnKeycard + //% "I have a Keycard" + text: qsTr("I have a Keycard") + anchors.top: btnExistingKey.bottom + anchors.topMargin: Style.current.padding + anchors.horizontalCenter: parent.horizontalCenter + visible: isExperimental === "1" || appSettings.isKeycardEnabled + type: "secondary" + } } } diff --git a/ui/onboarding/qmldir b/ui/onboarding/qmldir index 1f2985b609..3a0fa79ed6 100644 --- a/ui/onboarding/qmldir +++ b/ui/onboarding/qmldir @@ -7,3 +7,4 @@ EnterSeedPhraseModal 1.0 EnterSeedPhraseModal.qml CreatePasswordModal 1.0 CreatePasswordModal.qml GenKeyModal 1.0 GenKeyModal.qml BeforeGetStartedModal 1.0 BeforeGetStartedModal.qml +Keycard 1.0 Keycard.qml diff --git a/ui/shared/keycard/InsertCard.qml b/ui/shared/keycard/InsertCard.qml new file mode 100644 index 0000000000..87832d1d4b --- /dev/null +++ b/ui/shared/keycard/InsertCard.qml @@ -0,0 +1,4 @@ +import QtQuick 2.13 + +Item { +} \ No newline at end of file diff --git a/ui/shared/keycard/PINModal.qml b/ui/shared/keycard/PINModal.qml new file mode 100644 index 0000000000..3d80eb8fef --- /dev/null +++ b/ui/shared/keycard/PINModal.qml @@ -0,0 +1,65 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Dialogs 1.3 +import StatusQ.Controls 0.1 +import "../../imports" +import "../../shared" + +ModalPopup { + property bool pinFieldValid: false + + id: popup + title: qsTr("Authenticate PIN") + height: 400 + + onOpened: { + pinField.text = ""; + pinField.forceActiveFocus(Qt.MouseFocusReason) + } + + Input { + id: pinField + anchors.rightMargin: 56 + anchors.leftMargin: 56 + anchors.top: parent.top + anchors.topMargin: 88 + placeholderText: qsTr("PIN") + textField.echoMode: TextInput.Password + onTextChanged: { + [pinFieldValid, _] = + Utils.validatePINs("first", pinField, pinField); + } + } + + StyledText { + text: qsTr("Insert your 6-digit PIN") + wrapMode: Text.WordWrap + anchors.right: parent.right + anchors.rightMargin: Style.current.xlPadding + anchors.left: parent.left + anchors.leftMargin: Style.current.xlPadding + horizontalAlignment: Text.AlignHCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + color: Style.current.secondaryText + font.pixelSize: 12 + } + + footer: Item { + width: parent.width + height: submitBtn.height + + StatusButton { + id: submitBtn + anchors.bottom: parent.bottom + anchors.topMargin: Style.current.padding + anchors.right: parent.right + text: qsTr("Authenticate") + enabled: pinFieldValid + + onClicked: { + + } + } + } +} diff --git a/ui/shared/keycard/PairingModal.qml b/ui/shared/keycard/PairingModal.qml new file mode 100644 index 0000000000..092198fff2 --- /dev/null +++ b/ui/shared/keycard/PairingModal.qml @@ -0,0 +1,64 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Dialogs 1.3 +import StatusQ.Controls 0.1 +import "../../imports" +import "../../shared" + +ModalPopup { + property bool pairingPasswordFieldValid: false + + id: popup + title: qsTr("Insert pairing code") + height: 400 + + onOpened: { + pairingPasswordField.text = ""; + pairingPasswordField.forceActiveFocus(Qt.MouseFocusReason) + } + + Input { + id: pairingPasswordField + anchors.rightMargin: 56 + anchors.leftMargin: 56 + anchors.top: parent.top + anchors.topMargin: 88 + placeholderText: qsTr("Pairing code") + textField.echoMode: TextInput.Password + onTextChanged: { + pairingPasswordFieldValid = pairingPasswordField.text !== ""; + } + } + + StyledText { + text: qsTr("Insert the Keycard pairing code") + wrapMode: Text.WordWrap + anchors.right: parent.right + anchors.rightMargin: Style.current.xlPadding + anchors.left: parent.left + anchors.leftMargin: Style.current.xlPadding + horizontalAlignment: Text.AlignHCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + color: Style.current.secondaryText + font.pixelSize: 12 + } + + footer: Item { + width: parent.width + height: submitBtn.height + + StatusButton { + id: submitBtn + anchors.bottom: parent.bottom + anchors.topMargin: Style.current.padding + anchors.right: parent.right + text: qsTr("Pair") + enabled: pairingPasswordFieldValid + + onClicked: { + + } + } + } +} diff --git a/ui/shared/keycard/qmldir b/ui/shared/keycard/qmldir new file mode 100644 index 0000000000..3a9fbf66a1 --- /dev/null +++ b/ui/shared/keycard/qmldir @@ -0,0 +1,3 @@ +InsertCard 1.0 InsertCard.qml +PairingModal 1.0 PairingModal.qml +PINModal 1.0 PINModal.qml