|
@@ -33,18 +33,18 @@ import (
|
|
|
|
|
|
|
|
const JS = `
|
|
const JS = `
|
|
|
/**
|
|
/**
|
|
|
-This is an example implementation of a Javascript rule file.
|
|
|
|
|
|
|
+This is an example implementation of a Javascript rule file.
|
|
|
|
|
|
|
|
-When the signer receives a request over the external API, the corresponding method is evaluated.
|
|
|
|
|
-Three things can happen:
|
|
|
|
|
|
|
+When the signer receives a request over the external API, the corresponding method is evaluated.
|
|
|
|
|
+Three things can happen:
|
|
|
|
|
|
|
|
-1. The method returns "Approve". This means the operation is permitted.
|
|
|
|
|
-2. The method returns "Reject". This means the operation is rejected.
|
|
|
|
|
|
|
+1. The method returns "Approve". This means the operation is permitted.
|
|
|
|
|
+2. The method returns "Reject". This means the operation is rejected.
|
|
|
3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means
|
|
3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means
|
|
|
-that the operation will continue to manual processing, via the regular UI method chosen by the user.
|
|
|
|
|
|
|
+that the operation will continue to manual processing, via the regular UI method chosen by the user.
|
|
|
|
|
|
|
|
-[*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not
|
|
|
|
|
-only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all
|
|
|
|
|
|
|
+[*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not
|
|
|
|
|
+only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all
|
|
|
accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject").
|
|
accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject").
|
|
|
|
|
|
|
|
**/
|
|
**/
|
|
@@ -72,49 +72,49 @@ func mixAddr(a string) (*common.MixedcaseAddress, error) {
|
|
|
return common.NewMixedcaseAddressFromString(a)
|
|
return common.NewMixedcaseAddressFromString(a)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type alwaysDenyUi struct{}
|
|
|
|
|
|
|
+type alwaysDenyUI struct{}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) OnSignerStartup(info core.StartupInfo) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
|
|
|
return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
|
|
return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
|
|
return core.SignDataResponse{Approved: false, Password: ""}, nil
|
|
return core.SignDataResponse{Approved: false, Password: ""}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
|
return core.ExportResponse{Approved: false}, nil
|
|
return core.ExportResponse{Approved: false}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
|
return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil
|
|
return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
|
|
return core.ListResponse{Accounts: nil}, nil
|
|
return core.ListResponse{Accounts: nil}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
|
|
|
return core.NewAccountResponse{Approved: false, Password: ""}, nil
|
|
return core.NewAccountResponse{Approved: false, Password: ""}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ShowError(message string) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ShowError(message string) {
|
|
|
panic("implement me")
|
|
panic("implement me")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) ShowInfo(message string) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) ShowInfo(message string) {
|
|
|
panic("implement me")
|
|
panic("implement me")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (alwaysDenyUi) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
|
|
|
|
|
|
+func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
|
|
panic("implement me")
|
|
panic("implement me")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func initRuleEngine(js string) (*rulesetUi, error) {
|
|
|
|
|
- r, err := NewRuleEvaluator(&alwaysDenyUi{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
|
|
|
|
|
|
|
+func initRuleEngine(js string) (*rulesetUI, error) {
|
|
|
|
|
+ r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("failed to create js engine: %v", err)
|
|
return nil, fmt.Errorf("failed to create js engine: %v", err)
|
|
|
}
|
|
}
|
|
@@ -196,59 +196,59 @@ func TestSignTxRequest(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type dummyUi struct {
|
|
|
|
|
|
|
+type dummyUI struct {
|
|
|
calls []string
|
|
calls []string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
|
|
|
|
|
|
|
+func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
|
|
|
d.calls = append(d.calls, "ApproveTx")
|
|
d.calls = append(d.calls, "ApproveTx")
|
|
|
return core.SignTxResponse{}, core.ErrRequestDenied
|
|
return core.SignTxResponse{}, core.ErrRequestDenied
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
|
|
|
|
|
|
+func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
|
|
d.calls = append(d.calls, "ApproveSignData")
|
|
d.calls = append(d.calls, "ApproveSignData")
|
|
|
return core.SignDataResponse{}, core.ErrRequestDenied
|
|
return core.SignDataResponse{}, core.ErrRequestDenied
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
|
|
|
|
|
+func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
|
d.calls = append(d.calls, "ApproveExport")
|
|
d.calls = append(d.calls, "ApproveExport")
|
|
|
return core.ExportResponse{}, core.ErrRequestDenied
|
|
return core.ExportResponse{}, core.ErrRequestDenied
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
|
|
|
|
|
+func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
|
d.calls = append(d.calls, "ApproveImport")
|
|
d.calls = append(d.calls, "ApproveImport")
|
|
|
return core.ImportResponse{}, core.ErrRequestDenied
|
|
return core.ImportResponse{}, core.ErrRequestDenied
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
|
|
|
|
|
|
+func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
|
|
d.calls = append(d.calls, "ApproveListing")
|
|
d.calls = append(d.calls, "ApproveListing")
|
|
|
return core.ListResponse{}, core.ErrRequestDenied
|
|
return core.ListResponse{}, core.ErrRequestDenied
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
|
|
|
|
|
|
|
+func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
|
|
|
d.calls = append(d.calls, "ApproveNewAccount")
|
|
d.calls = append(d.calls, "ApproveNewAccount")
|
|
|
return core.NewAccountResponse{}, core.ErrRequestDenied
|
|
return core.NewAccountResponse{}, core.ErrRequestDenied
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ShowError(message string) {
|
|
|
|
|
|
|
+func (d *dummyUI) ShowError(message string) {
|
|
|
d.calls = append(d.calls, "ShowError")
|
|
d.calls = append(d.calls, "ShowError")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) ShowInfo(message string) {
|
|
|
|
|
|
|
+func (d *dummyUI) ShowInfo(message string) {
|
|
|
d.calls = append(d.calls, "ShowInfo")
|
|
d.calls = append(d.calls, "ShowInfo")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (d *dummyUi) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
|
|
|
|
|
|
+func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
|
|
d.calls = append(d.calls, "OnApprovedTx")
|
|
d.calls = append(d.calls, "OnApprovedTx")
|
|
|
}
|
|
}
|
|
|
-func (d *dummyUi) OnSignerStartup(info core.StartupInfo) {
|
|
|
|
|
|
|
+func (d *dummyUI) OnSignerStartup(info core.StartupInfo) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//TestForwarding tests that the rule-engine correctly dispatches requests to the next caller
|
|
//TestForwarding tests that the rule-engine correctly dispatches requests to the next caller
|
|
|
func TestForwarding(t *testing.T) {
|
|
func TestForwarding(t *testing.T) {
|
|
|
|
|
|
|
|
js := ""
|
|
js := ""
|
|
|
- ui := &dummyUi{make([]string, 0)}
|
|
|
|
|
|
|
+ ui := &dummyUI{make([]string, 0)}
|
|
|
jsBackend := storage.NewEphemeralStorage()
|
|
jsBackend := storage.NewEphemeralStorage()
|
|
|
credBackend := storage.NewEphemeralStorage()
|
|
credBackend := storage.NewEphemeralStorage()
|
|
|
r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
|
|
r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
|
|
@@ -308,22 +308,22 @@ func TestStorage(t *testing.T) {
|
|
|
function testStorage(){
|
|
function testStorage(){
|
|
|
storage.Put("mykey", "myvalue")
|
|
storage.Put("mykey", "myvalue")
|
|
|
a = storage.Get("mykey")
|
|
a = storage.Get("mykey")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
storage.Put("mykey", ["a", "list"]) // Should result in "a,list"
|
|
storage.Put("mykey", ["a", "list"]) // Should result in "a,list"
|
|
|
a += storage.Get("mykey")
|
|
a += storage.Get("mykey")
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]"
|
|
storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]"
|
|
|
a += storage.Get("mykey")
|
|
a += storage.Get("mykey")
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}'
|
|
storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}'
|
|
|
a += storage.Get("mykey")
|
|
a += storage.Get("mykey")
|
|
|
|
|
|
|
|
a += storage.Get("missingkey") //Missing keys should result in empty string
|
|
a += storage.Get("missingkey") //Missing keys should result in empty string
|
|
|
storage.Put("","missing key==noop") // Can't store with 0-length key
|
|
storage.Put("","missing key==noop") // Can't store with 0-length key
|
|
|
a += storage.Get("") // Should result in ''
|
|
a += storage.Get("") // Should result in ''
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
var b = new BigNumber(2)
|
|
var b = new BigNumber(2)
|
|
|
var c = new BigNumber(16)//"0xf0",16)
|
|
var c = new BigNumber(16)//"0xf0",16)
|
|
|
var d = b.plus(c)
|
|
var d = b.plus(c)
|
|
@@ -361,7 +361,7 @@ const ExampleTxWindow = `
|
|
|
if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
|
|
if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
|
|
|
return new BigNumber(str)
|
|
return new BigNumber(str)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Time window: 1 week
|
|
// Time window: 1 week
|
|
|
var window = 1000* 3600*24*7;
|
|
var window = 1000* 3600*24*7;
|
|
|
|
|
|
|
@@ -370,7 +370,7 @@ const ExampleTxWindow = `
|
|
|
|
|
|
|
|
function isLimitOk(transaction){
|
|
function isLimitOk(transaction){
|
|
|
var value = big(transaction.value)
|
|
var value = big(transaction.value)
|
|
|
- // Start of our window function
|
|
|
|
|
|
|
+ // Start of our window function
|
|
|
var windowstart = new Date().getTime() - window;
|
|
var windowstart = new Date().getTime() - window;
|
|
|
|
|
|
|
|
var txs = [];
|
|
var txs = [];
|
|
@@ -382,17 +382,17 @@ const ExampleTxWindow = `
|
|
|
// First, remove all that have passed out of the time-window
|
|
// First, remove all that have passed out of the time-window
|
|
|
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
|
|
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
|
|
|
console.log(txs, newtxs.length);
|
|
console.log(txs, newtxs.length);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Secondly, aggregate the current sum
|
|
// Secondly, aggregate the current sum
|
|
|
sum = new BigNumber(0)
|
|
sum = new BigNumber(0)
|
|
|
|
|
|
|
|
sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
|
|
sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
|
|
|
console.log("ApproveTx > Sum so far", sum);
|
|
console.log("ApproveTx > Sum so far", sum);
|
|
|
console.log("ApproveTx > Requested", value.toNumber());
|
|
console.log("ApproveTx > Requested", value.toNumber());
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Would we exceed weekly limit ?
|
|
// Would we exceed weekly limit ?
|
|
|
return sum.plus(value).lt(limit)
|
|
return sum.plus(value).lt(limit)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
function ApproveTx(r){
|
|
function ApproveTx(r){
|
|
|
console.log(r)
|
|
console.log(r)
|
|
@@ -405,14 +405,14 @@ const ExampleTxWindow = `
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
|
|
* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
|
|
|
- * 'response_str' contains the return value that will be sent to the external caller.
|
|
|
|
|
- * The return value from this method is ignore - the reason for having this callback is to allow the
|
|
|
|
|
- * ruleset to keep track of approved transactions.
|
|
|
|
|
|
|
+ * 'response_str' contains the return value that will be sent to the external caller.
|
|
|
|
|
+ * The return value from this method is ignore - the reason for having this callback is to allow the
|
|
|
|
|
+ * ruleset to keep track of approved transactions.
|
|
|
*
|
|
*
|
|
|
- * When implementing rate-limited rules, this callback should be used.
|
|
|
|
|
|
|
+ * When implementing rate-limited rules, this callback should be used.
|
|
|
* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
|
|
* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
|
|
|
* then accepts the transaction, this method will be called.
|
|
* then accepts the transaction, this method will be called.
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
|
|
* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
|
|
|
*/
|
|
*/
|
|
|
function OnApprovedTx(resp){
|
|
function OnApprovedTx(resp){
|