mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
203 lines
10 KiB
C#
203 lines
10 KiB
C#
using BTCPayServer.Abstractions.Constants;
|
|
using BTCPayServer.Abstractions.Extensions;
|
|
using BTCPayServer.Data;
|
|
using BTCPayServer.Lightning;
|
|
using BTCPayServer.Models;
|
|
using BTCPayServer.NTag424;
|
|
using Dapper;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NBitcoin.DataEncoders;
|
|
using System;
|
|
using System.Net.WebSockets;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using static BTCPayServer.BoltcardDataExtensions;
|
|
|
|
namespace BTCPayServer.Controllers
|
|
{
|
|
public partial class UIPullPaymentController
|
|
{
|
|
[AllowAnonymous]
|
|
[HttpGet("pull-payments/{pullPaymentId}/boltcard/{command}")]
|
|
public IActionResult SetupBoltcard(string pullPaymentId, string command)
|
|
{
|
|
return View(nameof(SetupBoltcard), new SetupBoltcardViewModel()
|
|
{
|
|
ReturnUrl = Url.Action(nameof(ViewPullPayment), "UIPullPayment", new { pullPaymentId }),
|
|
WebsocketPath = Url.Action(nameof(VaultNFCBridgeConnection), "UIPullPayment", new { pullPaymentId }),
|
|
Command = command
|
|
});
|
|
}
|
|
[AllowAnonymous]
|
|
[HttpPost("pull-payments/{pullPaymentId}/boltcard/{command}")]
|
|
public IActionResult SetupBoltcardPost(string pullPaymentId, string command)
|
|
{
|
|
TempData[WellKnownTempData.SuccessMessage] = "Boltcard is configured";
|
|
return RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId });
|
|
}
|
|
|
|
record CardOrigin
|
|
{
|
|
public record Blank() : CardOrigin;
|
|
public record ThisIssuer(BoltcardRegistration Registration) : CardOrigin;
|
|
public record ThisIssuerConfigured(string PullPaymentId, BoltcardRegistration Registration) : ThisIssuer(Registration);
|
|
public record OtherIssuer() : CardOrigin;
|
|
public record ThisIssuerReset(BoltcardRegistration Registration) : ThisIssuer(Registration);
|
|
}
|
|
|
|
[Route("pull-payments/{pullPaymentId}/nfc/bridge")]
|
|
public async Task<IActionResult> VaultNFCBridgeConnection(string pullPaymentId)
|
|
{
|
|
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
|
return NotFound();
|
|
var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, false);
|
|
if (pp is null)
|
|
return NotFound();
|
|
if (!_pullPaymentHostedService.SupportsLNURL(pp))
|
|
return BadRequest();
|
|
|
|
var boltcardUrl = Url.Action(nameof(UIBoltcardController.GetWithdrawRequest), "UIBoltcard");
|
|
boltcardUrl = Request.GetAbsoluteUri(boltcardUrl);
|
|
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
|
var vaultClient = new VaultClient(websocket);
|
|
var transport = new APDUVaultTransport(vaultClient);
|
|
var ntag = new Ntag424(transport);
|
|
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
|
|
{
|
|
next:
|
|
while (websocket.State == System.Net.WebSockets.WebSocketState.Open)
|
|
{
|
|
try
|
|
{
|
|
var command = await vaultClient.GetNextCommand(cts.Token);
|
|
var permission = await vaultClient.AskPermission(VaultServices.NFC, cts.Token);
|
|
if (permission is null)
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Error, "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>.", cts.Token);
|
|
goto next;
|
|
}
|
|
await vaultClient.Show(VaultMessageType.Ok, "BTCPayServer successfully connected to the vault.", cts.Token);
|
|
if (permission is false)
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Error, "The user declined access to the vault.", cts.Token);
|
|
goto next;
|
|
}
|
|
await vaultClient.Show(VaultMessageType.Ok, "Access to vault granted by owner.", cts.Token);
|
|
|
|
await vaultClient.Show(VaultMessageType.Processing, "Waiting for NFC to be presented...", cts.Token);
|
|
await transport.WaitForCard(cts.Token);
|
|
await vaultClient.Show(VaultMessageType.Ok, "NFC detected.", cts.Token);
|
|
|
|
var issuerKey = await _settingsRepository.GetIssuerKey(_env);
|
|
CardOrigin cardOrigin = await GetCardOrigin(pullPaymentId, ntag, issuerKey, cts.Token);
|
|
|
|
if (cardOrigin is CardOrigin.OtherIssuer)
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Error, "This card is already configured for another issuer", cts.Token);
|
|
goto next;
|
|
}
|
|
|
|
bool success = false;
|
|
switch (command)
|
|
{
|
|
case "configure-boltcard":
|
|
await vaultClient.Show(VaultMessageType.Processing, "Configuring Boltcard...", cts.Token);
|
|
if (cardOrigin is CardOrigin.Blank || cardOrigin is CardOrigin.ThisIssuerReset)
|
|
{
|
|
await ntag.AuthenticateEV2First(0, AESKey.Default, cts.Token);
|
|
var uid = await ntag.GetCardUID();
|
|
try
|
|
{
|
|
var version = await _dbContextFactory.LinkBoltcardToPullPayment(pullPaymentId, issuerKey, uid);
|
|
var cardKey = issuerKey.CreatePullPaymentCardKey(uid, version, pullPaymentId);
|
|
await ntag.SetupBoltcard(boltcardUrl, BoltcardKeys.Default, cardKey.DeriveBoltcardKeys(issuerKey));
|
|
}
|
|
catch
|
|
{
|
|
await _dbContextFactory.SetBoltcardResetState(issuerKey, uid);
|
|
throw;
|
|
}
|
|
await vaultClient.Show(VaultMessageType.Ok, "The card is now configured", cts.Token);
|
|
}
|
|
else if (cardOrigin is CardOrigin.ThisIssuer)
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Ok, "This card is already properly configured", cts.Token);
|
|
}
|
|
success = true;
|
|
break;
|
|
case "reset-boltcard":
|
|
await vaultClient.Show(VaultMessageType.Processing, "Resetting Boltcard...", cts.Token);
|
|
if (cardOrigin is CardOrigin.Blank)
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Ok, "This card is already in a factory state", cts.Token);
|
|
}
|
|
else if (cardOrigin is CardOrigin.ThisIssuer thisIssuer)
|
|
{
|
|
var cardKey = issuerKey.CreatePullPaymentCardKey(thisIssuer.Registration.UId, thisIssuer.Registration.Version, pullPaymentId);
|
|
await ntag.ResetCard(issuerKey, cardKey);
|
|
await _dbContextFactory.SetBoltcardResetState(issuerKey, thisIssuer.Registration.UId);
|
|
await vaultClient.Show(VaultMessageType.Ok, "Card reset succeed", cts.Token);
|
|
}
|
|
success = true;
|
|
break;
|
|
}
|
|
if (success)
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Processing, "Please remove the NFC from the card reader", cts.Token);
|
|
await transport.WaitForRemoved(cts.Token);
|
|
await vaultClient.Show(VaultMessageType.Ok, "Thank you!", cts.Token);
|
|
await vaultClient.SendSimpleMessage("done", cts.Token);
|
|
}
|
|
}
|
|
catch (Exception) when (websocket.State != WebSocketState.Open || cts.IsCancellationRequested)
|
|
{
|
|
await WebsocketHelper.CloseSocket(websocket);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try
|
|
{
|
|
await vaultClient.Show(VaultMessageType.Error, "Unexpected error: " + ex.Message, ex.ToString(), cts.Token);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
return new EmptyResult();
|
|
}
|
|
|
|
private async Task<CardOrigin> GetCardOrigin(string pullPaymentId, Ntag424 ntag, IssuerKey issuerKey, CancellationToken cancellationToken)
|
|
{
|
|
CardOrigin cardOrigin;
|
|
Uri uri = await ntag.TryReadNDefURI(cancellationToken);
|
|
|
|
if (uri is null)
|
|
{
|
|
cardOrigin = new CardOrigin.Blank();
|
|
}
|
|
else
|
|
{
|
|
var piccData = issuerKey.TryDecrypt(uri);
|
|
if (piccData is null)
|
|
{
|
|
cardOrigin = new CardOrigin.OtherIssuer();
|
|
}
|
|
else
|
|
{
|
|
var res = await _dbContextFactory.GetBoltcardRegistration(issuerKey, piccData.Uid);
|
|
if (res != null && res.PullPaymentId is null)
|
|
cardOrigin = new CardOrigin.ThisIssuerReset(res);
|
|
else if (res?.PullPaymentId != pullPaymentId)
|
|
cardOrigin = new CardOrigin.OtherIssuer();
|
|
else
|
|
cardOrigin = new CardOrigin.ThisIssuerConfigured(res.PullPaymentId, res);
|
|
}
|
|
}
|
|
|
|
return cardOrigin;
|
|
}
|
|
}
|
|
}
|