From 3326c8b5f54eaabe1fb73588c390991730ece908 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 25 Sep 2020 15:05:07 -0400 Subject: [PATCH] feat: ask for user approval for API requests --- src/app/provider/view.nim | 28 +++---- ui/app/AppLayouts/Browser/BrowserLayout.qml | 82 ++++++++++++++++++++- ui/app/AppLayouts/Browser/provider.js | 68 +++++++++-------- ui/imports/Constants.qml | 7 ++ 4 files changed, 140 insertions(+), 45 deletions(-) diff --git a/src/app/provider/view.nim b/src/app/provider/view.nim index d9eea0ade4..1836e6090e 100644 --- a/src/app/provider/view.nim +++ b/src/app/provider/view.nim @@ -41,8 +41,10 @@ type request: string APIRequest = ref object + isAllowed: bool messageId: JsonNode permission: Permissions + hostname: string proc requestType(message: string): RequestTypes = let data = message.parseJson @@ -74,7 +76,9 @@ proc toAPIRequest(message: string): APIRequest = result = APIRequest( messageId: data["messageId"], - permission: permission + isAllowed: data{"isAllowed"}.getBool(), + permission: permission, + hostname: data{"hostname"}.getStr() ) QtObject: @@ -133,30 +137,28 @@ QtObject: "result": rpcResult.parseJson } - - proc process*(data: APIRequest): string = - # TODO: Do a proper implementation. Must ask for approval from the user. - # Probably this should happen in BrowserLayout.qml - + proc process*(data: APIRequest): string = var value:JsonNode = case data.permission of Permissions.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")] of Permissions.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0") of Permissions.Unknown: newJNull() + let isAllowed = data.isAllowed and data.permission != Permissions.Unknown + + info "API request received", host=data.hostname, value=data.permission, isAllowed + + # TODO: if isAllowed, store permission grant + return $ %* { - "type": $ResponseTypes.APIResponse, - "isAllowed": true, # TODO isAllowed or permission is unknown + "type": ResponseTypes.APIResponse, + "isAllowed": isAllowed, "permission": data.permission, "messageId": data.messageId, "data": value } proc postMessage*(self: Web3ProviderView, message: string): string {.slot.} = - let requestType = message.requestType() - - info "Provider request received", value=requestType - - case requestType: + case message.requestType(): of RequestTypes.Web3SendAsyncReadOnly: message.toWeb3SendAsyncReadOnly().process() of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO: of RequestTypes.APIRequest: message.toAPIRequest().process() diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index 667bdabf31..723436c285 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -2,12 +2,16 @@ import QtQuick 2.13 import QtQuick.Layouts 1.13 import QtWebEngine 1.10 import QtWebChannel 1.13 +import "../../../shared" +import "../../../imports" Item { id: browserView Layout.fillHeight: true Layout.fillWidth: true + property var request: {"hostname": "", "permission": ""} + // TODO: example qml webbrowser available here: // https://doc.qt.io/qt-5/qtwebengine-webengine-quicknanobrowser-example.html @@ -24,6 +28,75 @@ Item { ] } + function postMessage(isAllowed){ + request.isAllowed = isAllowed; + provider.web3Response(web3Provider.postMessage(JSON.stringify(request))); + } + + ModalPopup { + id: accessDialog + + // TODO: design required + + StyledText { + id: siteName + text: request.hostname + anchors.top: parent.top + anchors.topMargin: Style.current.padding + width: parent.width + wrapMode: Text.WordWrap + } + + StyledText { + id: permission + text: qsTr("Permission requested: %1").arg(request.permission) + anchors.top: siteName.bottom + anchors.topMargin: Style.current.padding + width: parent.width + wrapMode: Text.WordWrap + } + + StyledText { + id: description + anchors.top: permission.bottom + anchors.topMargin: Style.current.padding + width: parent.width + wrapMode: Text.WordWrap + text: { + switch(request.permission){ + case Constants.permission_web3: return qsTr("Allowing authorizes this DApp to retrieve your wallet address and enable Web3"); + case Constants.permission_contactCode: return qsTr("Granting access authorizes this DApp to retrieve your chat key"); + default: return qsTr("Unknown permission"); + } + } + } + + StyledButton { + anchors.bottom: parent.bottom + anchors.bottomMargin: Style.current.padding + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + label: qsTr("Allow") + onClicked: { + postMessage(true); + accessDialog.close(); + } + } + + StyledButton { + anchors.bottom: parent.bottom + anchors.bottomMargin: Style.current.padding + anchors.right: parent.right + anchors.rightMargin: Style.current.padding + label: qsTr("Deny") + onClicked: { + postMessage(false); + accessDialog.close(); + } + } + } + + QtObject { id: provider WebChannel.id: "backend" @@ -31,7 +104,14 @@ Item { signal web3Response(string data); function postMessage(data){ - web3Response(web3Provider.postMessage(data)); + request = JSON.parse(data) + if(request.type === Constants.api_request){ + // TODO: check if permission has been granted before, + // to not show the dialog + accessDialog.open() + } else { + web3Response(web3Provider.postMessage(data)); + } } property int networkId: web3Provider.networkId diff --git a/ui/app/AppLayouts/Browser/provider.js b/ui/app/AppLayouts/Browser/provider.js index 378775d05d..d50e83f052 100644 --- a/ui/app/AppLayouts/Browser/provider.js +++ b/ui/app/AppLayouts/Browser/provider.js @@ -795,47 +795,50 @@ You may add additional accurate notices of copyright ownership. (function(){ // Based on // https://github.com/status-im/status-react/blob/f9fb4d6974138a276b0cdcc6e4ea1611063e70ca/resources/js/provider.js - if(typeof EthereumProvider === "undefined"){ let callbackId = 0; let callbacks = {}; const onMessage = function(message){ - const data = JSON.parse(message); - const id = data.messageId; - const callback = callbacks[id]; + try { + const data = JSON.parse(message); + const id = data.messageId; + const callback = callbacks[id]; - if (callback) { - if (data.type === "api-response") { - if (data.permission == "qr-code") { - qrCodeResponse(data, callback); // TODO: are we going to support the qr-code permission? - } else if (data.isAllowed) { - if (data.permission == "web3") { - window.statusAppcurrentAccountAddress = data.data[0]; - } - callback.resolve(data.data); - } else { - callback.reject(new UserRejectedRequest()); - } - } else if (data.type === "web3-send-async-callback") { - if (callback.beta) { - if (data.error) { - if (data.error.code == 4100) { - callback.reject(new Unauthorized()); - } else { - callback.reject(data.error); + if (callback) { + if (data.type === "api-response") { + if (data.permission == "qr-code") { + qrCodeResponse(data, callback); // TODO: are we going to support the qr-code permission? + } else if (data.isAllowed) { + if (data.permission == "web3") { + window.statusAppcurrentAccountAddress = data.data[0]; } + callback.resolve(data.data); } else { - callback.resolve(data.result.result); + callback.reject(new UserRejectedRequest()); + } + } else if (data.type === "web3-send-async-callback") { + if (callback.beta) { + if (data.error) { + if (data.error.code == 4100) { + callback.reject(new Unauthorized()); + } else { + callback.reject(data.error); + } + } else { + callback.resolve(data.result.result); + } + } else if (callback.results) { + callback.results.push(data.error || data.result); + if (callback.results.length == callback.num) + callback.callback(undefined, callback.results); + } else { + callback.callback(data.error, data.result); } - } else if (callback.results) { - callback.results.push(data.error || data.result); - if (callback.results.length == callback.num) - callback.callback(undefined, callback.results); - } else { - callback.callback(data.error, data.result); } } + } catch(e) { + console.error(e) } } @@ -845,7 +848,10 @@ You may add additional accurate notices of copyright ownership. backend.web3Response.connect(onMessage); }); - const bridgeSend = data => backend.postMessage(JSON.stringify(data)); + const bridgeSend = data => { + data.hostname = new URL(document.location).host + backend.postMessage(JSON.stringify(data)); + } let history = window.history; let pushState = history.pushState; diff --git a/ui/imports/Constants.qml b/ui/imports/Constants.qml index e14ebf226f..3e916b9065 100644 --- a/ui/imports/Constants.qml +++ b/ui/imports/Constants.qml @@ -57,4 +57,11 @@ QtObject { "#887af9", "#8B3131" ] + + + readonly property string api_request: "api-request" + + readonly property string permission_web3: "web3" + readonly property string permission_contactCode: "contact-code" + }