improvements

This commit is contained in:
wighawag 2016-05-09 16:41:46 +01:00
parent d7bdc8c10a
commit 15e4c31d44
1 changed files with 415 additions and 386 deletions

View File

@ -9,9 +9,9 @@
Abstract
========
This draft EIP describes the details of an authorization method that if provided by rpc enabled ethereum nodes would allow regular websites to send transactions (via ```eth_sendTransaction```) without the need to enable CORS for the website's domain but instead with the required consent from the users.
This draft EIP describes the details of an authorization method that if provided by rpc enabled ethereum nodes would allow regular websites to send transactions (via ```eth_sendTransaction```) without the need to enable CORS. Instead, user would be asked to confirm the transaction via an html popup.
For every transaction that the dapp wish to execute, an html popup is presented to the user to allow him/her to cancel or confirm the transaction. This allow users to safely interact with dapp running in their everyday web browser while their accounts are unlocked. In case the account is not unlocked and the node has allowed the "personal" api via rpc,the html page also allow the user to enter their password to unlock the account for the scope of the transaction.
Every read only rpc calls the dapp want to perform are redirected to an invisible iframe from the node's domain and for every transaction that the dapp wish to execute, an html popup is presented to the user to allow him/her to cancel or confirm the transaction. This allow the dapp to connect to the node's rpc api without being granted any kind of privileges. This allow users to safely interact with dapp running in their everyday web browser while their accounts are unlocked. In case the account is not unlocked and the node has allowed the "personal" api via rpc,the html page also allow the user to enter their password to unlock the account for the scope of the transaction.
Motivation
==========
@ -20,7 +20,7 @@ Currently, if a user navigate to a dapp running on a website using her/his every
- For more complex case, the user is asked to enter the transaction manually via the node command line interface.
This proposal aims to provide a safe and user friendly alternative:
This proposal aims to provide a safe and user friendly alternative.
Here are some screenshot of the provided implementation of that html popup:
@ -49,15 +49,46 @@ In order for the mechanism to work, the node need to serve an html file via http
This file will then be used by the dapp in 2 different modes (invisible iframe and popup window).
The invisible iframe will be embeded in the dapp to allow the dapp to send its read-only rpc call without having to enable CORS for the dapp's website domain. This is done by sending message to the iframe (via javascript ```window.postMessage```) which in turn execute the rpc call. This works since the iframe and the node share the same domain/port. The iframe first message is a message containing the string "ready" to let the parent know that it now accepts messages.
The invisible iframe will be embeded in the dapp to allow the dapp to send its read-only rpc call without having to enable CORS for the dapp's website domain. This is done by sending message to the iframe (via javascript ```window.postMessage```) which in turn execute the rpc call. This works since the iframe and the node share the same domain/port.
In iframe node the html file's javascript code will ensure that no call requiring an unlocked key can be made. This is to prevent dapp for embedding the visible iframe and tricking the user into clicking the confirm button.
If the dapp requires to make an ```eth_sendTransaction``` call, the dapp will instead open a new window using the same url.
In this popup window mode, the html file's javascript code will alow ```eth_sendTransaction``` (but not ```eth_sign``` as there is no way to display to the user the meaningfull content of the transaction to sign in a safe way) to be called. But instead of sending the call to the node directly, a confirmation dialog will be presented showing the sender and recipient addresses as well the amount being transfered along with the potential gas cost. Upon the user approving, the request will be sent and the result returned to the dapp. An error will be returned in case the user cancel the request. Similarly to the iframe mode, the window first message is a message containing the string "ready" to let the opener know that it now accepts messages.
In this popup window mode, the html file's javascript code will alow ```eth_sendTransaction``` (but not ```eth_sign``` as there is no way to display to the user the meaningfull content of the transaction to sign in a safe way) to be called. But instead of sending the call to the node directly, a confirmation dialog will be presented showing the sender and recipient addresses as well the amount being transfered along with the potential gas cost. Upon the user approving, the request will be sent and the result returned to the dapp. An error will be returned in case the user cancel the request.
The html page also check for the availability of the "personal" api and if so, will ask the user to unlock the account if necessary. The unlocking is temporary (3s) so the password will be asked again if a transaction is attempted before the end of this short time.
In both iframe mode and window mode, the communication with the dapp is achieved using ```window.postMessage```.
The fist message the iframe/window send is a message containing the string "ready" to let the dapp know that it now accepts messages. Then the dapp can start performing rpc call by sending message using the following object :
```
{
id:<a an id>, //so responses can be match as there is no guarantee of the order of the response
payload:<json rpc object> //this is the exact object that usually send to the node
}
```
For ```eth_sendTransaction``` the "gas", "gasPrice" and "from" field need to be set in the rpc parameter so that the window can display the correct value. If not all of these are passed in, the window will return an error.
Upon receiving such message, the iframe will perform the actual rpc call to the node but only if such a call is a read only call (not requiring an unlocked key). If it is not it will return a error. The window on the other will also accept ```eth_sendTransaction``` calls but will display a dialog so the user can accept or cancel the request.
In all the cases, the iframe/window will send a message back to the dapp using the following object:
```
{
id:<id matchign the request>,
result:<rpc result as is>,
error:<error object>
}
```
the error object cannot be a javascript Error object due to postMessage limitation. Instead it is
```
{
message:<a string describing the error>,
type:<a string defining the type of error> //type="cancel" means the user cancel the transaction
}
```
Rationale
=========
The design for that proposal was chosen for its simplicity and security. A previous idea was to use an oauth-like protocol in order for the user to accept or deny a transaction request. It would have required deeper code change in the node and some geth contributors argues that such change did not fit into geth code base as it would have required dapp aware code.
@ -68,7 +99,8 @@ The use of iframe/ window was required to have both security and user friendline
Implementations
===============
In order to implement this design, the following html file need to be served at the url \<node url\>/authorization.html
In order to implement this design, the following html file or an equivalent one need to be served at the url \<node url\>/authorization.html
That's it
@ -235,7 +267,7 @@ That's it
}else if(window.parent != window){
source = window.parent;
}else{
console.error("no opener nor parent");
window.close();
}
function showWaiting(){
@ -273,7 +305,6 @@ That's it
}
function sendAsync(url,payload, callback) {
//var url = window.location.protocol + '//' + window.location.host; // this would force the use of the iframe url
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type','application/json');
@ -286,7 +317,7 @@ That's it
result = JSON.parse(result);
} catch(e) {
var message = !!result && !!result.error && !!result.error.message ? result.error.message : 'Invalid JSON RPC response: ' + JSON.stringify(result);
error = {message:message};
error = {message:message,type:"JsonParse"};
}
callback(error, result);
}
@ -295,7 +326,7 @@ That's it
try {
request.send(JSON.stringify(payload));
} catch(e) {
callback({message:'CONNECTION ERROR: Couldn\'t connect to node '+ url +'.'});
callback({message:'CONNECTION ERROR: Couldn\'t connect to node '+ url +'.',type:"noConnection"});
}
}
@ -305,8 +336,7 @@ That's it
size: 8,
scale: 6
});
//icon.style="vertical-align:middle";
message.appendChild(icon); //
message.appendChild(icon);
}
function askAuthorization(transactionInfo, data, requireUnlock, sourceWindow){
@ -331,7 +361,6 @@ That's it
var span = document.createElement('span');
span.style="font-size:3em;";
span.innerHTML = "&nbsp;&nbsp;&nbsp;" + "&#x2192;" + "&nbsp;&nbsp;&nbsp;";
//span.innerHTML = "&#10145;";
message.appendChild(span);
addBlocky(message,transactionInfo.to);
@ -342,13 +371,13 @@ That's it
textSpan.innerHTML = etherValue.toFormat() + " ether <br/> + gas cost (" + gasEtherValue.toFormat() + " ether )"
if(requireUnlock){
passwordField.style.display = "block"; //inline ?
passwordField.style.display = "block";
}else{
passwordField.style.display = "none";
}
cancelButton.onclick = function(){
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Not Authorized"}},sourceWindow.location.href);
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Not Authorized"},type:"cancel"},sourceWindow.location.href);
window.close();
}
@ -365,7 +394,7 @@ That's it
if(error || result.error){
showMessage("Error unlocking account", "Please retry.", hideWaiting);
}else{
sendAsync(data.url,data.payload,function(error,result){ //data.url to allow use of different but trusted domain
sendAsync(data.url,data.payload,function(error,result){
sourceWindow.postMessage({id:data.id,result:result,error:error},sourceWindow.location.href);
window.close();
});
@ -373,7 +402,7 @@ That's it
}
});
}else{
sendAsync(data.url,data.payload,function(error,result){ //data.url to allow use of different but trusted domain
sendAsync(data.url,data.payload,function(error,result){
if(result && result.error){
processMessage(data,sourceWindow);
}else{
@ -419,9 +448,9 @@ That's it
function processMessage(data, sourceWindow){
if(data.payload.method == "eth_sendTransaction" || data.payload.method == "eth_sign"){
if(inIframe){
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Cannot make call that require an unlokced key (" + data.payload.method + ") via iframe"}},sourceWindow.location.href);
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Cannot make call that require an unlocked key (" + data.payload.method + ") via iframe"},type:"notAllowed"},sourceWindow.location.href);
}else if(data.payload.method == "eth_sign"){
sourceWindow.postMessage({id:data.id,result:null,error:{message:"cannot sign transaction (" + data.payload.method + ") via html"}},sourceWindow.location.href);
sourceWindow.postMessage({id:data.id,result:null,error:{message:"cannot sign transaction (" + data.payload.method + ") via html",type:"notAllowed"}},sourceWindow.location.href);
}else{
var transactionInfo = null;
if(data.payload.params.length > 0){
@ -450,12 +479,12 @@ That's it
});
}else{
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Need to specify from , to, gas and gasPrice"}},sourceWindow.location.href);
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Need to specify from , to, gas and gasPrice"},type:"notValid"},sourceWindow.location.href);
window.close();
}
}
}else{
sendAsync(data.url,data.payload,function(error,result){ //data.url to allow use of different but trusted domain
sendAsync(data.url,data.payload,function(error,result){
sourceWindow.postMessage({id:data.id,result:result,error:error},sourceWindow.location.href);
});
}