diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index d3de8f745b..04f106d0f9 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -29,7 +29,6 @@ Rectangle { property bool purchasesReceived: false; property bool balanceReceived: false; property bool securityImageResultReceived: false; - property bool keyFilePathIfExistsResultReceived: false; property string itemId: ""; property string itemHref: ""; property double balanceAfterPurchase: 0; @@ -46,10 +45,15 @@ Rectangle { root.activeView = "needsLogIn"; } else if (isLoggedIn) { root.activeView = "initialize"; - commerce.getSecurityImage(); commerce.getKeyFilePathIfExists(); - commerce.balance(); - commerce.inventory(); + } + } + + onKeyFilePathIfExistsResult: { + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } else if (path !== "" && root.activeView === "initialize") { + commerce.getSecurityImage(); } } @@ -57,8 +61,8 @@ Rectangle { securityImageResultReceived = true; if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" root.activeView = "notSetUp"; - } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { - root.activeView = "checkoutMain"; + } else if (exists && root.activeView === "initialize") { + commerce.getWalletAuthenticatedStatus(); } else if (exists) { // just set the source again (to be sure the change was noticed) securityImage.source = ""; @@ -66,12 +70,17 @@ Rectangle { } } - onKeyFilePathIfExistsResult: { - keyFilePathIfExistsResultReceived = true; - if (path === "" && root.activeView !== "notSetUp") { - root.activeView = "notSetUp"; - } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + onWalletAuthenticatedStatusResult: { + if (!isAuthenticated && !passphraseModal.visible) { + passphraseModal.visible = true; + } else if (isAuthenticated) { root.activeView = "checkoutMain"; + if (!balanceReceived) { + commerce.balance(); + } + if (!purchasesReceived) { + commerce.inventory(); + } } } @@ -110,10 +119,6 @@ Rectangle { } } - HifiWallet.SecurityImageModel { - id: securityImageModel; - } - // // TITLE BAR START // @@ -195,11 +200,10 @@ Rectangle { securityImageResultReceived = false; purchasesReceived = false; balanceReceived = false; - keyFilePathIfExistsResultReceived = false; commerce.getLoginStatus(); } } - + HifiWallet.NeedsLogIn { id: needsLogIn; visible: root.activeView === "needsLogIn"; @@ -221,8 +225,21 @@ Rectangle { } } + HifiWallet.PassphraseModal { + id: passphraseModal; + visible: false; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; - + Connections { + onSendSignalToParent: { + sendToScript(msg); + } + } + } + // // "WALLET NOT SET UP" START // @@ -233,7 +250,7 @@ Rectangle { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; - + RalewayRegular { id: notSetUpText; text: "Your wallet isn't set up.

Set up your Wallet (no credit card necessary) to claim your free HFC " + @@ -264,7 +281,7 @@ Rectangle { anchors.left: parent.left; anchors.bottom: parent.bottom; anchors.bottomMargin: 24; - + // "Cancel" button HifiControlsUit.Button { id: cancelButton; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index bc843a140d..383223a49c 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -28,7 +28,6 @@ Rectangle { property string activeView: "initialize"; property string referrerURL: ""; property bool securityImageResultReceived: false; - property bool keyFilePathIfExistsResultReceived: false; property bool purchasesReceived: false; property bool punctuationMode: false; // Style @@ -41,9 +40,15 @@ Rectangle { root.activeView = "needsLogIn"; } else if (isLoggedIn) { root.activeView = "initialize"; - commerce.getSecurityImage(); commerce.getKeyFilePathIfExists(); - commerce.inventory(); + } + } + + onKeyFilePathIfExistsResult: { + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } else if (path !== "" && root.activeView === "initialize") { + commerce.getSecurityImage(); } } @@ -51,8 +56,8 @@ Rectangle { securityImageResultReceived = true; if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" root.activeView = "notSetUp"; - } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { - root.activeView = "purchasesMain"; + } else if (exists && root.activeView === "initialize") { + commerce.getWalletAuthenticatedStatus(); } else if (exists) { // just set the source again (to be sure the change was noticed) securityImage.source = ""; @@ -60,12 +65,12 @@ Rectangle { } } - onKeyFilePathIfExistsResult: { - keyFilePathIfExistsResultReceived = true; - if (path === "" && root.activeView !== "notSetUp") { - root.activeView = "notSetUp"; - } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + onWalletAuthenticatedStatusResult: { + if (!isAuthenticated && !passphraseModal.visible) { + passphraseModal.visible = true; + } else if (isAuthenticated) { root.activeView = "purchasesMain"; + commerce.inventory(); } } @@ -166,7 +171,6 @@ Rectangle { Component.onCompleted: { securityImageResultReceived = false; purchasesReceived = false; - keyFilePathIfExistsResultReceived = false; commerce.getLoginStatus(); } } @@ -191,6 +195,21 @@ Rectangle { commerce.getLoginStatus(); } } + + HifiWallet.PassphraseModal { + id: passphraseModal; + visible: false; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + Connections { + onSendSignalToParent: { + sendToScript(msg); + } + } + } // // "WALLET NOT SET UP" START diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 47b3f6daf6..402209f38d 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -47,11 +47,26 @@ Item { HifiControlsUit.Button { color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; - anchors.bottom: helpText.bottom; + anchors.bottom: resetButton.top; + anchors.bottomMargin: 15; anchors.horizontalCenter: parent.horizontalCenter; height: 50; width: 250; - text: "Testing: Reset Wallet!"; + text: "DEBUG: Clear Cached Passphrase"; + onClicked: { + commerce.setPassphrase(""); + } + } + HifiControlsUit.Button { + id: resetButton; + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: helpText.bottom; + anchors.bottomMargin: 15; + anchors.horizontalCenter: parent.horizontalCenter; + height: 50; + width: 250; + text: "DEBUG: Reset Wallet!"; onClicked: { commerce.reset(); sendSignalToWallet({method: 'walletReset'}); diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml new file mode 100644 index 0000000000..4157e4082f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -0,0 +1,304 @@ +// +// PassphraseModal.qml +// qml/hifi/commerce/wallet +// +// PassphraseModal +// +// Created by Zach Fox on 2017-08-31 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls + +// references XXX from root context + +Item { + HifiConstants { id: hifi; } + + id: root; + z: 998; + property bool keyboardRaised: false; + + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + passphraseModalSecurityImage.source = ""; + passphraseModalSecurityImage.source = "image://security/securityImage"; + } + + onWalletAuthenticatedStatusResult: { + submitPassphraseInputButton.enabled = true; + if (!isAuthenticated) { + errorText.text = "Authentication failed - please try again."; + } else { + root.visible = false; + } + } + } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + // This will cause a bug -- if you bring up passphrase selection in HUD mode while + // in HMD while having HMD preview enabled, then move, then finish passphrase selection, + // HMD preview will stay off. + // TODO: Fix this unlikely bug + onVisibleChanged: { + if (visible) { + passphraseField.focus = true; + sendSignalToParent({method: 'disableHmdPreview'}); + } else { + sendSignalToParent({method: 'maybeEnableHmdPreview'}); + } + } + + // Background rectangle + Rectangle { + anchors.fill: parent; + color: "black"; + opacity: 0.9; + } + + Rectangle { + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 250; + color: hifi.colors.baseGray; + + RalewaySemiBold { + id: instructionsText; + text: "Enter Wallet Passphrase"; + size: 16; + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 16; + width: passphraseField.width; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignLeft; + } + + HifiControlsUit.TextField { + id: passphraseField; + anchors.top: instructionsText.bottom; + anchors.topMargin: 4; + anchors.left: instructionsText.left; + width: 280; + height: 50; + echoMode: TextInput.Password; + placeholderText: "passphrase"; + + onFocusChanged: { + root.keyboardRaised = focus; + } + + MouseArea { + anchors.fill: parent; + + onClicked: { + parent.focus = true; + root.keyboardRaised = true; + } + } + + onAccepted: { + submitPassphraseInputButton.enabled = false; + commerce.setPassphrase(passphraseField.text); + } + } + + // Show passphrase text + HifiControlsUit.CheckBox { + id: showPassphrase; + colorScheme: hifi.colorSchemes.dark; + anchors.left: passphraseField.left; + anchors.top: passphraseField.bottom; + anchors.topMargin: 8; + height: 30; + text: "Show passphrase as plain text"; + boxSize: 24; + onClicked: { + passphraseField.echoMode = checked ? TextInput.Normal : TextInput.Password; + } + } + + // Security Image + Item { + id: securityImageContainer; + // Anchors + anchors.top: instructionsText.top; + anchors.left: passphraseField.right; + anchors.leftMargin: 12; + anchors.right: parent.right; + Image { + id: passphraseModalSecurityImage; + anchors.top: parent.top; + anchors.horizontalCenter: parent.horizontalCenter; + height: 75; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + source: "image://security/securityImage"; + cache: false; + onVisibleChanged: { + commerce.getSecurityImage(); + } + } + Image { + id: passphraseModalSecurityImageOverlay; + source: "images/lockOverlay.png"; + width: passphraseModalSecurityImage.width * 0.45; + height: passphraseModalSecurityImage.height * 0.45; + anchors.bottom: passphraseModalSecurityImage.bottom; + anchors.right: passphraseModalSecurityImage.right; + mipmap: true; + opacity: 0.9; + } + // "Security image" text below pic + RalewayRegular { + text: "security image"; + // Text size + size: 12; + // Anchors + anchors.top: passphraseModalSecurityImage.bottom; + anchors.topMargin: 4; + anchors.left: securityImageContainer.left; + anchors.right: securityImageContainer.right; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + // Error text above buttons + RalewaySemiBold { + id: errorText; + text: ""; + // Text size + size: 16; + // Anchors + anchors.bottom: passphrasePopupActionButtonsContainer.top; + anchors.bottomMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 30; + // Style + color: hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + // + // ACTION BUTTONS START + // + Item { + id: passphrasePopupActionButtonsContainer; + // Size + width: root.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 10; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelPassphraseInputButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + height: 40; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + sendSignalToParent({method: 'passphrasePopup_cancelClicked'}); + } + } + + // "Submit" button + HifiControlsUit.Button { + id: submitPassphraseInputButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + height: 40; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: "Submit" + onClicked: { + submitPassphraseInputButton.enabled = false; + commerce.setPassphrase(passphraseField.text); + } + } + } + } + + Item { + id: keyboardContainer; + z: 999; + visible: keyboard.raised; + property bool punctuationMode: false; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + + Image { + id: lowerKeyboardButton; + source: "images/lowerKeyboard.png"; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: keyboard.top; + height: 30; + width: 120; + + MouseArea { + anchors.fill: parent; + + onClicked: { + root.keyboardRaised = false; + } + } + } + + HifiControlsUit.Keyboard { + id: keyboard; + raised: HMD.mounted && root.keyboardRaised; + numeric: parent.punctuationMode; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + } + } + + signal sendSignalToParent(var msg); +} diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 39d07315d5..7dca5a75b8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -40,8 +40,8 @@ Item { passphrasePageSecurityImage.source = "image://security/securityImage"; } - onPassphraseSetupStatusResult: { - sendMessageToLightbox({method: 'statusResult', status: passphraseIsSetup}); + onWalletAuthenticatedStatusResult: { + sendMessageToLightbox({method: 'statusResult', status: isAuthenticated}); } } @@ -58,10 +58,6 @@ Item { } } - SecurityImageModel { - id: gridModel; - } - HifiControlsUit.TextField { id: passphraseField; anchors.top: parent.top; @@ -199,7 +195,7 @@ Item { // Text below TextFields RalewaySemiBold { id: passwordReqs; - text: "Passphrase must be at least 4 characters"; + text: "Passphrase must be at least 3 characters"; // Text size size: 16; // Anchors @@ -256,7 +252,7 @@ Item { } function validateAndSubmitPassphrase() { - if (passphraseField.text.length < 4) { + if (passphraseField.text.length < 3) { setErrorText("Passphrase too short."); return false; } else if (passphraseField.text !== passphraseFieldAgain.text) { diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 53838fa58c..5695d47a68 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -26,8 +26,6 @@ Rectangle { id: root; property string activeView: "initialize"; - property bool securityImageResultReceived: false; - property bool keyFilePathIfExistsResultReceived: false; property bool keyboardRaised: false; // Style @@ -40,25 +38,30 @@ Rectangle { root.activeView = "needsLogIn"; } else if (isLoggedIn) { root.activeView = "initialize"; - commerce.getSecurityImage(); commerce.getKeyFilePathIfExists(); } } - onSecurityImageResult: { - securityImageResultReceived = true; - if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" + onKeyFilePathIfExistsResult: { + if (path === "" && root.activeView !== "notSetUp") { root.activeView = "notSetUp"; - } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { - root.activeView = "walletHome"; + } else if (path !== "" && root.activeView === "initialize") { + commerce.getSecurityImage(); } } - onKeyFilePathIfExistsResult: { - keyFilePathIfExistsResultReceived = true; - if (path === "" && root.activeView !== "notSetUp") { + onSecurityImageResult: { + if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" root.activeView = "notSetUp"; - } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + } else if (exists && root.activeView === "initialize") { + commerce.getWalletAuthenticatedStatus(); + } + } + + onWalletAuthenticatedStatusResult: { + if (!isAuthenticated && !passphraseModal.visible) { + passphraseModal.visible = true; + } else if (isAuthenticated) { root.activeView = "walletHome"; } } @@ -89,7 +92,8 @@ Rectangle { if (msg.method === 'walletSetup_cancelClicked') { walletSetupLightbox.visible = false; } else if (msg.method === 'walletSetup_finished') { - root.activeView = "walletHome"; + root.activeView = "initialize"; + commerce.getLoginStatus(); } else if (msg.method === 'walletSetup_raiseKeyboard') { root.keyboardRaised = true; } else if (msg.method === 'walletSetup_lowerKeyboard') { @@ -218,6 +222,21 @@ Rectangle { } } + PassphraseModal { + id: passphraseModal; + visible: false; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + Connections { + onSendSignalToParent: { + sendToScript(msg); + } + } + } + NotSetUp { id: notSetUp; visible: root.activeView === "notSetUp"; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 413fd8b71c..a7050febfa 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -88,13 +88,14 @@ Item { height: 60; Rectangle { id: hfcBalanceField; + color: hifi.colors.darkGray; anchors.right: parent.right; anchors.left: parent.left; anchors.bottom: parent.bottom; height: parent.height - 15; // "HFC" balance label - RalewayRegular { + FiraSansRegular { id: balanceLabel; text: "HFC"; // Text size @@ -106,7 +107,7 @@ Item { anchors.rightMargin: 4; width: paintedWidth; // Style - color: hifi.colors.darkGray; + color: hifi.colors.lightGrayText; // Alignment horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignVCenter; @@ -121,7 +122,7 @@ Item { } // Balance Text - FiraSansRegular { + FiraSansSemiBold { id: balanceText; text: "--"; // Text size @@ -133,7 +134,7 @@ Item { anchors.right: balanceLabel.left; anchors.rightMargin: 4; // Style - color: hifi.colors.darkGray; + color: hifi.colors.lightGrayText; // Alignment horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignVCenter; @@ -258,7 +259,7 @@ Item { delegate: Item { width: parent.width; height: transactionText.height + 30; - RalewayRegular { + FiraSansRegular { id: transactionText; text: model.text; // Style @@ -288,7 +289,7 @@ Item { } // This should never be visible (since you immediately get 100 HFC) - RalewayRegular { + FiraSansRegular { id: emptyTransationHistory; size: 24; visible: !transactionHistory.visible && root.historyReceived; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml index 4470ec7a75..2956dfb518 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml @@ -39,9 +39,9 @@ Rectangle { } } - onPassphraseSetupStatusResult: { + onWalletAuthenticatedStatusResult: { securityImageContainer.visible = false; - if (passphraseIsSetup) { + if (isAuthenticated) { privateKeysReadyContainer.visible = true; } else { choosePassphraseContainer.visible = true; @@ -117,7 +117,7 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 16; height: 280; - + Connections { onSendSignalToWallet: { sendSignalToWallet(msg); @@ -210,7 +210,7 @@ Rectangle { onVisibleChanged: { if (visible) { - commerce.getPassphraseSetupStatus(); + commerce.getWalletAuthenticatedStatus(); } } diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 655f228672..96f2a02f31 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -29,6 +29,30 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); } +void QmlCommerce::getLoginStatus() { + emit loginStatusResult(DependencyManager::get()->isLoggedIn()); +} + +void QmlCommerce::getKeyFilePathIfExists() { + auto wallet = DependencyManager::get(); + wallet->sendKeyFilePathIfExists(); +} + +void QmlCommerce::getWalletAuthenticatedStatus() { + auto wallet = DependencyManager::get(); + emit walletAuthenticatedStatusResult(wallet->walletIsAuthenticatedWithPassphrase()); +} + +void QmlCommerce::getSecurityImage() { + auto wallet = DependencyManager::get(); + wallet->getSecurityImage(); +} + +void QmlCommerce::chooseSecurityImage(const QString& imageFile) { + auto wallet = DependencyManager::get(); + wallet->chooseSecurityImage(imageFile); +} + void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); @@ -60,30 +84,14 @@ void QmlCommerce::history() { ledger->history(wallet->listPublicKeys()); } -void QmlCommerce::chooseSecurityImage(const QString& imageFile) { - auto wallet = DependencyManager::get(); - wallet->chooseSecurityImage(imageFile); -} - -void QmlCommerce::getSecurityImage() { - auto wallet = DependencyManager::get(); - wallet->getSecurityImage(); -} - -void QmlCommerce::getLoginStatus() { - emit loginStatusResult(DependencyManager::get()->isLoggedIn()); -} - void QmlCommerce::setPassphrase(const QString& passphrase) { - emit passphraseSetupStatusResult(true); -} - -void QmlCommerce::getPassphraseSetupStatus() { - emit passphraseSetupStatusResult(false); -} -void QmlCommerce::getKeyFilePathIfExists() { auto wallet = DependencyManager::get(); - wallet->sendKeyFilePathIfExists(); + if (wallet->getPassphrase() && !wallet->getPassphrase()->isEmpty()) { + wallet->changePassphrase(passphrase); + } else { + wallet->setPassphrase(passphrase); + } + getWalletAuthenticatedStatus(); } void QmlCommerce::reset() { @@ -91,4 +99,4 @@ void QmlCommerce::reset() { auto wallet = DependencyManager::get(); ledger->reset(); wallet->reset(); -} \ No newline at end of file +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index deb11b7714..f66bf518f5 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -28,28 +28,32 @@ public: QmlCommerce(QQuickItem* parent = nullptr); signals: + void loginStatusResult(bool isLoggedIn); + void keyFilePathIfExistsResult(const QString& path); + void securityImageResult(bool exists); + void walletAuthenticatedStatusResult(bool isAuthenticated); + void buyResult(QJsonObject result); // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). void balanceResult(QJsonObject result); void inventoryResult(QJsonObject result); - void securityImageResult(bool exists); - void loginStatusResult(bool isLoggedIn); - void passphraseSetupStatusResult(bool passphraseIsSetup); void historyResult(QJsonObject result); - void keyFilePathIfExistsResult(const QString& path); protected: + Q_INVOKABLE void getLoginStatus(); + Q_INVOKABLE void getKeyFilePathIfExists(); + Q_INVOKABLE void getSecurityImage(); + Q_INVOKABLE void getWalletAuthenticatedStatus(); + + Q_INVOKABLE void chooseSecurityImage(const QString& imageFile); + Q_INVOKABLE void setPassphrase(const QString& passphrase); + Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); Q_INVOKABLE void history(); - Q_INVOKABLE void chooseSecurityImage(const QString& imageFile); - Q_INVOKABLE void getSecurityImage(); - Q_INVOKABLE void getLoginStatus(); - Q_INVOKABLE void setPassphrase(const QString& passphrase); - Q_INVOKABLE void getPassphraseSetupStatus(); - Q_INVOKABLE void getKeyFilePathIfExists(); + Q_INVOKABLE void reset(); }; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 32852602d7..1f56ae32c4 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -56,16 +56,72 @@ QString imageFilePath() { int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now auto passphrase = DependencyManager::get()->getPassphrase(); - if (passphrase) { + if (passphrase && !passphrase->isEmpty()) { strcpy(password, passphrase->toLocal8Bit().constData()); return static_cast(passphrase->size()); } else { - // ok gotta bring up modal dialog... But right now lets just - // just keep it empty + // this shouldn't happen - so lets log it to tell us we have + // a problem with the flow... + qCCritical(commerce) << "no cached passphrase while decrypting!"; return 0; } } +RSA* readKeys(const char* filename) { + FILE* fp; + RSA* key = NULL; + if ((fp = fopen(filename, "rt"))) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) { + // now read private key + + qCDebug(commerce) << "read public key"; + + if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) { + qCDebug(commerce) << "read private key"; + fclose(fp); + return key; + } + qCDebug(commerce) << "failed to read private key"; + } else { + qCDebug(commerce) << "failed to read public key"; + } + fclose(fp); + } else { + qCDebug(commerce) << "failed to open key file" << filename; + } + return key; +} + +bool writeKeys(const char* filename, RSA* keys) { + FILE* fp; + bool retval = false; + if ((fp = fopen(filename, "wt"))) { + if (!PEM_write_RSAPublicKey(fp, keys)) { + fclose(fp); + qCDebug(commerce) << "failed to write public key"; + QFile(QString(filename)).remove(); + return retval; + } + + if (!PEM_write_RSAPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { + fclose(fp); + qCDebug(commerce) << "failed to write private key"; + QFile(QString(filename)).remove(); + return retval; + } + + retval = true; + qCDebug(commerce) << "wrote keys successfully"; + fclose(fp); + } else { + qCDebug(commerce) << "failed to open key file" << filename; + } + return retval; +} + + // BEGIN copied code - this will be removed/changed at some point soon // copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. // We will have a different implementation in practice, but this gives us a start for now @@ -124,25 +180,9 @@ QPair generateRSAKeypair() { } - - // now lets persist them to files - // FIXME: for now I'm appending to the file if it exists. As long as we always put - // the keys in the same order, this works fine. TODO: verify this will skip over - // anything else (like an embedded image) - FILE* fp; - if ((fp = fopen(keyFilePath().toStdString().c_str(), "at"))) { - if (!PEM_write_RSAPublicKey(fp, keyPair)) { - fclose(fp); - qCDebug(commerce) << "failed to write public key"; - return retval; - } - - if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { - fclose(fp); - qCDebug(commerce) << "failed to write private key"; - return retval; - } - fclose(fp); + if (!writeKeys(keyFilePath().toStdString().c_str(), keyPair)) { + qCDebug(commerce) << "couldn't save keys!"; + return retval; } RSA_free(keyPair); @@ -201,9 +241,6 @@ RSA* readPrivateKey(const char* filename) { // file opened successfully qCDebug(commerce) << "opened key file" << filename; if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) { - // cleanup - fclose(fp); - qCDebug(commerce) << "parsed private key file successfully"; } else { @@ -215,7 +252,6 @@ RSA* readPrivateKey(const char* filename) { } return key; } - static const unsigned char IVEC[16] = "IAmAnIVecYay123"; void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) { @@ -341,6 +377,24 @@ bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBuf return true; } +bool Wallet::walletIsAuthenticatedWithPassphrase() { + // try to read existing keys if they exist... + + // FIXME: initialize OpenSSL elsewhere soon + initialize(); + + auto publicKey = readPublicKey(keyFilePath().toStdString().c_str()); + + if (publicKey.size() > 0) { + if (auto key = readPrivateKey(keyFilePath().toStdString().c_str())) { + RSA_free(key); + return true; + } + } + + return false; +} + bool Wallet::createIfNeeded() { if (_publicKeys.count() > 0) return false; @@ -512,3 +566,30 @@ void Wallet::reset() { keyFile.remove(); imageFile.remove(); } + +bool Wallet::changePassphrase(const QString& newPassphrase) { + qCDebug(commerce) << "changing passphrase"; + RSA* keys = readKeys(keyFilePath().toStdString().c_str()); + if (keys) { + // we read successfully, so now write to a new temp file + // save old passphrase just in case + // TODO: force re-enter? + QString oldPassphrase = *_passphrase; + setPassphrase(newPassphrase); + QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { + // ok, now move the temp file to the correct spot + QFile(QString(keyFilePath())).remove(); + QFile(tempFileName).rename(QString(keyFilePath())); + qCDebug(commerce) << "passphrase changed successfully"; + return true; + } else { + qCDebug(commerce) << "couldn't write keys"; + QFile(tempFileName).remove(); + setPassphrase(oldPassphrase); + return false; + } + } + qCDebug(commerce) << "couldn't read keys"; + return false; +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 4acd913181..3b470210de 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -39,18 +39,21 @@ public: void setPassphrase(const QString& passphrase); QString* getPassphrase() { return _passphrase; } + bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); } + bool walletIsAuthenticatedWithPassphrase(); + bool changePassphrase(const QString& newPassphrase); void reset(); signals: - void securityImageResult(bool exists) ; + void securityImageResult(bool exists); void keyFilePathIfExistsResult(const QString& path); private: QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; QByteArray _salt {"iamsalt!"}; - QString* _passphrase { new QString("pwd") }; + QString* _passphrase { new QString("") }; void updateImageProvider(); bool encryptFile(const QString& inputFilePath, const QString& outputFilePath); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index a7b7b50379..5f07c4cbe7 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -56,6 +56,7 @@ var isHmdPreviewDisabled = true; function fromQml(message) { switch (message.method) { + case 'passphrasePopup_cancelClicked': case 'walletSetup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoHomeScreen(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7d1aaee157..2eaefe7565 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -197,6 +197,7 @@ // Description: // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML // in the format "{method, params}", like json-rpc. + var isHmdPreviewDisabled = true; function fromQml(message) { switch (message.method) { case 'checkout_setUpClicked': @@ -231,12 +232,20 @@ case 'purchases_goToMarketplaceClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'needsLogIn_loginClicked': openLoginWindow(); break; + case 'disableHmdPreview': + isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); + Menu.setIsOptionChecked("Disable Preview", true); + break; + case 'maybeEnableHmdPreview': + Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); }