mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 13:26:47 +01:00
Simplify vault logic by introducing a VaultClient (#5434)
This commit is contained in:
parent
89041a6744
commit
b702621a04
@ -50,9 +50,10 @@ namespace BTCPayServer.Controllers
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
var vaultClient = new VaultClient(websocket);
|
||||
var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
|
||||
{
|
||||
Transport = new HwiWebSocketTransport(websocket)
|
||||
Transport = new VaultHWITransport(vaultClient)
|
||||
};
|
||||
Hwi.HwiDeviceClient device = null;
|
||||
HwiEnumerateEntry deviceEntry = null;
|
||||
|
@ -1,24 +0,0 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class HwiWebSocketTransport : Hwi.Transports.ITransport
|
||||
{
|
||||
private readonly WebSocketHelper _webSocket;
|
||||
|
||||
public HwiWebSocketTransport(WebSocket webSocket)
|
||||
{
|
||||
_webSocket = new WebSocketHelper(webSocket);
|
||||
}
|
||||
public async Task<string> SendCommandAsync(string[] arguments, CancellationToken cancel)
|
||||
{
|
||||
JObject request = new JObject();
|
||||
request.Add("params", new JArray(arguments));
|
||||
await _webSocket.Send(request.ToString(), cancel);
|
||||
return await _webSocket.NextMessageAsync(cancel);
|
||||
}
|
||||
}
|
||||
}
|
129
BTCPayServer/VaultClient.cs
Normal file
129
BTCPayServer/VaultClient.cs
Normal file
@ -0,0 +1,129 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon.S3.Model.Internal.MarshallTransformations;
|
||||
using ExchangeSharp;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public enum VaultMessageType
|
||||
{
|
||||
Ok,
|
||||
Error,
|
||||
Processing
|
||||
}
|
||||
public enum VaultServices
|
||||
{
|
||||
HWI,
|
||||
NFC
|
||||
}
|
||||
|
||||
public class VaultNotConnectedException : VaultException
|
||||
{
|
||||
public VaultNotConnectedException() : base("BTCPay Vault isn't connected")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public class VaultException : Exception
|
||||
{
|
||||
public VaultException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public class VaultClient
|
||||
{
|
||||
public VaultClient(WebSocket websocket)
|
||||
{
|
||||
Websocket = new WebSocketHelper(websocket);
|
||||
}
|
||||
|
||||
public WebSocketHelper Websocket { get; }
|
||||
|
||||
public async Task<string> GetNextCommand(CancellationToken cancellationToken)
|
||||
{
|
||||
return await Websocket.NextMessageAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task SendMessage(JObject mess, CancellationToken cancellationToken)
|
||||
{
|
||||
await Websocket.Send(mess.ToString(), cancellationToken);
|
||||
}
|
||||
|
||||
public Task Show(VaultMessageType type, string message, CancellationToken cancellationToken)
|
||||
{
|
||||
return Show(type, message, null, cancellationToken);
|
||||
}
|
||||
public async Task Show(VaultMessageType type, string message, string? debug, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
await SendMessage(new JObject()
|
||||
{
|
||||
["command"] = "showMessage",
|
||||
["message"] = message,
|
||||
["type"] = type.ToString(),
|
||||
["debug"] = debug
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
string? _ServiceUri;
|
||||
public async Task<bool?> AskPermission(VaultServices service, CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = service switch
|
||||
{
|
||||
VaultServices.HWI => "http://127.0.0.1:65092/hwi-bridge/v1",
|
||||
VaultServices.NFC => "http://127.0.0.1:65092/nfc-bridge/v1",
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
await this.SendMessage(new JObject()
|
||||
{
|
||||
["command"] = "sendRequest",
|
||||
["uri"] = uri + "/request-permission"
|
||||
}, cancellationToken);
|
||||
var result = await GetNextMessage(cancellationToken);
|
||||
if (result["httpCode"] is { } p)
|
||||
{
|
||||
var ok = p.Value<int>() == 200;
|
||||
if (ok)
|
||||
_ServiceUri = uri;
|
||||
return ok;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<JToken?> SendVaultRequest(string? path, JObject? body, CancellationToken cancellationToken)
|
||||
{
|
||||
var isAbsolute = path is not null && Uri.IsWellFormedUriString(path, UriKind.Absolute);
|
||||
var query = new JObject()
|
||||
{
|
||||
["command"] = "sendRequest",
|
||||
["uri"] = isAbsolute ? path : _ServiceUri + path
|
||||
};
|
||||
if (body is not null)
|
||||
query["body"] = body;
|
||||
await this.SendMessage(query, cancellationToken);
|
||||
var resp = await GetNextMessage(cancellationToken);
|
||||
if (resp["httpCode"] is not { } p)
|
||||
throw new VaultNotConnectedException();
|
||||
if (p.Value<int>() != 200)
|
||||
throw new VaultException($"Unexpected response code from vault {p.Value<int>()}");
|
||||
return resp["body"] as JToken;
|
||||
}
|
||||
|
||||
public async Task<JObject> GetNextMessage(CancellationToken cancellationToken)
|
||||
{
|
||||
return JObject.Parse(await this.Websocket.NextMessageAsync(cancellationToken));
|
||||
}
|
||||
|
||||
public Task SendSimpleMessage(string command, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendMessage(new JObject() { ["command"] = command }, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
26
BTCPayServer/VaultHWITransport.cs
Normal file
26
BTCPayServer/VaultHWITransport.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class VaultHWITransport : Hwi.Transports.ITransport
|
||||
{
|
||||
private readonly VaultClient _vaultClient;
|
||||
|
||||
public VaultHWITransport(VaultClient vaultClient)
|
||||
{
|
||||
_vaultClient = vaultClient;
|
||||
}
|
||||
public async Task<string> SendCommandAsync(string[] arguments, CancellationToken cancel)
|
||||
{
|
||||
var resp = await _vaultClient.SendVaultRequest("http://127.0.0.1:65092/hwi-bridge/v1",
|
||||
new JObject()
|
||||
{
|
||||
["params"] = new JArray(arguments)
|
||||
}, cancel);
|
||||
return (string)((JValue)resp).Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,12 @@
|
||||
<div class="vault-feedback vault-feedback3 mb-2 d-flex">
|
||||
<span class="vault-feedback-icon mt-1 me-2"></span> <span class="vault-feedback-content flex-grow"></span>
|
||||
</div>
|
||||
<div class="vault-feedback vault-feedback4 mb-2 d-flex">
|
||||
<span class="vault-feedback-icon mt-1 me-2"></span> <span class="vault-feedback-content flex-grow"></span>
|
||||
</div>
|
||||
<div class="vault-feedback vault-feedback5 mb-2 d-flex">
|
||||
<span class="vault-feedback-icon mt-1 me-2"></span> <span class="vault-feedback-content flex-grow"></span>
|
||||
</div>
|
||||
<div id="pin-input" class="mt-4" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
@ -7,8 +7,6 @@ var vault = (function () {
|
||||
* @type {WebSocket}
|
||||
*/
|
||||
this.socket = websocket;
|
||||
this.onerror = function (error) { };
|
||||
this.onbackendmessage = function (json) { };
|
||||
this.close = function () { if (websocket) websocket.close(); };
|
||||
/**
|
||||
* @returns {Promise}
|
||||
@ -23,28 +21,37 @@ var vault = (function () {
|
||||
if (event.data === "ping")
|
||||
return;
|
||||
var jsonObject = JSON.parse(event.data);
|
||||
if (jsonObject.hasOwnProperty("params")) {
|
||||
if (jsonObject.command == "sendRequest") {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4 && request.status == 200) {
|
||||
if (self.socket.readyState == 1)
|
||||
self.socket.send(request.responseText);
|
||||
else
|
||||
self.onerror(vault.errors.socketError);
|
||||
}
|
||||
if (request.readyState == 4 && request.status == 0) {
|
||||
self.onerror(vault.errors.notRunning);
|
||||
}
|
||||
if (request.readyState == 4 && request.status == 401) {
|
||||
self.onerror(vault.errors.denied);
|
||||
if (request.readyState == 4) {
|
||||
if (request.status === 0) {
|
||||
self.socket.send("{\"error\": \"Failed to connect to uri\"}");
|
||||
}
|
||||
else if (self.socket.readyState == 1) {
|
||||
var body = null;
|
||||
if (request.responseText) {
|
||||
var contentType = request.getResponseHeader('Content-Type') || 'text/plain';
|
||||
if (contentType === 'text/plain')
|
||||
body = request.responseText;
|
||||
else
|
||||
body = JSON.parse(request.responseText);
|
||||
}
|
||||
|
||||
self.socket.send(JSON.stringify(
|
||||
{
|
||||
httpCode: request.status,
|
||||
body: body
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
request.overrideMimeType("text/plain");
|
||||
request.open('POST', 'http://127.0.0.1:65092/hwi-bridge/v1');
|
||||
request.send(JSON.stringify(jsonObject));
|
||||
request.open('POST', jsonObject.uri);
|
||||
jsonObject.body = jsonObject.body || {};
|
||||
request.send(JSON.stringify(jsonObject.body));
|
||||
}
|
||||
else {
|
||||
self.onbackendmessage(jsonObject);
|
||||
if (self.nextResolveBackendMessage)
|
||||
self.nextResolveBackendMessage(jsonObject);
|
||||
}
|
||||
|
@ -6,52 +6,50 @@ var vaultui = (function () {
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {string} txt
|
||||
* @param {string} category
|
||||
* @param {string} id
|
||||
*/
|
||||
function VaultFeedback(type, txt, category, id) {
|
||||
function VaultFeedback(type, txt, id) {
|
||||
var self = this;
|
||||
this.type = type;
|
||||
this.txt = txt;
|
||||
this.category = category;
|
||||
this.id = id;
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {string} by
|
||||
*/
|
||||
this.replace = function (str, by) {
|
||||
return new VaultFeedback(self.type, self.txt.replace(str, by), self.category, self.id);
|
||||
return new VaultFeedback(self.type, self.txt.replace(str, by), self.id);
|
||||
};
|
||||
}
|
||||
|
||||
var VaultFeedbacks = {
|
||||
vaultLoading: new VaultFeedback("?", "Checking BTCPay Server Vault is running...", "vault-feedback1", "vault-loading"),
|
||||
vaultDenied: new VaultFeedback("failed", "The user declined access to the vault.", "vault-feedback1", "vault-denied"),
|
||||
vaultGranted: new VaultFeedback("ok", "Access to vault granted by owner.", "vault-feedback1", "vault-granted"),
|
||||
noVault: new VaultFeedback("failed", "BTCPay Server Vault does not seem to be running, you can download it on <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">Github</a>.", "vault-feedback1", "no-vault"),
|
||||
noWebsockets: new VaultFeedback("failed", "Web sockets are not supported by the browser.", "vault-feedback1", "no-websocket"),
|
||||
errorWebsockets: new VaultFeedback("failed", "Error of the websocket while connecting to the backend.", "vault-feedback1", "error-websocket"),
|
||||
bridgeConnected: new VaultFeedback("ok", "BTCPayServer successfully connected to the vault.", "vault-feedback1", "bridge-connected"),
|
||||
vaultNeedUpdate: new VaultFeedback("failed", "Your BTCPay Server Vault version is outdated. Please <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">download</a> the latest version.", "vault-feedback2", "vault-outdated"),
|
||||
noDevice: new VaultFeedback("failed", "No device connected.", "vault-feedback2", "no-device"),
|
||||
needInitialized: new VaultFeedback("failed", "The device has not been initialized.", "vault-feedback2", "need-initialized"),
|
||||
fetchingDevice: new VaultFeedback("?", "Fetching device...", "vault-feedback2", "fetching-device"),
|
||||
deviceFound: new VaultFeedback("ok", "Device found: {{0}}", "vault-feedback2", "device-selected"),
|
||||
fetchingXpubs: new VaultFeedback("?", "Fetching public keys...", "vault-feedback3", "fetching-xpubs"),
|
||||
askXpubs: new VaultFeedback("?", "Select your address type and account", "vault-feedback3", "fetching-xpubs"),
|
||||
fetchedXpubs: new VaultFeedback("ok", "Public keys successfully fetched.", "vault-feedback3", "xpubs-fetched"),
|
||||
unexpectedError: new VaultFeedback("failed", "An unexpected error happened. ({{0}})", "vault-feedback3", "unknown-error"),
|
||||
invalidNetwork: new VaultFeedback("failed", "The device is targeting a different chain.", "vault-feedback3", "invalid-network"),
|
||||
needPin: new VaultFeedback("?", "Enter the pin.", "vault-feedback3", "need-pin"),
|
||||
incorrectPin: new VaultFeedback("failed", "Incorrect pin code.", "vault-feedback3", "incorrect-pin"),
|
||||
invalidPasswordConfirmation: new VaultFeedback("failed", "Invalid password confirmation.", "vault-feedback3", "invalid-password-confirm"),
|
||||
wrongWallet: new VaultFeedback("failed", "This device can't sign the transaction. (Wrong device, wrong passphrase or wrong device fingerprint in your wallet settings)", "vault-feedback3", "wrong-wallet"),
|
||||
wrongKeyPath: new VaultFeedback("failed", "This device can't sign the transaction. (The wallet keypath in your wallet settings seems incorrect)", "vault-feedback3", "wrong-keypath"),
|
||||
needPassphrase: new VaultFeedback("?", "Enter the passphrase.", "vault-feedback3", "need-passphrase"),
|
||||
needPassphraseOnDevice: new VaultFeedback("?", "Please, enter the passphrase on the device.", "vault-feedback3", "need-passphrase-on-device"),
|
||||
signingTransaction: new VaultFeedback("?", "Please review and confirm the transaction on your device...", "vault-feedback3", "ask-signing"),
|
||||
reviewAddress: new VaultFeedback("?", "Sending... Please review the address on your device...", "vault-feedback3", "ask-signing"),
|
||||
signingRejected: new VaultFeedback("failed", "The user refused to sign the transaction", "vault-feedback3", "user-reject"),
|
||||
vaultLoading: new VaultFeedback("?", "Checking BTCPay Server Vault is running...", "vault-loading"),
|
||||
vaultDenied: new VaultFeedback("failed", "The user declined access to the vault.", "vault-denied"),
|
||||
vaultGranted: new VaultFeedback("ok", "Access to vault granted by owner.", "vault-granted"),
|
||||
noVault: new VaultFeedback("failed", "BTCPay Server Vault does not seem to be running, you can download it on <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">Github</a>.", "no-vault"),
|
||||
noWebsockets: new VaultFeedback("failed", "Web sockets are not supported by the browser.", "no-websocket"),
|
||||
errorWebsockets: new VaultFeedback("failed", "Error of the websocket while connecting to the backend.", "error-websocket"),
|
||||
bridgeConnected: new VaultFeedback("ok", "BTCPayServer successfully connected to the vault.", "bridge-connected"),
|
||||
vaultNeedUpdate: new VaultFeedback("failed", "Your BTCPay Server Vault version is outdated. Please <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">download</a> the latest version.", "vault-outdated"),
|
||||
noDevice: new VaultFeedback("failed", "No device connected.", "no-device"),
|
||||
needInitialized: new VaultFeedback("failed", "The device has not been initialized.", "need-initialized"),
|
||||
fetchingDevice: new VaultFeedback("?", "Fetching device...", "fetching-device"),
|
||||
deviceFound: new VaultFeedback("ok", "Device found: {{0}}", "device-selected"),
|
||||
fetchingXpubs: new VaultFeedback("?", "Fetching public keys...", "fetching-xpubs"),
|
||||
askXpubs: new VaultFeedback("?", "Select your address type and account", "fetching-xpubs"),
|
||||
fetchedXpubs: new VaultFeedback("ok", "Public keys successfully fetched.", "xpubs-fetched"),
|
||||
unexpectedError: new VaultFeedback("failed", "An unexpected error happened. ({{0}})", "unknown-error"),
|
||||
invalidNetwork: new VaultFeedback("failed", "The device is targeting a different chain.", "invalid-network"),
|
||||
needPin: new VaultFeedback("?", "Enter the pin.", "need-pin"),
|
||||
incorrectPin: new VaultFeedback("failed", "Incorrect pin code.", "incorrect-pin"),
|
||||
invalidPasswordConfirmation: new VaultFeedback("failed", "Invalid password confirmation.", "invalid-password-confirm"),
|
||||
wrongWallet: new VaultFeedback("failed", "This device can't sign the transaction. (Wrong device, wrong passphrase or wrong device fingerprint in your wallet settings)", "wrong-wallet"),
|
||||
wrongKeyPath: new VaultFeedback("failed", "This device can't sign the transaction. (The wallet keypath in your wallet settings seems incorrect)", "wrong-keypath"),
|
||||
needPassphrase: new VaultFeedback("?", "Enter the passphrase.", "need-passphrase"),
|
||||
needPassphraseOnDevice: new VaultFeedback("?", "Please, enter the passphrase on the device.", "need-passphrase-on-device"),
|
||||
signingTransaction: new VaultFeedback("?", "Please review and confirm the transaction on your device...", "ask-signing"),
|
||||
reviewAddress: new VaultFeedback("?", "Sending... Please review the address on your device...", "ask-signing"),
|
||||
signingRejected: new VaultFeedback("failed", "The user refused to sign the transaction", "user-reject"),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -83,11 +81,13 @@ var vaultui = (function () {
|
||||
button.show();
|
||||
}
|
||||
|
||||
this.currentFeedback = 1;
|
||||
|
||||
/**
|
||||
* @param {VaultFeedback} feedback
|
||||
*/
|
||||
function show(feedback) {
|
||||
var icon = $(".vault-feedback." + feedback.category + " " + ".vault-feedback-icon");
|
||||
var icon = $(".vault-feedback.vault-feedback" + self.currentFeedback + " " + ".vault-feedback-icon");
|
||||
icon.removeClass();
|
||||
icon.addClass("vault-feedback-icon mt-1 me-2");
|
||||
if (feedback.type == "?") {
|
||||
@ -100,8 +100,12 @@ var vaultui = (function () {
|
||||
icon.addClass("fa fa-times-circle feedback-icon-failed");
|
||||
showRetry();
|
||||
}
|
||||
var content = $(".vault-feedback." + feedback.category + " " + ".vault-feedback-content");
|
||||
var content = $(".vault-feedback.vault-feedback" + self.currentFeedback + " " + ".vault-feedback-content");
|
||||
content.html(feedback.txt);
|
||||
if (feedback.type === 'ok')
|
||||
self.currentFeedback++;
|
||||
if (feedback.type === 'failed')
|
||||
self.currentFeedback = 1;
|
||||
}
|
||||
function showError(json) {
|
||||
if (json.hasOwnProperty("error")) {
|
||||
@ -182,7 +186,7 @@ var vaultui = (function () {
|
||||
if (self.retryShowing) {
|
||||
await self.waitRetryPushed();
|
||||
}
|
||||
if (!self.bridge) {
|
||||
if (!self.bridge || self.bridge.socket.readyState !== 1) {
|
||||
$("#vault-dropdown").css("display", "none");
|
||||
show(VaultFeedbacks.vaultLoading);
|
||||
try {
|
||||
@ -208,6 +212,7 @@ var vaultui = (function () {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
this.askForDisplayAddress = async function (rootedKeyPath) {
|
||||
if (!await self.ensureConnectedToBackend())
|
||||
return false;
|
||||
@ -235,6 +240,7 @@ var vaultui = (function () {
|
||||
show(VaultFeedbacks.deviceFound.replace("{{0}}", json.model));
|
||||
return true;
|
||||
};
|
||||
|
||||
this.askForXPubs = async function () {
|
||||
if (!await self.ensureConnectedToBackend())
|
||||
return false;
|
||||
|
Loading…
Reference in New Issue
Block a user