mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-19 15:04:19 +01:00
Plugins: Add a way for LightningClient to validate the connection string asynchronously
This commit is contained in:
@@ -217,17 +217,25 @@ namespace BTCPayServer
|
|||||||
return endpoint != null;
|
return endpoint != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri GetServerUri(this ILightningClient client)
|
[Obsolete("Use GetServerUri(this ILightningClient client, string connectionString) instead")]
|
||||||
|
public static Uri GetServerUri(this ILightningClient client) => GetServerUri(client, client.ToString());
|
||||||
|
public static Uri GetServerUri(this ILightningClient client, string connectionString)
|
||||||
{
|
{
|
||||||
var kv = LightningConnectionStringHelper.ExtractValues(client.ToString(), out _);
|
if (client is IExtendedLightningClient { ServerUri: { } uri })
|
||||||
|
return uri;
|
||||||
|
var kv = client.ExtractValues(connectionString);
|
||||||
return !kv.TryGetValue("server", out var server) ? null : new Uri(server, UriKind.Absolute);
|
return !kv.TryGetValue("server", out var server) ? null : new Uri(server, UriKind.Absolute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDisplayName(this ILightningClient client)
|
[Obsolete("Use GetDisplayName(this ILightningClient client, string connectionString) instead")]
|
||||||
|
public static string GetDisplayName(this ILightningClient client) => GetDisplayName(client, client.ToString());
|
||||||
|
public static string GetDisplayName(this ILightningClient client, string connectionString)
|
||||||
{
|
{
|
||||||
LightningConnectionStringHelper.ExtractValues(client.ToString(), out var type);
|
if (client is IExtendedLightningClient { DisplayName: { } displayName })
|
||||||
|
return displayName;
|
||||||
|
var kv = client.ExtractValues(connectionString);
|
||||||
|
if (!kv.TryGetValue("type", out var type))
|
||||||
|
return "???";
|
||||||
var lncType = typeof(LightningConnectionType);
|
var lncType = typeof(LightningConnectionType);
|
||||||
var fields = lncType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
var fields = lncType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||||
var field = fields.FirstOrDefault(f => f.GetValue(lncType)?.ToString() == type);
|
var field = fields.FirstOrDefault(f => f.GetValue(lncType)?.ToString() == type);
|
||||||
@@ -236,9 +244,96 @@ namespace BTCPayServer
|
|||||||
return attr?.Name ?? type;
|
return attr?.Name ?? type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSafe(this ILightningClient client)
|
private static bool TryParseLegacy(string str, out Dictionary<string, string> connectionString)
|
||||||
{
|
{
|
||||||
var kv = LightningConnectionStringHelper.ExtractValues(client.ToString(), out _);
|
if (str.StartsWith("/"))
|
||||||
|
{
|
||||||
|
str = "unix:" + str;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, string> dictionary = new Dictionary<string, string>();
|
||||||
|
connectionString = null;
|
||||||
|
if (!Uri.TryCreate(str, UriKind.Absolute, out Uri result))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!new string[4] { "unix", "tcp", "http", "https" }.Contains(result.Scheme))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Scheme == "unix")
|
||||||
|
{
|
||||||
|
str = result.AbsoluteUri.Substring("unix:".Length);
|
||||||
|
while (str.Length >= 1 && str[0] == '/')
|
||||||
|
{
|
||||||
|
str = str.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new Uri("unix://" + str, UriKind.Absolute);
|
||||||
|
dictionary.Add("type", "clightning");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Scheme == "tcp")
|
||||||
|
{
|
||||||
|
dictionary.Add("type", "clightning");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Scheme == "http" || result.Scheme == "https")
|
||||||
|
{
|
||||||
|
string[] array = result.UserInfo.Split(':');
|
||||||
|
if (string.IsNullOrEmpty(result.UserInfo) || array.Length != 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.Add("type", "charge");
|
||||||
|
dictionary.Add("username", array[0]);
|
||||||
|
dictionary.Add("password", array[1]);
|
||||||
|
if (result.Scheme == "http")
|
||||||
|
{
|
||||||
|
dictionary.Add("allowinsecure", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(result.UserInfo))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.Add("server", new UriBuilder(result)
|
||||||
|
{
|
||||||
|
UserName = "",
|
||||||
|
Password = ""
|
||||||
|
}.Uri.ToString());
|
||||||
|
connectionString = dictionary;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dictionary<string, string> ExtractValues(this ILightningClient client, string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(connectionString);
|
||||||
|
if (TryParseLegacy(connectionString, out var legacy))
|
||||||
|
return legacy;
|
||||||
|
string[] source = connectionString.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var kv = new Dictionary<string, string>();
|
||||||
|
foreach (string item in source.Select((string p) => p.Trim()))
|
||||||
|
{
|
||||||
|
int num = item.IndexOf('=');
|
||||||
|
if (num == -1)
|
||||||
|
continue;
|
||||||
|
string text = item.Substring(0, num).Trim().ToLowerInvariant();
|
||||||
|
string value = item.Substring(num + 1).Trim();
|
||||||
|
kv.TryAdd(text, value);
|
||||||
|
}
|
||||||
|
return kv;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use IsSafe(this ILightningClient client, string connectionString) instead")]
|
||||||
|
public static bool IsSafe(this ILightningClient client) => IsSafe(client, client.ToString());
|
||||||
|
public static bool IsSafe(this ILightningClient client, string connectionString)
|
||||||
|
{
|
||||||
|
var kv = client.ExtractValues(connectionString);
|
||||||
if (kv.TryGetValue("cookiefilepath", out _) ||
|
if (kv.TryGetValue("cookiefilepath", out _) ||
|
||||||
kv.TryGetValue("macaroondirectorypath", out _) ||
|
kv.TryGetValue("macaroondirectorypath", out _) ||
|
||||||
kv.TryGetValue("macaroonfilepath", out _) )
|
kv.TryGetValue("macaroonfilepath", out _) )
|
||||||
@@ -662,6 +757,9 @@ namespace BTCPayServer
|
|||||||
return controller.View("PostRedirect", redirectVm);
|
return controller.View("PostRedirect", redirectVm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string RemoveUserInfo(this Uri uri)
|
||||||
|
=> string.IsNullOrEmpty(uri.UserInfo) ? uri.ToString() : uri.ToString().Replace(uri.UserInfo, "***");
|
||||||
|
|
||||||
public static DataDirectories Configure(this DataDirectories dataDirectories, IConfiguration configuration)
|
public static DataDirectories Configure(this DataDirectories dataDirectories, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var networkType = DefaultConfiguration.GetNetworkType(configuration);
|
var networkType = DefaultConfiguration.GetNetworkType(configuration);
|
||||||
|
|||||||
25
BTCPayServer/Payments/Lightning/IExtendedLightningClient.cs
Normal file
25
BTCPayServer/Payments/Lightning/IExtendedLightningClient.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Lightning;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Payments.Lightning
|
||||||
|
{
|
||||||
|
public interface IExtendedLightningClient : ILightningClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to validate the client configuration
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<ValidationResult> Validate();
|
||||||
|
/// <summary>
|
||||||
|
/// The display name of this client (ie. LND (REST), Eclair, LNDhub)
|
||||||
|
/// </summary>
|
||||||
|
public string? DisplayName { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The server URI of this client (ie. http://localhost:8080)
|
||||||
|
/// </summary>
|
||||||
|
public Uri? ServerUri { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -263,7 +264,16 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = _lightningClientFactory.Create(config.ConnectionString, _Network);
|
var client = _lightningClientFactory.Create(config.ConnectionString, _Network);
|
||||||
if (!client.IsSafe())
|
if (client is IExtendedLightningClient vlc)
|
||||||
|
{
|
||||||
|
var result = await vlc.Validate();
|
||||||
|
if (result != ValidationResult.Success)
|
||||||
|
{
|
||||||
|
validationContext.ModelState.AddModelError(nameof(config.ConnectionString), result?.ErrorMessage ?? "Invalid connection string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!client.IsSafe(config.ConnectionString))
|
||||||
{
|
{
|
||||||
var canManage = (await validationContext.AuthorizationService.AuthorizeAsync(validationContext.User, null,
|
var canManage = (await validationContext.AuthorizationService.AuthorizeAsync(validationContext.User, null,
|
||||||
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
|
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
|
||||||
@@ -274,6 +284,11 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (FormatException ex)
|
||||||
|
{
|
||||||
|
validationContext.ModelState.AddModelError(nameof(config.ConnectionString), ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
validationContext.ModelState.AddModelError(nameof(config.ConnectionString), "Invalid connection string");
|
validationContext.ModelState.AddModelError(nameof(config.ConnectionString), "Invalid connection string");
|
||||||
|
|||||||
@@ -500,27 +500,20 @@ retry:
|
|||||||
public CancellationTokenSource? StopListeningCancellationTokenSource;
|
public CancellationTokenSource? StopListeningCancellationTokenSource;
|
||||||
async Task Listen(CancellationToken cancellation)
|
async Task Listen(CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
Uri? uri = null;
|
string? uri = null;
|
||||||
string? logUrl = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lightningClient = _lightningClientFactory.Create(ConnectionString, _network);
|
var lightningClient = _lightningClientFactory.Create(ConnectionString, _network);
|
||||||
if(lightningClient is null)
|
if(lightningClient is null)
|
||||||
return;
|
return;
|
||||||
uri = lightningClient.GetServerUri();
|
uri = lightningClient.GetServerUri(ConnectionString)?.RemoveUserInfo() ?? "";
|
||||||
logUrl = uri switch
|
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): Start listening {Uri}", _network.CryptoCode, uri);
|
||||||
{
|
|
||||||
null when LightningConnectionStringHelper.ExtractValues(ConnectionString, out var type) is not null => type,
|
|
||||||
null => string.Empty,
|
|
||||||
_ => string.IsNullOrEmpty(uri.UserInfo) ? uri.ToString() : uri.ToString().Replace(uri.UserInfo, "***")
|
|
||||||
};
|
|
||||||
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): Start listening {Uri}", _network.CryptoCode, logUrl);
|
|
||||||
using var session = await lightningClient.Listen(cancellation);
|
using var session = await lightningClient.Listen(cancellation);
|
||||||
// Just in case the payment arrived after our last poll but before we listened.
|
// Just in case the payment arrived after our last poll but before we listened.
|
||||||
await PollAllListenedInvoices(cancellation);
|
await PollAllListenedInvoices(cancellation);
|
||||||
if (_ErrorAlreadyLogged)
|
if (_ErrorAlreadyLogged)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): Could reconnect successfully to {Uri}", _network.CryptoCode, logUrl);
|
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): Could reconnect successfully to {Uri}", _network.CryptoCode, uri);
|
||||||
}
|
}
|
||||||
_ErrorAlreadyLogged = false;
|
_ErrorAlreadyLogged = false;
|
||||||
while (!_ListenedInvoices.IsEmpty)
|
while (!_ListenedInvoices.IsEmpty)
|
||||||
@@ -552,12 +545,12 @@ retry:
|
|||||||
catch (Exception ex) when (!cancellation.IsCancellationRequested && !_ErrorAlreadyLogged)
|
catch (Exception ex) when (!cancellation.IsCancellationRequested && !_ErrorAlreadyLogged)
|
||||||
{
|
{
|
||||||
_ErrorAlreadyLogged = true;
|
_ErrorAlreadyLogged = true;
|
||||||
Logs.PayServer.LogError(ex, "{CryptoCode} (Lightning): Error while contacting {Uri}", _network.CryptoCode, logUrl);
|
Logs.PayServer.LogError(ex, "{CryptoCode} (Lightning): Error while contacting {Uri}", _network.CryptoCode, uri);
|
||||||
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): Stop listening {Uri}", _network.CryptoCode, logUrl);
|
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): Stop listening {Uri}", _network.CryptoCode, uri);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
|
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
|
||||||
if (_ListenedInvoices.IsEmpty)
|
if (_ListenedInvoices.IsEmpty)
|
||||||
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): No more invoice to listen on {Uri}, releasing the connection", _network.CryptoCode, logUrl);
|
Logs.PayServer.LogInformation("{CryptoCode} (Lightning): No more invoice to listen on {Uri}, releasing the connection", _network.CryptoCode, uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint256? GetPaymentHash(ListenedInvoice listenedInvoice)
|
private uint256? GetPaymentHash(ListenedInvoice listenedInvoice)
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
@try
|
@try
|
||||||
{
|
{
|
||||||
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
|
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
|
||||||
<span>@client.GetDisplayName()</span>
|
<span>@client.GetDisplayName(Model.ConnectionString)</span>
|
||||||
var uri = client.GetServerUri();
|
var uri = client.GetServerUri(Model.ConnectionString);
|
||||||
if (uri is not null)
|
if (uri is not null)
|
||||||
{
|
{
|
||||||
<span>(@uri.Host)</span>
|
<span>(@uri.Host)</span>
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
@try
|
@try
|
||||||
{
|
{
|
||||||
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
|
var client = LightningClientFactoryService.Create(Model.ConnectionString, NetworkProvider.GetNetwork<BTCPayNetwork>(Model.CryptoCode));
|
||||||
<span>@client.GetDisplayName()</span>
|
<span>@client.GetDisplayName(Model.ConnectionString)</span>
|
||||||
var uri = client.GetServerUri();
|
var uri = client.GetServerUri(Model.ConnectionString);
|
||||||
if (uri is not null)
|
if (uri is not null)
|
||||||
{
|
{
|
||||||
<span>(@uri.Host)</span>
|
<span>(@uri.Host)</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user