Merge pull request #11294 from zfox23/commerce_walletPassphraseModal

Commerce: Wallet Passphrase Input Modal
This commit is contained in:
David Kelly 2017-09-01 17:46:09 -07:00 committed by GitHub
commit 8723ff2df2
14 changed files with 602 additions and 125 deletions

View file

@ -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: "<b>Your wallet isn't set up.</b><br><br>Set up your Wallet (no credit card necessary) to claim your <b>free HFC</b> " +
@ -264,7 +281,7 @@ Rectangle {
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 24;
// "Cancel" button
HifiControlsUit.Button {
id: cancelButton;

View file

@ -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

View file

@ -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'});

View file

@ -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);
}

View file

@ -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) {

View file

@ -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";

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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<AccountManager>()->isLoggedIn());
}
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get<Wallet>();
wallet->sendKeyFilePathIfExists();
}
void QmlCommerce::getWalletAuthenticatedStatus() {
auto wallet = DependencyManager::get<Wallet>();
emit walletAuthenticatedStatusResult(wallet->walletIsAuthenticatedWithPassphrase());
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getSecurityImage();
}
void QmlCommerce::chooseSecurityImage(const QString& imageFile) {
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageFile);
}
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
@ -60,30 +84,14 @@ void QmlCommerce::history() {
ledger->history(wallet->listPublicKeys());
}
void QmlCommerce::chooseSecurityImage(const QString& imageFile) {
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageFile);
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getSecurityImage();
}
void QmlCommerce::getLoginStatus() {
emit loginStatusResult(DependencyManager::get<AccountManager>()->isLoggedIn());
}
void QmlCommerce::setPassphrase(const QString& passphrase) {
emit passphraseSetupStatusResult(true);
}
void QmlCommerce::getPassphraseSetupStatus() {
emit passphraseSetupStatusResult(false);
}
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get<Wallet>();
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<Wallet>();
ledger->reset();
wallet->reset();
}
}

View file

@ -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();
};

View file

@ -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<Wallet>()->getPassphrase();
if (passphrase) {
if (passphrase && !passphrase->isEmpty()) {
strcpy(password, passphrase->toLocal8Bit().constData());
return static_cast<int>(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<QByteArray*, QByteArray*> 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;
}

View file

@ -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);

View file

@ -56,6 +56,7 @@
var isHmdPreviewDisabled = true;
function fromQml(message) {
switch (message.method) {
case 'passphrasePopup_cancelClicked':
case 'walletSetup_cancelClicked':
case 'needsLogIn_cancelClicked':
tablet.gotoHomeScreen();

View file

@ -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));
}