Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
1814cb2d6e | |||
94a6f20a05 | |||
22e700a53e | |||
cd78e559cf | |||
f0257fb8f7 | |||
34cdbf73f0 | |||
b291a6d25a | |||
fa7e974e73 | |||
976d9d0cda | |||
6ea2d9175d | |||
10ceddc709 | |||
5dd57c8064 | |||
a256dd3277 | |||
5ee9a92f1e | |||
65c7c85c14 |
@ -101,6 +101,7 @@ namespace BTCPayServer.Tests
|
||||
/// <returns></returns>
|
||||
private async Task PrepareLightningAsync(ILightningInvoiceClient client)
|
||||
{
|
||||
bool awaitingLocking = false;
|
||||
while (true)
|
||||
{
|
||||
var merchantInfo = await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
|
||||
@ -126,8 +127,9 @@ namespace BTCPayServer.Tests
|
||||
await CustomerLightningD.FundChannelAsync(merchantNodeInfo, Money.Satoshis(16777215));
|
||||
break;
|
||||
case "CHANNELD_AWAITING_LOCKIN":
|
||||
ExplorerNode.Generate(1);
|
||||
ExplorerNode.Generate(awaitingLocking ? 1 : 10);
|
||||
await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
|
||||
awaitingLocking = true;
|
||||
break;
|
||||
case "CHANNELD_NORMAL":
|
||||
return;
|
||||
|
@ -1662,7 +1662,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
private async Task EventuallyAsync(Func<Task> act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(20000);
|
||||
CancellationTokenSource cts = new CancellationTokenSource(20000000);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.2.41</Version>
|
||||
<Version>1.0.2.45</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@ -40,10 +40,10 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.20" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.27" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.29" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.2.12" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.2.13" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.14" />
|
||||
@ -111,6 +111,7 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Build\" />
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -175,6 +175,7 @@ namespace BTCPayServer.Controllers
|
||||
[Route("invoice")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
[ReferrerPolicyAttribute("origin")]
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string paymentMethodId = null)
|
||||
{
|
||||
//Keep compatibility with Bitpay
|
||||
@ -186,6 +187,20 @@ namespace BTCPayServer.Controllers
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
_CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue
|
||||
if(!string.IsNullOrEmpty(model.CustomCSSLink) &&
|
||||
Uri.TryCreate(model.CustomCSSLink, UriKind.Absolute, out var uri))
|
||||
{
|
||||
_CSP.Clear();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(model.CustomLogoLink) &&
|
||||
Uri.TryCreate(model.CustomLogoLink, UriKind.Absolute, out uri))
|
||||
{
|
||||
_CSP.Clear();
|
||||
}
|
||||
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
|
@ -41,12 +41,14 @@ using NBXplorer;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController : Controller
|
||||
{
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
ContentSecurityPolicies _CSP;
|
||||
BTCPayRateProviderFactory _RateProvider;
|
||||
StoreRepository _StoreRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
@ -64,6 +66,7 @@ namespace BTCPayServer.Controllers
|
||||
StoreRepository storeRepository,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
ContentSecurityPolicies csp,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
_ServiceProvider = serviceProvider;
|
||||
@ -75,6 +78,7 @@ namespace BTCPayServer.Controllers
|
||||
_EventAggregator = eventAggregator;
|
||||
_NetworkProvider = networkProvider;
|
||||
_WalletProvider = walletProvider;
|
||||
_CSP = csp;
|
||||
}
|
||||
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var internalDomain = internalLightning.BaseUri?.DnsSafeHost;
|
||||
var internalDomain = internalLightning?.BaseUri?.DnsSafeHost;
|
||||
|
||||
bool isInternalNode = connectionString.ConnectionType == LightningConnectionType.CLightning ||
|
||||
connectionString.BaseUri.DnsSafeHost == internalDomain ||
|
||||
|
@ -98,6 +98,26 @@ namespace BTCPayServer
|
||||
return str + "/";
|
||||
}
|
||||
|
||||
public static void SetHeaderOnStarting(this HttpResponse resp, string name, string value)
|
||||
{
|
||||
if (resp.HasStarted)
|
||||
return;
|
||||
resp.OnStarting(() =>
|
||||
{
|
||||
SetHeader(resp, name, value);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetHeader(this HttpResponse resp, string name, string value)
|
||||
{
|
||||
var existing = resp.Headers[name].FirstOrDefault();
|
||||
if (existing != null && value == null)
|
||||
resp.Headers.Remove(name);
|
||||
else
|
||||
resp.Headers[name] = value;
|
||||
}
|
||||
|
||||
public static string GetAbsoluteRoot(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
@ -165,6 +185,14 @@ namespace BTCPayServer
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return await doing;
|
||||
}
|
||||
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
|
||||
{
|
||||
var waiting = Task.Delay(-1, cancellationToken);
|
||||
var doing = task;
|
||||
await Task.WhenAny(waiting, doing);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await doing;
|
||||
}
|
||||
|
||||
public static (string Signature, String Id, String Authorization) GetBitpayAuth(this HttpContext ctx)
|
||||
{
|
||||
|
106
BTCPayServer/Filters/ContentSecurityPolicyAttribute.cs
Normal file
106
BTCPayServer/Filters/ContentSecurityPolicyAttribute.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public interface IContentSecurityPolicy : IFilterMetadata { }
|
||||
public class ContentSecurityPolicyAttribute : Attribute, IActionFilter, IContentSecurityPolicy
|
||||
{
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool AutoSelf { get; set; } = true;
|
||||
public bool UnsafeInline { get; set; } = true;
|
||||
public bool FixWebsocket { get; set; } = true;
|
||||
public string FontSrc { get; set; } = null;
|
||||
public string ImgSrc { get; set; } = null;
|
||||
public string DefaultSrc { get; set; }
|
||||
public string StyleSrc { get; set; }
|
||||
public string ScriptSrc { get; set; }
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
if (context.IsEffectivePolicy<IContentSecurityPolicy>(this))
|
||||
{
|
||||
var policies = context.HttpContext.RequestServices.GetService(typeof(ContentSecurityPolicies)) as ContentSecurityPolicies;
|
||||
if (policies == null)
|
||||
return;
|
||||
if (DefaultSrc != null)
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy("default-src", DefaultSrc));
|
||||
}
|
||||
if (UnsafeInline)
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy("script-src", "'unsafe-inline'"));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(FontSrc))
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy("font-src", FontSrc));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ImgSrc))
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy("img-src", ImgSrc));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(StyleSrc))
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy("style-src", StyleSrc));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ScriptSrc))
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy("script-src", ScriptSrc));
|
||||
}
|
||||
|
||||
if (FixWebsocket && AutoSelf) // Self does not match wss:// and ws:// :(
|
||||
{
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
var url = string.Concat(
|
||||
request.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ? "ws" : "wss",
|
||||
"://",
|
||||
request.Host.ToUriComponent(),
|
||||
request.PathBase.ToUriComponent());
|
||||
policies.Add(new ConsentSecurityPolicy("connect-src", url));
|
||||
}
|
||||
|
||||
context.HttpContext.Response.OnStarting(() =>
|
||||
{
|
||||
if (!policies.HasRules)
|
||||
return Task.CompletedTask;
|
||||
if (AutoSelf)
|
||||
{
|
||||
bool hasSelf = false;
|
||||
foreach (var group in policies.Rules.GroupBy(p => p.Name))
|
||||
{
|
||||
hasSelf = group.Any(g => g.Value.Contains("'self'", StringComparison.OrdinalIgnoreCase));
|
||||
if (!hasSelf && !group.Any(g => g.Value.Contains("'none'", StringComparison.OrdinalIgnoreCase) ||
|
||||
g.Value.Contains("*", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy(group.Key, "'self'"));
|
||||
hasSelf = true;
|
||||
}
|
||||
if (hasSelf)
|
||||
{
|
||||
foreach (var authorized in policies.Authorized)
|
||||
{
|
||||
policies.Add(new ConsentSecurityPolicy(group.Key, authorized));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
context.HttpContext.Response.SetHeader("Content-Security-Policy", policies.ToString());
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
BTCPayServer/Filters/ReferrerPolicyAttribute.cs
Normal file
30
BTCPayServer/Filters/ReferrerPolicyAttribute.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public interface IReferrerPolicy : IFilterMetadata { }
|
||||
public class ReferrerPolicyAttribute : Attribute, IActionFilter
|
||||
{
|
||||
public ReferrerPolicyAttribute(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public string Value { get; set; }
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
if (context.IsEffectivePolicy<ReferrerPolicyAttribute>(this))
|
||||
{
|
||||
context.HttpContext.Response.SetHeaderOnStarting("Referrer-Policy", Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
BTCPayServer/Filters/XContentTypeOptionsAttribute.cs
Normal file
25
BTCPayServer/Filters/XContentTypeOptionsAttribute.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public class XContentTypeOptionsAttribute : Attribute, IActionFilter
|
||||
{
|
||||
public XContentTypeOptionsAttribute(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public string Value { get; set; }
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
context.HttpContext.Response.SetHeaderOnStarting("X-Content-Type-Options", Value);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,11 +23,7 @@ namespace BTCPayServer.Filters
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var existing = context.HttpContext.Response.Headers["x-frame-options"].FirstOrDefault();
|
||||
if (existing != null && Value == null)
|
||||
context.HttpContext.Response.Headers.Remove("x-frame-options");
|
||||
else
|
||||
context.HttpContext.Response.Headers["x-frame-options"] = Value;
|
||||
context.HttpContext.Response.SetHeaderOnStarting("X-Frame-Options", Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
BTCPayServer/Filters/XXSSProtectionAttribute.cs
Normal file
23
BTCPayServer/Filters/XXSSProtectionAttribute.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public class XXSSProtectionAttribute : Attribute, IActionFilter
|
||||
{
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
context.HttpContext.Response.SetHeaderOnStarting("X-XSS-Protection", "1; mode=block");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ using NBXplorer.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using BTCPayServer.Security;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
@ -50,6 +52,33 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentSecurityPolicyCssThemeManager : Attribute, IActionFilter, IOrderedFilter
|
||||
{
|
||||
public int Order => 1001;
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var manager = context.HttpContext.RequestServices.GetService(typeof(CssThemeManager)) as CssThemeManager;
|
||||
var policies = context.HttpContext.RequestServices.GetService(typeof(ContentSecurityPolicies)) as ContentSecurityPolicies;
|
||||
if (manager != null && policies != null)
|
||||
{
|
||||
if(manager.CreativeStartUri != null && Uri.TryCreate(manager.CreativeStartUri, UriKind.Absolute, out var uri))
|
||||
{
|
||||
policies.Clear();
|
||||
}
|
||||
if (manager.BootstrapUri != null && Uri.TryCreate(manager.BootstrapUri, UriKind.Absolute, out uri))
|
||||
{
|
||||
policies.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CssThemeManagerHostedService : BaseAsyncService
|
||||
{
|
||||
private SettingsRepository _SettingsRepository;
|
||||
|
@ -104,6 +104,7 @@ namespace BTCPayServer.Hosting
|
||||
});
|
||||
|
||||
services.AddSingleton<CssThemeManager>();
|
||||
services.Configure<MvcOptions>((o) => { o.Filters.Add(new ContentSecurityPolicyCssThemeManager()); });
|
||||
services.AddSingleton<IHostedService, CssThemeManagerHostedService>();
|
||||
|
||||
services.AddSingleton<Payments.IPaymentMethodHandler<DerivationStrategy>, Payments.Bitcoin.BitcoinLikePaymentHandler>();
|
||||
|
@ -39,6 +39,7 @@ using Microsoft.AspNetCore.Mvc.Cors.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using System.Net;
|
||||
using Meziantou.AspNetCore.BundleTagHelpers;
|
||||
using BTCPayServer.Security;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -79,8 +80,19 @@ namespace BTCPayServer.Hosting
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
|
||||
o.Filters.Add(new XXSSProtectionAttribute());
|
||||
o.Filters.Add(new ReferrerPolicyAttribute("same-origin"));
|
||||
//o.Filters.Add(new ContentSecurityPolicyAttribute()
|
||||
//{
|
||||
// FontSrc = "'self' https://fonts.gstatic.com/",
|
||||
// ImgSrc = "'self' data:",
|
||||
// DefaultSrc = "'none'",
|
||||
// StyleSrc = "'self' 'unsafe-inline'",
|
||||
// ScriptSrc = "'self' 'unsafe-inline'"
|
||||
//});
|
||||
});
|
||||
|
||||
services.TryAddScoped<ContentSecurityPolicies>();
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = false;
|
||||
|
@ -123,7 +123,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
|
||||
using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
||||
{
|
||||
await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)), cancellation);
|
||||
await tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)).WithCancellation(cancellation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -131,15 +131,5 @@ namespace BTCPayServer.Payments.Lightning
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {nodeInfo.Host}:{nodeInfo.Port} ({ex.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
static Task WithTimeout(Task task, CancellationToken token)
|
||||
{
|
||||
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
|
||||
var registration = token.Register(() => { try { tcs.TrySetResult(true); } catch { } });
|
||||
#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler
|
||||
var timeoutTask = tcs.Task;
|
||||
#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler
|
||||
return Task.WhenAny(task, timeoutTask).Unwrap().ContinueWith(t => registration.Dispose(), TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,8 +146,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||
try
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}");
|
||||
var charge = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||
var session = await charge.Listen(_Cts.Token);
|
||||
var lightningClient = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||
var session = await lightningClient.Listen(_Cts.Token);
|
||||
while (true)
|
||||
{
|
||||
var notification = await session.WaitInvoice(_Cts.Token);
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
@ -27,59 +28,72 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
||||
CancellationTokenSource _Cts = new CancellationTokenSource();
|
||||
ManualResetEventSlim _Stopped = new ManualResetEventSlim(false);
|
||||
|
||||
|
||||
HttpClient _Client;
|
||||
HttpResponseMessage _Response;
|
||||
Stream _Body;
|
||||
StreamReader _Reader;
|
||||
|
||||
public LndInvoiceClientSession(LndSwaggerClient parent)
|
||||
{
|
||||
_Parent = parent;
|
||||
}
|
||||
|
||||
public async void StartListening()
|
||||
public async Task StartListening()
|
||||
{
|
||||
_Client = _Parent.CreateHttpClient();
|
||||
_Client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, _Parent.BaseUrl.WithTrailingSlash() + "v1/invoices/subscribe");
|
||||
_Response = await _Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _Cts.Token);
|
||||
_Body = await _Response.Content.ReadAsStreamAsync();
|
||||
_Reader = new StreamReader(_Body);
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
ListenLoop();
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
|
||||
private async Task ListenLoop()
|
||||
{
|
||||
var urlBuilder = new StringBuilder();
|
||||
urlBuilder.Append(_Parent.BaseUrl).Append("/v1/invoices/subscribe");
|
||||
try
|
||||
{
|
||||
using (var client = _Parent.CreateHttpClient())
|
||||
while (!_Cts.IsCancellationRequested)
|
||||
{
|
||||
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.ToString());
|
||||
|
||||
using (var response = await client.SendAsync(
|
||||
request, HttpCompletionOption.ResponseHeadersRead, _Cts.Token))
|
||||
string line = await _Reader.ReadLineAsync().WithCancellation(_Cts.Token);
|
||||
if (line != null && line.StartsWith("{\"result\":", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
using (var body = await response.Content.ReadAsStreamAsync())
|
||||
using (var reader = new StreamReader(body))
|
||||
{
|
||||
while (!_Cts.IsCancellationRequested)
|
||||
{
|
||||
string line = await reader.ReadLineAsync().WithCancellation(_Cts.Token);
|
||||
if (line != null && line.StartsWith("{\"result\":", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var invoiceString = JObject.Parse(line)["result"].ToString();
|
||||
LnrpcInvoice parsedInvoice = _Parent.Deserialize<LnrpcInvoice>(invoiceString);
|
||||
await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice));
|
||||
}
|
||||
}
|
||||
}
|
||||
var invoiceString = JObject.Parse(line)["result"].ToString();
|
||||
LnrpcInvoice parsedInvoice = _Parent.Deserialize<LnrpcInvoice>(invoiceString);
|
||||
await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice), _Cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch when (_Cts.IsCancellationRequested)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Ex = ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_Stopped.Set();
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Exception _Ex;
|
||||
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _Invoices.Reader.ReadAsync(cancellation);
|
||||
}
|
||||
catch when (!cancellation.IsCancellationRequested && _Ex != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(_Ex).Throw();
|
||||
throw;
|
||||
}
|
||||
catch (ChannelClosedException)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
@ -88,6 +102,18 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_Cts.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
_Reader?.Dispose();
|
||||
_Reader = null;
|
||||
_Body?.Dispose();
|
||||
_Body = null;
|
||||
_Response?.Dispose();
|
||||
_Response = null;
|
||||
_Client?.Dispose();
|
||||
_Client = null;
|
||||
|
||||
_Cts.Cancel();
|
||||
_Stopped.Wait();
|
||||
_Invoices.Writer.Complete();
|
||||
@ -157,11 +183,11 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
||||
return ConvertLndInvoice(resp);
|
||||
}
|
||||
|
||||
public Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken))
|
||||
public async Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
var session = new LndInvoiceClientSession(this._rpcClient);
|
||||
session.StartListening();
|
||||
return Task.FromResult<ILightningListenInvoiceSession>(session);
|
||||
await session.StartListening();
|
||||
return session;
|
||||
}
|
||||
|
||||
internal static LightningInvoice ConvertLndInvoice(LnrpcInvoice resp)
|
||||
|
120
BTCPayServer/Security/ContentSecurityPolicies.cs
Normal file
120
BTCPayServer/Security/ContentSecurityPolicies.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class ConsentSecurityPolicy
|
||||
{
|
||||
public ConsentSecurityPolicy(string name, string value)
|
||||
{
|
||||
_Value = value;
|
||||
_Name = name;
|
||||
}
|
||||
|
||||
|
||||
private readonly string _Name;
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Name;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string _Value;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
ConsentSecurityPolicy item = obj as ConsentSecurityPolicy;
|
||||
if (item == null)
|
||||
return false;
|
||||
return GetHashCode().Equals(item.GetHashCode());
|
||||
}
|
||||
public static bool operator ==(ConsentSecurityPolicy a, ConsentSecurityPolicy b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.GetHashCode() == b.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator !=(ConsentSecurityPolicy a, ConsentSecurityPolicy b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Name, Value);
|
||||
}
|
||||
}
|
||||
public class ContentSecurityPolicies
|
||||
{
|
||||
public ContentSecurityPolicies()
|
||||
{
|
||||
|
||||
}
|
||||
HashSet<ConsentSecurityPolicy> _Policies = new HashSet<ConsentSecurityPolicy>();
|
||||
public void Add(ConsentSecurityPolicy policy)
|
||||
{
|
||||
if (_Policies.Any(p => p.Name == policy.Name && p.Value == policy.Name))
|
||||
return;
|
||||
_Policies.Add(policy);
|
||||
}
|
||||
|
||||
public IEnumerable<ConsentSecurityPolicy> Rules => _Policies;
|
||||
public bool HasRules => _Policies.Count != 0;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder value = new StringBuilder();
|
||||
bool firstGroup = true;
|
||||
foreach(var group in Rules.GroupBy(r => r.Name))
|
||||
{
|
||||
if (!firstGroup)
|
||||
{
|
||||
value.Append(';');
|
||||
}
|
||||
List<string> values = new List<string>();
|
||||
values.Add(group.Key);
|
||||
foreach (var v in group)
|
||||
{
|
||||
values.Add(v.Value);
|
||||
}
|
||||
foreach(var i in authorized)
|
||||
{
|
||||
values.Add(i);
|
||||
}
|
||||
value.Append(String.Join(" ", values.OfType<object>().ToArray()));
|
||||
firstGroup = false;
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
authorized.Clear();
|
||||
_Policies.Clear();
|
||||
}
|
||||
|
||||
HashSet<string> authorized = new HashSet<string>();
|
||||
internal void AddAllAuthorized(string v)
|
||||
{
|
||||
authorized.Add(v);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Authorized => authorized;
|
||||
}
|
||||
}
|
@ -74,8 +74,8 @@
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
||||
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,14 @@
|
||||
<div class="container text-center">
|
||||
<h2>Video tutorials</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-6 text-center">
|
||||
<iframe width="400" height="225" src="https://www.youtube.com/embed/xh3Eac66qc4" frameborder="0" allowfullscreen></iframe>
|
||||
<div class="col-md-4 text-center">
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<a href="https://www.youtube.com/channel/UCpG9WL6TJuoNfFVkaDMp9ug" target="_blank">
|
||||
<img src="~/img/youtube.png" height="225" width="400" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 text-center">
|
||||
<iframe width="400" height="225" src="https://www.youtube.com/embed/Xo_vApXTZBU" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,7 @@ const locales_nl = {
|
||||
"Contact_Body": "Bedankt om je email adres in te vullen voor een mogelijke opvolging. We contacteren je indien er een probleem optreedt.",
|
||||
"Your email": "Je email adres",
|
||||
"Continue": "Verdergaan",
|
||||
"Please enter a valid email address": "Bedankt om een geldig email adres in te vullen",
|
||||
"Please enter a valid email address": "Vul een geldig email adres in",
|
||||
"Order Amount": "Bedrag van je bestelling",
|
||||
"Network Cost": "Netwerk kosten",
|
||||
"Already Paid": "Reeds betaald",
|
||||
@ -25,25 +25,25 @@ const locales_nl = {
|
||||
"Address": "Adres",
|
||||
"Copied": "Gekopieerd",
|
||||
// Conversion tab
|
||||
"ConversionTab_BodyTop": "Je kan alternatieve cryptocurrencies gebruiken die niet ondersteund zijn door de verkoper, om {{btcDue}} {{cryptoCode}} te betalen.",
|
||||
"ConversionTab_BodyTop": "Je kan altcoins gebruiken die niet ondersteund zijn door de verkoper, om {{btcDue}} {{cryptoCode}} te betalen.",
|
||||
"ConversionTab_BodyDesc": "Deze dienst wordt door een externe partij geleverd. Bijgevolg, hebben we geen zicht over jouw fondsen. De factuur wordt pas als betaald beschouwd, wanneer de fondsen door de blockchain aanvaard zijn {{ cryptoCode }}.",
|
||||
"Shapeshift_Button_Text": "Betalen met een alternatieve cryptocurrency",
|
||||
"Shapeshift_Button_Text": "Betalen met altcoins",
|
||||
"ConversionTab_Lightning": "Geen leverancier beschikbaar voor de betalingen op het Lightning Network",
|
||||
// Invoice expired
|
||||
"Invoice expiring soon...": "De factuur zal weldra vervallen...",
|
||||
"Invoice expiring soon...": "De factuur verloopt binnenkort...",
|
||||
"Invoice expired": "Vervallen factuur",
|
||||
"What happened?": "Wat gebeurde er?",
|
||||
"InvoiceExpired_Body_1": "De factuur is vervallen. Een factuur is geldig voor {{maxTimeMinutes}} minuten. \
|
||||
Je kan terug komen naar {{storeName}} indien je nog eens je betaling wilt proberen uit te voeren.",
|
||||
"InvoiceExpired_Body_2": "Indien je een betaling uitvoerde, werd deze nog niet aanvaard door de blockchain. We hebben je fondsen nog niet ontvangen.",
|
||||
"InvoiceExpired_Body_3": "Indien je transactie niet door de blockchain werd aanvaard, zullen je fondsen terug in wallet verschijnen. Volgens de wallet, kan dit 48 to 72 uren duren.",
|
||||
"InvoiceExpired_Body_1": "De factuur is vervallen. Een factuur is alleen geldig voor {{maxTimeMinutes}} minuten. \
|
||||
Je kan terug komen naar {{storeName}} als je de betaling opnieuw wilt proberen",
|
||||
"InvoiceExpired_Body_2": "Als je een betaling uitvoerde, dan werd dit nog niet bevestigd door het netwerk. We hebben je fondsen nog niet ontvangen.",
|
||||
"InvoiceExpired_Body_3": "Als de transactie niet geaccepteerd werd door het netwerk, zullen je fondsen terug in wallet verschijnen. Afhankelijk van je wallet, kan dit 48 to 72 uren duren.",
|
||||
"Invoice ID": "Factuurnummer",
|
||||
"Order ID": "Bestllingsnummer",
|
||||
"Return to StoreName": "Terug naar {{storeName}}",
|
||||
// Invoice paid
|
||||
"This invoice has been paid": "Deze factuur werd betaald",
|
||||
"This invoice has been paid": "Deze factuur is betaald",
|
||||
// Invoice archived
|
||||
"This invoice has been archived": "Deze factuur werd geactiveerd",
|
||||
"This invoice has been archived": "Deze factuur is gearchiveerd",
|
||||
"Archived_Body": "Bedankt om de winkel te contacteren voor bijstand met of informatie over deze bestelling",
|
||||
// Lightning
|
||||
"BOLT 11 Invoice": "BOLT 11 Factuur",
|
||||
|
BIN
BTCPayServer/wwwroot/img/youtube.png
Normal file
BIN
BTCPayServer/wwwroot/img/youtube.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
@ -2902,7 +2902,7 @@ tbody.collapse.show {
|
||||
border-color: rgba(0, 0, 0, 0.1); }
|
||||
|
||||
.navbar-light .navbar-toggler-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
|
||||
|
||||
.navbar-light .navbar-text {
|
||||
color: rgba(0, 0, 0, 0.5); }
|
||||
|
1
BTCPayServer/wwwroot/vendor/highlightjs/default.min.css
vendored
Normal file
1
BTCPayServer/wwwroot/vendor/highlightjs/default.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
|
3
BTCPayServer/wwwroot/vendor/highlightjs/highlight.min.js
vendored
Normal file
3
BTCPayServer/wwwroot/vendor/highlightjs/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user