Add separate recovery seed backup view

fix
This commit is contained in:
Dennis Reimann 2020-07-15 19:51:01 +02:00
parent f6549cda33
commit cfef1f3432
No known key found for this signature in database
GPG key ID: 5009E1797F03F8D0
8 changed files with 226 additions and 3 deletions

View file

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -191,6 +192,13 @@ namespace BTCPayServer.Controllers
return View(vm); return View(vm);
} }
[Route("recovery-seed-backup")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult RecoverySeedBackup(RecoverySeedBackupViewModel vm)
{
return View("RecoverySeedBackup", vm);
}
public IActionResult Error() public IActionResult Error()
{ {
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });

View file

@ -305,8 +305,17 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = $"Your wallet has been generated. Please store your seed securely! <br/><code class=\"alert-link\">{response.Mnemonic}</code>" Html = $"Your wallet has been generated. Please store your seed securely!"
}); });
var vm = new RecoverySeedBackupViewModel()
{
CryptoCode = cryptoCode,
Mnemonic = response.Mnemonic,
Passphrase = response.Passphrase,
IsStored = request.SavePrivateKeys,
ReturnUrl = Url.Action(nameof(UpdateStore), new { storeId })
};
return this.RedirectToRecoverySeedBackup(vm);
} }
else else
{ {

View file

@ -9,6 +9,7 @@ using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.ModelBinders; using BTCPayServer.ModelBinders;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Models.WalletViewModels; using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Security; using BTCPayServer.Security;
@ -1147,8 +1148,16 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = $"Please store your seed securely! <br/><code class=\"alert-link\">{seed}</code>" Html = $"Please store your seed securely!"
}); });
var recoveryVm = new RecoverySeedBackupViewModel()
{
CryptoCode = walletId.CryptoCode,
Mnemonic = seed,
IsStored = true,
ReturnUrl = Url.Action(nameof(WalletSettings), new { walletId })
};
return this.RedirectToRecoverySeedBackup(recoveryVm);
} }
return RedirectToAction(nameof(WalletSettings)); return RedirectToAction(nameof(WalletSettings));

View file

@ -12,12 +12,14 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -429,5 +431,23 @@ namespace BTCPayServer
{ {
ctx.Items["BTCPAY.STORESDATA"] = storeData; ctx.Items["BTCPAY.STORESDATA"] = storeData;
} }
public static IActionResult RedirectToRecoverySeedBackup(this Controller controller, RecoverySeedBackupViewModel vm)
{
var redirectVm = new PostRedirectViewModel()
{
AspController = "Home",
AspAction = "RecoverySeedBackup",
Parameters =
{
new KeyValuePair<string, string>("cryptoCode", vm.CryptoCode),
new KeyValuePair<string, string>("mnemonic", vm.Mnemonic),
new KeyValuePair<string, string>("passphrase", vm.Passphrase),
new KeyValuePair<string, string>("isStored", vm.IsStored ? "true" : "false"),
new KeyValuePair<string, string>("returnUrl", vm.ReturnUrl)
}
};
return controller.View("PostRedirect", redirectVm);
}
} }
} }

View file

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using NBitcoin;
namespace BTCPayServer.Models.StoreViewModels
{
public class RecoverySeedBackupViewModel
{
public string CryptoCode { get; set; }
public string Mnemonic { get; set; }
public string Passphrase { get; set; }
public bool IsStored { get; set; }
public string ReturnUrl { get; set; }
public string[] Words
{
get => Mnemonic.Split(" ");
}
}
}

View file

@ -0,0 +1,59 @@
@model RecoverySeedBackupViewModel
@{
Layout = "_LayoutSimple";
ViewData["Title"] = "Save your recovery phrase";
}
<div class="row justify-content-md-center">
<div class="col-md-9 col-lg-8">
<partial name="_StatusMessage" />
<h1 class="text-center text-primary mb-5">@ViewData["Title"]</h1>
<div class="row">
<div class="col-12 col-sm-2 d-flex align-items-center justify-content-center">
<span class="fa fa-warning align-self-center p-3" style="font-size:3.5em;"></span>
</div>
<div class="col-12 col-sm-10 lead">
<p>
The words below are called your recovery phrase.
Please save these words securely.
They will allow you to recover your wallet.
</p>
@if (!Model.IsStored)
{
<p class="mt-3 mb-3">The recovery phrase will only be shown before being wiped from the server.</p>
}
<p class="mt-3 mb-0">
Do not photograph or store this in a non air-gapped digital format.
Anyone with access to your recovery phrase can steal your funds.
</p>
@if (!string.IsNullOrEmpty(Model.Passphrase))
{
<p class="mt-3 mb-0">Please make also sure to store your passphrase.</p>
}
</div>
</div>
<ol class="my-5 d-flex flex-wrap justify-content-center align-items-center p-0" style="max-width:800px">
@foreach (var word in Model.Words)
{
<li class="ml-4 p-3 text-secondary" style="flex: 0 1 11em">
<span class="text-dark h5">@word</span>
</li>
}
</ol>
<style>
form#recoveryConfirmation button { position: absolute; bottom:0; left:50%; width:200px; margin-left:-100px; }
form#recoveryConfirmation button:not([disabled]) { display: none; }
form#recoveryConfirmation input:checked ~ button[disabled] { display: none; }
form#recoveryConfirmation input:checked + button:not([disabled]) { display: inline-block; }
</style>
<form id="recoveryConfirmation" action="@Model.ReturnUrl" class="d-flex align-items-center justify-content-center" style="padding-bottom: 80px">
<label class="form-check-label lead order-2" for="confirm">I have saved my recovery phrase in a secure location</label>
<input type="checkbox" class="mr-3 order-1" id="confirm">
<button type="submit" class="btn btn-primary btn-lg px-5 order-3">Done</button>
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" disabled>Done</button>
</form>
</div>
</div>

View file

@ -1,12 +1,25 @@
@model PostRedirectViewModel @model PostRedirectViewModel
@{ @{
Layout = null; Layout = null;
var routeData = Context.GetRouteData();
var routeParams = new Dictionary<string, string>();
if (routeData != null)
{
routeParams["walletId"] = routeData.Values["walletId"]?.ToString();
}
} }
<html> <html>
<head></head> <head></head>
<body> <body>
<form method="post" id="postform" asp-action="@Model.AspAction" asp-controller="@Model.AspController" asp-route-walletId="@this.Context.GetRouteValue("walletId").ToString()"> <form
method="post"
id="postform"
asp-action="@Model.AspAction"
asp-controller="@Model.AspController"
asp-all-route-data="@routeParams"
>
@foreach(var o in Model.Parameters) { @foreach(var o in Model.Parameters) {
<input type="hidden" name="@o.Key" value="@o.Value" /> <input type="hidden" name="@o.Key" value="@o.Value" />
} }

View file

@ -0,0 +1,84 @@
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<partial name="Header" />
<link href="~/main/fonts/Montserrat.css" rel="stylesheet" asp-append-version="true">
<style>
.content-wrapper {
padding: 50px 0;
}
.col-head {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
text-align: center;
padding: 0 2rem;
}
.head-logo {
height: 70px;
margin-bottom: 1rem;
}
.lead-title {
font-family: Montserrat;
font-style: normal;
font-weight: bold;
font-size: 24px;
line-height: 1.2;
max-width: 17em;
letter-spacing: .1em;
}
.lead-login {
font-family: Montserrat;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 1.8;
letter-spacing: 0.1em;
}
@@media screen and (min-width: 768px) {
.content-wrapper {
padding: 75px 0;
}
.col-head {
text-align: left;
flex-direction: row;
}
.lead-title {
font-size: 34px;
}
.lead-login {
font-size: 18px;
}
.head-logo {
height: 80px;
margin: 0 30px 0 0;
}
}
</style>
</head>
<body>
<section class="content-wrapper">
<!-- Dummy navbar-brand, hackish way to keep test AssertNoError passing -->
<div class="navbar-brand d-none"></div>
<div class="container">
@RenderBody()
</div>
</section>
@await Html.PartialAsync("_ValidationScriptsPartial")
</body>
</html>