Compare commits
150 Commits
v1.0.3.110
...
v1.0.3.127
Author | SHA1 | Date | |
---|---|---|---|
63df6ac5eb | |||
039bee5b65 | |||
be5597085b | |||
6b355cbe1b | |||
dec5d19a2f | |||
ff533994d8 | |||
221e2c7898 | |||
fb77fddcc3 | |||
c37086e000 | |||
3d6783b743 | |||
c479e6aae5 | |||
59a770e0d7 | |||
140259e737 | |||
59a391dcc9 | |||
3a1cdefa09 | |||
7be104f486 | |||
d90a65975c | |||
4e53f59a9c | |||
8e58fc128d | |||
756b6e9692 | |||
23d546c559 | |||
6d4ea6a951 | |||
f9b5dcd4a6 | |||
eab679cb2b | |||
ddf8b20091 | |||
f1457582fe | |||
7841f79f31 | |||
56e5acfb65 | |||
6b777878e3 | |||
428c7c5444 | |||
f8427eb801 | |||
2a53c056ca | |||
21d555ee6b | |||
d79fda166f | |||
c8025ebaac | |||
42d7ad02b0 | |||
21556d4c07 | |||
89a7166c1b | |||
5d6c28c997 | |||
717cadc64b | |||
3dac7ef3f3 | |||
056cb60d5d | |||
bb4e92ec50 | |||
9218fb6463 | |||
d9baea4c38 | |||
6df6537cf9 | |||
72d199f390 | |||
233bce578b | |||
63472d54d7 | |||
db57b5ae80 | |||
8896d89908 | |||
8e07bf3ffb | |||
6194d0ad44 | |||
138532d3d4 | |||
4716b704d4 | |||
109e576811 | |||
631c878722 | |||
4cbcdb8af5 | |||
d24628a386 | |||
7e714bdfa2 | |||
e3283fb29b | |||
be0285155f | |||
1c055a7282 | |||
8d3cdd39ca | |||
010ba4d5b6 | |||
d176a16caa | |||
fd4a27c1a3 | |||
ae73858e23 | |||
1427e5458b | |||
8853cf9f83 | |||
de7f22bcbc | |||
b8b2fa29d7 | |||
476a241936 | |||
dc97982fad | |||
e488f93b17 | |||
e6e9668bbb | |||
56976898bd | |||
221ff05c49 | |||
8f719d3e33 | |||
67c2abca2d | |||
36046f08f7 | |||
3c4455c23c | |||
e3db2e2b76 | |||
5567a26b33 | |||
5387c3dd97 | |||
d14eef979c | |||
c7069f4fd9 | |||
b40239f93b | |||
2958175add | |||
719ad8f4d4 | |||
4055eda757 | |||
442df56629 | |||
477ab67fe9 | |||
64c60741a0 | |||
9e354d7703 | |||
1932c1cd7c | |||
11670d0c0f | |||
6cab02cd99 | |||
fc1d781272 | |||
a58ecfd35a | |||
645516ee1b | |||
f570de5086 | |||
81ccfa1e6c | |||
b808aa4971 | |||
d1f1bc93b3 | |||
faf433f644 | |||
ba4660a03a | |||
03aa3693d0 | |||
ecae976993 | |||
307c8980e0 | |||
e53d0eda47 | |||
369b15b20b | |||
ff8bbcd88a | |||
c52d22dc30 | |||
4fa6c9dc3d | |||
a958d10dd9 | |||
ff86ce64b4 | |||
f31d8aa9d7 | |||
3cf7406123 | |||
5d8bf196a8 | |||
019bd26c51 | |||
0e1f924fc3 | |||
15c3893aab | |||
deeab7c238 | |||
e5ba7b9e69 | |||
ca5be7e38d | |||
fb530f2b34 | |||
29cbf63346 | |||
13c03cc0c2 | |||
281280d3ec | |||
410be51951 | |||
eefe8289b3 | |||
a53a5944f8 | |||
cd009466b6 | |||
f0c106de75 | |||
fcf1b679e6 | |||
03ba57cd46 | |||
bea08e5cfd | |||
01787e2662 | |||
ac76220349 | |||
796954c6e3 | |||
292c188182 | |||
1f7097ef89 | |||
b97e083017 | |||
8ffd182b98 | |||
1e77546251 | |||
8711960e74 | |||
8e2bcef824 | |||
d418cf7b07 | |||
77338c6054 |
@ -22,13 +22,14 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
BTCPayNetworkProvider(BTCPayNetworkProvider filtered, string[] cryptoCodes)
|
||||
BTCPayNetworkProvider(BTCPayNetworkProvider unfiltered, string[] cryptoCodes)
|
||||
{
|
||||
NetworkType = filtered.NetworkType;
|
||||
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(filtered.NetworkType);
|
||||
UnfilteredNetworks = unfiltered.UnfilteredNetworks ?? unfiltered;
|
||||
NetworkType = unfiltered.NetworkType;
|
||||
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(unfiltered.NetworkType);
|
||||
_Networks = new Dictionary<string, BTCPayNetworkBase>();
|
||||
cryptoCodes = cryptoCodes.Select(c => c.ToUpperInvariant()).ToArray();
|
||||
foreach (var network in filtered._Networks)
|
||||
foreach (var network in unfiltered._Networks)
|
||||
{
|
||||
if(cryptoCodes.Contains(network.Key))
|
||||
{
|
||||
@ -37,9 +38,12 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
public BTCPayNetworkProvider UnfilteredNetworks { get; }
|
||||
|
||||
public NetworkType NetworkType { get; private set; }
|
||||
public BTCPayNetworkProvider(NetworkType networkType)
|
||||
{
|
||||
UnfilteredNetworks = this;
|
||||
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
|
||||
NetworkType = networkType;
|
||||
InitBitcoin();
|
||||
@ -102,9 +106,14 @@ namespace BTCPayServer
|
||||
{
|
||||
return _Networks.ContainsKey(cryptoCode.ToUpperInvariant());
|
||||
}
|
||||
|
||||
public BTCPayNetworkBase GetNetwork(string cryptoCode)
|
||||
{
|
||||
return GetNetwork<BTCPayNetworkBase>(cryptoCode);
|
||||
}
|
||||
public T GetNetwork<T>(string cryptoCode) where T: BTCPayNetworkBase
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(cryptoCode));
|
||||
if(!_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetworkBase network))
|
||||
{
|
||||
if (cryptoCode == "XBT")
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Version.csproj" Condition="Exists('../Version.csproj')" />
|
||||
<Import Project="../Common.csproj" />
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.2.35" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.17" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.2.38" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.18" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Version.csproj" Condition="Exists('../Version.csproj')" />
|
||||
<Import Project="../Common.csproj" />
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
|
@ -70,61 +70,73 @@ namespace BTCPayServer.Services.Rates
|
||||
//}
|
||||
//b.AppendLine("}.ToArray()");
|
||||
AvailableExchanges = new CoinAverageExchanges();
|
||||
foreach(var item in
|
||||
foreach (var item in
|
||||
new[] {
|
||||
(DisplayName: "BitBargain", Name: "bitbargain"),
|
||||
(DisplayName: "Tidex", Name: "tidex"),
|
||||
(DisplayName: "LocalBitcoins", Name: "localbitcoins"),
|
||||
(DisplayName: "EtherDelta", Name: "etherdelta"),
|
||||
(DisplayName: "Kraken", Name: "kraken"),
|
||||
(DisplayName: "BitBay", Name: "bitbay"),
|
||||
(DisplayName: "Independent Reserve", Name: "independentreserve"),
|
||||
(DisplayName: "Exmoney", Name: "exmoney"),
|
||||
(DisplayName: "Bitcoin.co.id", Name: "bitcoin_co_id"),
|
||||
(DisplayName: "Huobi", Name: "huobi"),
|
||||
(DisplayName: "GDAX", Name: "gdax"),
|
||||
(DisplayName: "Coincheck", Name: "coincheck"),
|
||||
(DisplayName: "Bittylicious", Name: "bittylicious"),
|
||||
(DisplayName: "Gemini", Name: "gemini"),
|
||||
(DisplayName: "Bit2C", Name: "bit2c"),
|
||||
(DisplayName: "Luno", Name: "luno"),
|
||||
(DisplayName: "Negocie Coins", Name: "negociecoins"),
|
||||
(DisplayName: "FYB-SE", Name: "fybse"),
|
||||
(DisplayName: "Hitbtc", Name: "hitbtc"),
|
||||
(DisplayName: "Bitex.la", Name: "bitex"),
|
||||
(DisplayName: "Korbit", Name: "korbit"),
|
||||
(DisplayName: "itBit", Name: "itbit"),
|
||||
(DisplayName: "Okex", Name: "okex"),
|
||||
(DisplayName: "Bitsquare", Name: "bitsquare"),
|
||||
(DisplayName: "Bitfinex", Name: "bitfinex"),
|
||||
(DisplayName: "CoinMate", Name: "coinmate"),
|
||||
(DisplayName: "Bitstamp", Name: "bitstamp"),
|
||||
(DisplayName: "Cryptonit", Name: "cryptonit"),
|
||||
(DisplayName: "Foxbit", Name: "foxbit"),
|
||||
(DisplayName: "QuickBitcoin", Name: "quickbitcoin"),
|
||||
(DisplayName: "Poloniex", Name: "poloniex"),
|
||||
(DisplayName: "Bit-Z", Name: "bitz"),
|
||||
(DisplayName: "Liqui", Name: "liqui"),
|
||||
(DisplayName: "BitKonan", Name: "bitkonan"),
|
||||
(DisplayName: "Kucoin", Name: "kucoin"),
|
||||
(DisplayName: "Binance", Name: "binance"),
|
||||
(DisplayName: "Rock Trading", Name: "rocktrading"),
|
||||
(DisplayName: "Mercado Bitcoin", Name: "mercado"),
|
||||
(DisplayName: "Coinsecure", Name: "coinsecure"),
|
||||
(DisplayName: "Idex", Name: "idex"),
|
||||
(DisplayName: "Coinfloor", Name: "coinfloor"),
|
||||
(DisplayName: "bitFlyer", Name: "bitflyer"),
|
||||
(DisplayName: "BTCTurk", Name: "btcturk"),
|
||||
(DisplayName: "Bittrex", Name: "bittrex"),
|
||||
(DisplayName: "CampBX", Name: "campbx"),
|
||||
(DisplayName: "Zaif", Name: "zaif"),
|
||||
(DisplayName: "FYB-SG", Name: "fybsg"),
|
||||
(DisplayName: "Quoine", Name: "quoine"),
|
||||
(DisplayName: "Okex", Name: "okex"),
|
||||
(DisplayName: "Bitfinex", Name: "bitfinex"),
|
||||
(DisplayName: "Bittylicious", Name: "bittylicious"),
|
||||
(DisplayName: "BTC Markets", Name: "btcmarkets"),
|
||||
(DisplayName: "Kucoin", Name: "kucoin"),
|
||||
(DisplayName: "IDAX", Name: "idax"),
|
||||
(DisplayName: "Kraken", Name: "kraken"),
|
||||
(DisplayName: "Bit2C", Name: "bit2c"),
|
||||
(DisplayName: "Mercado Bitcoin", Name: "mercado"),
|
||||
(DisplayName: "CEX.IO", Name: "cex"),
|
||||
(DisplayName: "Bitex.la", Name: "bitex"),
|
||||
(DisplayName: "Quoine", Name: "quoine"),
|
||||
(DisplayName: "Stex", Name: "stex"),
|
||||
(DisplayName: "CoinTiger", Name: "cointiger"),
|
||||
(DisplayName: "Poloniex", Name: "poloniex"),
|
||||
(DisplayName: "Zaif", Name: "zaif"),
|
||||
(DisplayName: "Huobi", Name: "huobi"),
|
||||
(DisplayName: "QuickBitcoin", Name: "quickbitcoin"),
|
||||
(DisplayName: "Tidex", Name: "tidex"),
|
||||
(DisplayName: "Tokenomy", Name: "tokenomy"),
|
||||
(DisplayName: "Bitcoin.co.id", Name: "bitcoin_co_id"),
|
||||
(DisplayName: "Kryptono", Name: "kryptono"),
|
||||
(DisplayName: "Bitso", Name: "bitso"),
|
||||
(DisplayName: "Korbit", Name: "korbit"),
|
||||
(DisplayName: "Yobit", Name: "yobit"),
|
||||
(DisplayName: "BitBargain", Name: "bitbargain"),
|
||||
(DisplayName: "Livecoin", Name: "livecoin"),
|
||||
(DisplayName: "Hotbit", Name: "hotbit"),
|
||||
(DisplayName: "Coincheck", Name: "coincheck"),
|
||||
(DisplayName: "Binance", Name: "binance"),
|
||||
(DisplayName: "Bit-Z", Name: "bitz"),
|
||||
(DisplayName: "Coinbase Pro", Name: "coinbasepro"),
|
||||
(DisplayName: "Rock Trading", Name: "rocktrading"),
|
||||
(DisplayName: "Bittrex", Name: "bittrex"),
|
||||
(DisplayName: "BitBay", Name: "bitbay"),
|
||||
(DisplayName: "Tokenize", Name: "tokenize"),
|
||||
(DisplayName: "Hitbtc", Name: "hitbtc"),
|
||||
(DisplayName: "Upbit", Name: "upbit"),
|
||||
(DisplayName: "Bitstamp", Name: "bitstamp"),
|
||||
(DisplayName: "Luno", Name: "luno"),
|
||||
(DisplayName: "Trade.io", Name: "tradeio"),
|
||||
(DisplayName: "LocalBitcoins", Name: "localbitcoins"),
|
||||
(DisplayName: "Independent Reserve", Name: "independentreserve"),
|
||||
(DisplayName: "Coinsquare", Name: "coinsquare"),
|
||||
(DisplayName: "Exmoney", Name: "exmoney"),
|
||||
(DisplayName: "Coinegg", Name: "coinegg"),
|
||||
(DisplayName: "FYB-SG", Name: "fybsg"),
|
||||
(DisplayName: "Cryptonit", Name: "cryptonit"),
|
||||
(DisplayName: "BTCTurk", Name: "btcturk"),
|
||||
(DisplayName: "bitFlyer", Name: "bitflyer"),
|
||||
(DisplayName: "Negocie Coins", Name: "negociecoins"),
|
||||
(DisplayName: "OasisDEX", Name: "oasisdex"),
|
||||
(DisplayName: "CoinMate", Name: "coinmate"),
|
||||
(DisplayName: "BitForex", Name: "bitforex"),
|
||||
(DisplayName: "Bitsquare", Name: "bitsquare"),
|
||||
(DisplayName: "FYB-SE", Name: "fybse"),
|
||||
(DisplayName: "itBit", Name: "itbit"),
|
||||
})
|
||||
{
|
||||
AvailableExchanges.TryAdd(item.Name, new CoinAverageExchange(item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}"));
|
||||
}
|
||||
// Keep back-compat
|
||||
AvailableExchanges.Add(new CoinAverageExchange("gdax", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/coinbasepro"));
|
||||
}
|
||||
|
||||
public Task AddHeader(HttpRequestMessage message)
|
||||
|
@ -116,7 +116,6 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
|
||||
|
||||
// Those exchanges make multiple requests when calling GetTickers so we remove them
|
||||
//DirectProviders.Add("gdax", new ExchangeSharpRateProvider("gdax", new ExchangeGdaxAPI()));
|
||||
//DirectProviders.Add("gemini", new ExchangeSharpRateProvider("gemini", new ExchangeGeminiAPI()));
|
||||
//DirectProviders.Add("bitfinex", new ExchangeSharpRateProvider("bitfinex", new ExchangeBitfinexAPI()));
|
||||
//DirectProviders.Add("okex", new ExchangeSharpRateProvider("okex", new ExchangeOkexAPI()));
|
||||
|
368
BTCPayServer.Tests/AuthenticationTests.cs
Normal file
368
BTCPayServer.Tests/AuthenticationTests.cs
Normal file
@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using BTCPayServer.Data;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenQA.Selenium;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class AuthenticationTests
|
||||
{
|
||||
public AuthenticationTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task GetRedirectedToLoginPathOnChallenge()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var client = tester.PayTester.HttpClient;
|
||||
//Wallets endpoint is protected
|
||||
var response = await client.GetAsync("wallets");
|
||||
var urlPath = response.RequestMessage.RequestUri.ToString()
|
||||
.Replace(tester.PayTester.ServerUri.ToString(), "");
|
||||
//Cookie Challenge redirects you to login page
|
||||
Assert.StartsWith("Account/Login", urlPath, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
var queryString = response.RequestMessage.RequestUri.ParseQueryString();
|
||||
|
||||
Assert.NotNull(queryString["ReturnUrl"]);
|
||||
Assert.Equal("/wallets", queryString["ReturnUrl"]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanGetOpenIdConfiguration()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
using (var response =
|
||||
await tester.PayTester.HttpClient.GetAsync("/.well-known/openid-configuration"))
|
||||
{
|
||||
using (var streamToReadFrom = new StreamReader(await response.Content.ReadAsStreamAsync()))
|
||||
{
|
||||
var json = await streamToReadFrom.ReadToEndAsync();
|
||||
Assert.NotNull(json);
|
||||
var configuration = OpenIdConnectConfiguration.Create(json);
|
||||
Assert.NotNull(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseNonInteractiveFlows()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var token = await RegisterPasswordClientAndGetAccessToken(user, null, tester);
|
||||
await TestApiAgainstAccessToken(token, tester, user);
|
||||
token = await RegisterPasswordClientAndGetAccessToken(user, "secret", tester);
|
||||
await TestApiAgainstAccessToken(token, tester, user);
|
||||
token = await RegisterClientCredentialsFlowAndGetAccessToken(user, "secret", tester);
|
||||
await TestApiAgainstAccessToken(token, tester, user);
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Fact]
|
||||
public async Task CanUseImplicitFlow()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Start();
|
||||
var tester = s.Server;
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Implicit},
|
||||
RedirectUris = {redirecturi}
|
||||
});
|
||||
var implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid&nonce={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
var url = s.Driver.Url;
|
||||
var results = url.Split("#").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
|
||||
|
||||
//in Implicit mode, you renew your token by hitting the same endpoint but adding prompt=none. If you are still logged in on the site, you will receive a fresh token.
|
||||
var implicitAuthorizeUrlSilentModel = new Uri($"{implicitAuthorizeUrl.OriginalString}&prompt=none");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
url = s.Driver.Url;
|
||||
results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
|
||||
LogoutFlow(tester, id, s);
|
||||
}
|
||||
}
|
||||
|
||||
void LogoutFlow(ServerTester tester, string clientId, SeleniumTester seleniumTester)
|
||||
{
|
||||
var logoutUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/logout?response_type=token&client_id={clientId}");
|
||||
seleniumTester.Driver.Navigate().GoToUrl(logoutUrl);
|
||||
seleniumTester.GoToHome();
|
||||
Assert.Throws<NoSuchElementException>(() => seleniumTester.Driver.FindElement(By.Id("Logout")));
|
||||
|
||||
}
|
||||
|
||||
[Trait("Selenium", "Selenium")]
|
||||
[Fact]
|
||||
public async Task CanUseCodeFlow()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Start();
|
||||
var tester = s.Server;
|
||||
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
|
||||
var secret = "secret";
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions =
|
||||
{
|
||||
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
|
||||
OpenIddictConstants.Permissions.GrantTypes.RefreshToken
|
||||
},
|
||||
RedirectUris = {redirecturi}
|
||||
}, secret);
|
||||
var authorizeUrl = new Uri(tester.PayTester.ServerUri,
|
||||
$"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access&state={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(authorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
var url = s.Driver.Url;
|
||||
var results = url.Split("?").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
|
||||
var httpClient = tester.PayTester.HttpClient;
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(tester.PayTester.ServerUri, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type",
|
||||
OpenIddictConstants.GrantTypes.AuthorizationCode),
|
||||
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
|
||||
new KeyValuePair<string, string>("client_secret", secret),
|
||||
new KeyValuePair<string, string>("code", results["code"]),
|
||||
new KeyValuePair<string, string>("redirect_uri", redirecturi.AbsoluteUri)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
|
||||
|
||||
await TestApiAgainstAccessToken(result.AccessToken, tester, user);
|
||||
|
||||
var refreshedAccessToken = await RefreshAnAccessToken(result.RefreshToken, httpClient, id, secret);
|
||||
|
||||
await TestApiAgainstAccessToken(refreshedAccessToken, tester, user);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> RefreshAnAccessToken(string refreshToken, HttpClient client, string clientId,
|
||||
string clientSecret = null)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(client.BaseAddress, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type",
|
||||
OpenIddictConstants.GrantTypes.RefreshToken),
|
||||
new KeyValuePair<string, string>("client_id", clientId),
|
||||
new KeyValuePair<string, string>("client_secret", clientSecret),
|
||||
new KeyValuePair<string, string>("refresh_token", refreshToken)
|
||||
})
|
||||
};
|
||||
|
||||
var response = await client.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
}
|
||||
|
||||
private static async Task<string> RegisterClientCredentialsFlowAndGetAccessToken(TestAccount user,
|
||||
string secret,
|
||||
ServerTester tester)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = {OpenIddictConstants.Permissions.GrantTypes.ClientCredentials}
|
||||
}, secret);
|
||||
|
||||
|
||||
var httpClient = tester.PayTester.HttpClient;
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(tester.PayTester.ServerUri, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type",
|
||||
OpenIddictConstants.GrantTypes.ClientCredentials),
|
||||
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
|
||||
new KeyValuePair<string, string>("client_secret", secret)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
}
|
||||
|
||||
private static async Task<string> RegisterPasswordClientAndGetAccessToken(TestAccount user, string secret,
|
||||
ServerTester tester)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var openIdClient = await user.RegisterOpenIdClient(
|
||||
new OpenIddictApplicationDescriptor()
|
||||
{
|
||||
ClientId = id,
|
||||
DisplayName = id,
|
||||
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Password}
|
||||
}, secret);
|
||||
|
||||
|
||||
var httpClient = tester.PayTester.HttpClient;
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||
new Uri(tester.PayTester.ServerUri, "/connect/token"))
|
||||
{
|
||||
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type", OpenIddictConstants.GrantTypes.Password),
|
||||
new KeyValuePair<string, string>("username", user.RegisterDetails.Email),
|
||||
new KeyValuePair<string, string>("password", user.RegisterDetails.Password),
|
||||
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
|
||||
new KeyValuePair<string, string>("client_secret", secret)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest);
|
||||
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
}
|
||||
|
||||
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount)
|
||||
{
|
||||
var resultUser =
|
||||
await TestApiAgainstAccessToken<string>(accessToken, "api/test/me/id",
|
||||
tester.PayTester.HttpClient);
|
||||
Assert.Equal(testAccount.UserId, resultUser);
|
||||
|
||||
var secondUser = tester.NewAccount();
|
||||
secondUser.GrantAccess();
|
||||
|
||||
var resultStores =
|
||||
await TestApiAgainstAccessToken<StoreData[]>(accessToken, "api/test/me/stores",
|
||||
tester.PayTester.HttpClient);
|
||||
Assert.Contains(resultStores,
|
||||
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.DoesNotContain(resultStores,
|
||||
data => data.Id.Equals(secondUser.StoreId, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"api/test/me/stores/{testAccount.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient));
|
||||
|
||||
|
||||
Assert.Equal(testAccount.RegisterDetails.IsAdmin, await TestApiAgainstAccessToken<bool>(accessToken,
|
||||
$"api/test/me/is-admin",
|
||||
tester.PayTester.HttpClient));
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
await TestApiAgainstAccessToken<bool>(accessToken, $"api/test/me/stores/{secondUser.StoreId}/can-edit",
|
||||
tester.PayTester.HttpClient);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<T> TestApiAgainstAccessToken<T>(string accessToken, string url, HttpClient client)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get,
|
||||
new Uri(client.BaseAddress, url));
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||
var result = await client.SendAsync(httpRequest);
|
||||
result.EnsureSuccessStatusCode();
|
||||
|
||||
var rawJson = await result.Content.ReadAsStringAsync();
|
||||
if (typeof(T).IsPrimitive || typeof(T) == typeof(string))
|
||||
{
|
||||
return (T)Convert.ChangeType(rawJson, typeof(T));
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(rawJson);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Configuration;
|
||||
using System.Linq;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
@ -148,7 +148,7 @@ namespace BTCPayServer.Tests
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
_Host.Start();
|
||||
_Host.StartWithTasksAsync().GetAwaiter().GetResult();
|
||||
|
||||
var urls = _Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
|
||||
foreach (var url in urls)
|
||||
|
@ -5,7 +5,7 @@ ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
|
||||
WORKDIR /source
|
||||
COPY Common.csproj Common.csproj
|
||||
COPY Build/Common.csproj Build/Common.csproj
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||
COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj
|
||||
COPY BTCPayServer.Rating/BTCPayServer.Rating.csproj BTCPayServer.Rating/BTCPayServer.Rating.csproj
|
||||
|
239
BTCPayServer.Tests/PaymentHandlerTest.cs
Normal file
239
BTCPayServer.Tests/PaymentHandlerTest.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using NBitcoin;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class PaymentHandlerTest
|
||||
{
|
||||
private BitcoinLikePaymentHandler handlerBTC;
|
||||
private LightningLikePaymentHandler handlerLN;
|
||||
private Dictionary<CurrencyPair, Task<RateResult>> currencyPairRateResult;
|
||||
|
||||
public PaymentHandlerTest(ITestOutputHelper helper)
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
|
||||
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
|
||||
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
|
||||
currencyPairRateResult = new Dictionary<CurrencyPair, Task<RateResult>>();
|
||||
|
||||
var rateResultUSDBTC = new RateResult();
|
||||
rateResultUSDBTC.BidAsk= new BidAsk(1m);
|
||||
|
||||
var rateResultBTCUSD = new RateResult();
|
||||
rateResultBTCUSD.BidAsk= new BidAsk(1m);
|
||||
|
||||
currencyPairRateResult.Add(new CurrencyPair("USD", "BTC"), Task.FromResult(rateResultUSDBTC));
|
||||
currencyPairRateResult.Add(new CurrencyPair("BTC", "USD"), Task.FromResult(rateResultBTCUSD));
|
||||
|
||||
handlerBTC = new BitcoinLikePaymentHandler(null, networkProvider, null, null);
|
||||
handlerLN = new LightningLikePaymentHandler(null, null, networkProvider, null);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayWithLightningWhenInvoiceTotalUnderLightningMaxValue()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = null,
|
||||
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
|
||||
};
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
|
||||
|
||||
//When
|
||||
var totalInvoiceAmount = new Money(98m, MoneyUnit.BTC);
|
||||
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerLN.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.Equal(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CannotPayWithLightningWhenInvoiceTotalAboveLightningMaxValue()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = null,
|
||||
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
|
||||
};
|
||||
var totalInvoiceAmount = new Money(102m, MoneyUnit.BTC);
|
||||
|
||||
//When
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerLN.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.NotEqual(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayWithLightningWhenInvoiceTotalEqualLightningMaxValue()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = null,
|
||||
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
|
||||
};
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
|
||||
|
||||
//When
|
||||
var totalInvoiceAmount = new Money(100m, MoneyUnit.BTC);
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerLN.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.Equal(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayWithBitcoinWhenInvoiceTotalAboveOnChainMinValue()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"},
|
||||
LightningMaxValue = null
|
||||
};
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
|
||||
//When
|
||||
var totalInvoiceAmount = new Money(105m, MoneyUnit.BTC);
|
||||
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.Equal(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CannotPayWithBitcoinWhenInvoiceTotalUnderOnChainMinValue()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"},
|
||||
LightningMaxValue = null
|
||||
};
|
||||
var totalInvoiceAmount = new Money(98m, MoneyUnit.BTC);
|
||||
|
||||
//When
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.NotEqual(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPayWithBitcoinWhenInvoiceTotalEqualOnChainMinValue()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"},
|
||||
LightningMaxValue = null
|
||||
};
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
|
||||
//When
|
||||
var totalInvoiceAmount = new Money(100m, MoneyUnit.BTC);
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.Equal(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CannotPayWithBitcoinWhenInvoiceTotalUnderOnChainMinValueWhenLightningMaxValueIsGreater()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0618
|
||||
|
||||
//Given
|
||||
var store = new StoreBlob
|
||||
{
|
||||
OnChainMinValue = new CurrencyValue() {Value = 50.00m, Currency = "USD"},
|
||||
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
|
||||
};
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
|
||||
//When
|
||||
var totalInvoiceAmount = new Money(45m, MoneyUnit.BTC);
|
||||
|
||||
//Then
|
||||
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
|
||||
totalInvoiceAmount, paymentMethodId);
|
||||
|
||||
Assert.NotEqual(errorMessage.Result, string.Empty);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ namespace BTCPayServer.Tests
|
||||
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
|
||||
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
|
||||
builder.AppendLine("// Some other cool comments");
|
||||
builder.AppendLine("BTC_usd = GDax(BTC_USD)");
|
||||
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
|
||||
builder.AppendLine("BTC_X = Coinbase(BTC_X);");
|
||||
builder.AppendLine("X_X = CoinAverage(X_X) * 1.02");
|
||||
|
||||
@ -45,14 +45,14 @@ namespace BTCPayServer.Tests
|
||||
"DOGE_X = DOGE_BTC * BTC_X * 1.1;\n" +
|
||||
"DOGE_BTC = bittrex(DOGE_BTC);\n" +
|
||||
"// Some other cool comments\n" +
|
||||
"BTC_USD = gdax(BTC_USD);\n" +
|
||||
"BTC_USD = kraken(BTC_USD);\n" +
|
||||
"BTC_X = coinbase(BTC_X);\n" +
|
||||
"X_X = coinaverage(X_X) * 1.02;",
|
||||
rules.ToString());
|
||||
var tests = new[]
|
||||
{
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1"),
|
||||
(Pair: "BTC_USD", Expected: "gdax(BTC_USD)"),
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1"),
|
||||
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)"),
|
||||
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"),
|
||||
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
|
||||
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"),
|
||||
@ -62,14 +62,14 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(test.Expected, rules.GetRuleFor(CurrencyPair.Parse(test.Pair)).ToString());
|
||||
}
|
||||
rules.Spread = 0.2m;
|
||||
Assert.Equal("(bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
|
||||
Assert.Equal("(bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
|
||||
////////////////
|
||||
|
||||
// Check errors conditions
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("DOGE_X = LTC_CAD * BTC_X * 1.1");
|
||||
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
|
||||
builder.AppendLine("BTC_usd = GDax(BTC_USD)");
|
||||
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
|
||||
builder.AppendLine("LTC_CHF = LTC_CHF * 1.01");
|
||||
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
@ -77,7 +77,7 @@ namespace BTCPayServer.Tests
|
||||
tests = new[]
|
||||
{
|
||||
(Pair: "LTC_CAD", Expected: "ERR_NO_RULE_MATCH(LTC_CAD)"),
|
||||
(Pair: "DOGE_USD", Expected: "ERR_NO_RULE_MATCH(LTC_CAD) * gdax(BTC_USD) * 1.1"),
|
||||
(Pair: "DOGE_USD", Expected: "ERR_NO_RULE_MATCH(LTC_CAD) * kraken(BTC_USD) * 1.1"),
|
||||
(Pair: "LTC_CHF", Expected: "ERR_TOO_MUCH_NESTED_CALLS(LTC_CHF) * 1.01"),
|
||||
};
|
||||
foreach (var test in tests)
|
||||
@ -90,15 +90,15 @@ namespace BTCPayServer.Tests
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
|
||||
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
|
||||
builder.AppendLine("BTC_usd = GDax(BTC_USD)");
|
||||
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
|
||||
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
|
||||
builder.AppendLine("X_X = CoinAverage(X_X) * 1.02");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
|
||||
var tests2 = new[]
|
||||
{
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),gdax(BTC_USD)"),
|
||||
(Pair: "BTC_USD", Expected: "gdax(BTC_USD)", ExpectedExchangeRates: "gdax(BTC_USD)"),
|
||||
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),kraken(BTC_USD)"),
|
||||
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
|
||||
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"),
|
||||
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"),
|
||||
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"),
|
||||
|
@ -130,14 +130,22 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("Status Code: 404; Not Found", Driver.PageSource);
|
||||
}
|
||||
|
||||
internal void GoToHome()
|
||||
public void GoToHome()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
|
||||
}
|
||||
|
||||
internal void Logout()
|
||||
public void Logout()
|
||||
{
|
||||
Driver.FindElement(By.Id("Logout")).Click();
|
||||
}
|
||||
|
||||
public void Login(string user, string password)
|
||||
{
|
||||
Driver.FindElement(By.Id("Email")).SendKeys(user);
|
||||
Driver.FindElement(By.Id("Password")).SendKeys(password);
|
||||
Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogIn(SeleniumTester s, string email)
|
||||
static void LogIn(SeleniumTester s, string email)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("Login")).Click();
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
|
||||
@ -94,6 +94,55 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
}
|
||||
[Fact]
|
||||
public void CanUseDynamicDns()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Start();
|
||||
var alice = s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
|
||||
s.Driver.AssertNoError();
|
||||
if (s.Driver.PageSource.Contains("pouet.hello.com"))
|
||||
{
|
||||
// Cleanup old test run
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
}
|
||||
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
// We will just cheat for test purposes by only querying the server
|
||||
s.Driver.FindElement(By.Id("ServiceUrl")).SendKeys(s.Link("/"));
|
||||
s.Driver.FindElement(By.Id("Settings_Hostname")).SendKeys("pouet.hello.com");
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("MyLog");
|
||||
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("MyLog" + Keys.Enter);
|
||||
s.Driver.AssertNoError();
|
||||
Assert.Contains("The Dynamic DNS has been successfully queried", s.Driver.PageSource);
|
||||
Assert.EndsWith("/server/services/dynamic-dns", s.Driver.Url);
|
||||
|
||||
// Try to do the same thing should fail (hostname already exists)
|
||||
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.Id("ServiceUrl")).SendKeys(s.Link("/"));
|
||||
s.Driver.FindElement(By.Id("Settings_Hostname")).SendKeys("pouet.hello.com");
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("MyLog");
|
||||
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("MyLog" + Keys.Enter);
|
||||
s.Driver.AssertNoError();
|
||||
Assert.Contains("This hostname already exists", s.Driver.PageSource);
|
||||
|
||||
// Delete it
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
|
||||
Assert.Contains("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCreateStores()
|
||||
@ -166,7 +215,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateInvoice(SeleniumTester s, string store)
|
||||
static void CreateInvoice(SeleniumTester s, string store)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("Invoices")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
@ -193,7 +242,9 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("input#EnableShoppingCart.form-check")).Click();
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
|
||||
Assert.True(s.Driver.PageSource.Contains("App updated"), "Unable to create PoS");
|
||||
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using Xunit;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Payments;
|
||||
@ -18,6 +19,8 @@ using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Data;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -166,5 +169,14 @@ namespace BTCPayServer.Tests
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
public async Task<BTCPayOpenIdClient> RegisterOpenIdClient(OpenIddictApplicationDescriptor descriptor, string secret = null)
|
||||
{
|
||||
var openIddictApplicationManager = parent.PayTester.GetService<OpenIddictApplicationManager<BTCPayOpenIdClient>>();
|
||||
var client = new BTCPayOpenIdClient {ApplicationUserId = UserId};
|
||||
await openIddictApplicationManager.PopulateAsync(client, descriptor);
|
||||
await openIddictApplicationManager.CreateAsync(client, secret);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ namespace BTCPayServer.Tests
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
});
|
||||
InvoiceEntity invoiceEntity = new InvoiceEntity() { PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
|
||||
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
||||
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
invoiceEntity.ProductInformation = new ProductInformation() {Price = 100};
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
@ -123,7 +123,7 @@ namespace BTCPayServer.Tests
|
||||
}));
|
||||
invoiceEntity.SetPaymentMethods(paymentMethods);
|
||||
|
||||
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
var accounting = btc.Calculate();
|
||||
|
||||
invoiceEntity.Payments.Add(
|
||||
@ -131,8 +131,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Accounted = true,
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.00000100m
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
@ -144,8 +143,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Accounted = true,
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.00000100m
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
@ -155,14 +153,14 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Zero, accounting.DueUncapped);
|
||||
|
||||
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike), null);
|
||||
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = ltc.Calculate();
|
||||
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
// LTC might have over paid due to BTC paying above what it should (round 1 satoshi up)
|
||||
Assert.True(accounting.DueUncapped < Money.Zero);
|
||||
|
||||
var paymentMethod = InvoiceWatcher.GetNearestClearedPayment(paymentMethods, out var accounting2, null);
|
||||
var paymentMethod = InvoiceWatcher.GetNearestClearedPayment(paymentMethods, out var accounting2);
|
||||
Assert.Equal(btc.CryptoCode, paymentMethod.CryptoCode);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
@ -216,7 +214,7 @@ namespace BTCPayServer.Tests
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
});
|
||||
var entity = new InvoiceEntity() {PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
|
||||
var entity = new InvoiceEntity();
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
@ -234,8 +232,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Output = new TxOut(Money.Coins(0.5m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
@ -247,8 +244,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
@ -259,39 +255,48 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Output = new TxOut(Money.Coins(0.6m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Accounted = true,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
});
|
||||
entity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Accounted = true
|
||||
});
|
||||
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity = new InvoiceEntity() {PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
|
||||
entity = new InvoiceEntity();
|
||||
entity.ProductInformation = new ProductInformation() {Price = 5000};
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() {CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m)});
|
||||
new PaymentMethod()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
Rate = 1000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
});
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() {CryptoCode = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m)});
|
||||
new PaymentMethod()
|
||||
{
|
||||
CryptoCode = "LTC",
|
||||
Rate = 500,
|
||||
NextNetworkFee = Money.Coins(0.01m)
|
||||
});
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Coins(5.1m), accounting.Due);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
|
||||
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
|
||||
@ -301,11 +306,10 @@ namespace BTCPayServer.Tests
|
||||
CryptoCode = "BTC",
|
||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Coins(4.2m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -313,7 +317,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Coins(5.2m), accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due);
|
||||
Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid);
|
||||
@ -325,11 +329,10 @@ namespace BTCPayServer.Tests
|
||||
CryptoCode = "LTC",
|
||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.01m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.01m
|
||||
});
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -337,7 +340,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -351,11 +354,10 @@ namespace BTCPayServer.Tests
|
||||
CryptoCode = "BTC",
|
||||
Output = new TxOut(remaining, new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m,
|
||||
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
|
||||
NetworkFee = 0.1m
|
||||
});
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid);
|
||||
@ -364,7 +366,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike), null);
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -377,29 +379,6 @@ namespace BTCPayServer.Tests
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task GetRedirectedToLoginPathOnChallenge()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var client = tester.PayTester.HttpClient;
|
||||
//Wallets endpoint is protected
|
||||
var response = await client.GetAsync("wallets");
|
||||
var urlPath = response.RequestMessage.RequestUri.ToString()
|
||||
.Replace(tester.PayTester.ServerUri.ToString(), "");
|
||||
//Cookie Challenge redirects you to login page
|
||||
Assert.StartsWith("Account/Login", urlPath, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
var queryString = response.RequestMessage.RequestUri.ParseQueryString();
|
||||
|
||||
Assert.NotNull(queryString["ReturnUrl"]);
|
||||
Assert.Equal("/wallets", queryString["ReturnUrl"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseTestWebsiteUI()
|
||||
@ -407,8 +386,7 @@ namespace BTCPayServer.Tests
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var http = new HttpClient();
|
||||
var response = await http.GetAsync(tester.PayTester.ServerUri);
|
||||
var response = await tester.PayTester.HttpClient.GetAsync("");
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
}
|
||||
}
|
||||
@ -423,7 +401,7 @@ namespace BTCPayServer.Tests
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
});
|
||||
var entity = new InvoiceEntity() {PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
|
||||
var entity = new InvoiceEntity();
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
@ -749,7 +727,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanRescanWallet()
|
||||
public async Task CanRescanWallet()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
@ -811,6 +789,32 @@ namespace BTCPayServer.Tests
|
||||
transactions = Assert.IsType<ListTransactionsViewModel>(Assert.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
var tx = Assert.Single(transactions.Transactions);
|
||||
Assert.Equal(tx.Id, txId.ToString());
|
||||
|
||||
// Hijack the test to see if we can add label and comments
|
||||
Assert.IsType<RedirectToActionResult>(await walletController.ModifyTransaction(walletId, tx.Id, addlabel: "test"));
|
||||
Assert.IsType<RedirectToActionResult>(await walletController.ModifyTransaction(walletId, tx.Id, addlabelclick: "test2"));
|
||||
Assert.IsType<RedirectToActionResult>(await walletController.ModifyTransaction(walletId, tx.Id, addcomment: "hello"));
|
||||
|
||||
transactions = Assert.IsType<ListTransactionsViewModel>(Assert.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Value));
|
||||
Assert.Contains("test2", tx.Labels.Select(l => l.Value));
|
||||
Assert.Equal(2, tx.Labels.GroupBy(l => l.Color).Count());
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(await walletController.ModifyTransaction(walletId, tx.Id, removelabel: "test2"));
|
||||
|
||||
transactions = Assert.IsType<ListTransactionsViewModel>(Assert.IsType<ViewResult>(walletController.WalletTransactions(walletId).Result).Model);
|
||||
tx = Assert.Single(transactions.Transactions);
|
||||
|
||||
Assert.Equal("hello", tx.Comment);
|
||||
Assert.Contains("test", tx.Labels.Select(l => l.Value));
|
||||
Assert.DoesNotContain("test2", tx.Labels.Select(l => l.Value));
|
||||
Assert.Single(tx.Labels.GroupBy(l => l.Color));
|
||||
|
||||
var walletInfo = await tester.PayTester.GetService<WalletRepository>().GetWalletInfo(walletId);
|
||||
Assert.Single(walletInfo.LabelColors); // the test2 color should have been removed
|
||||
}
|
||||
}
|
||||
|
||||
@ -2666,27 +2670,28 @@ donation:
|
||||
{
|
||||
var unusedUri = new Uri("https://toto.com");
|
||||
Assert.True(ExternalConnectionString.TryParse("server=/test", out var connStr, out var error));
|
||||
var expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge);
|
||||
var expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
Assert.Equal(new Uri("https://toto.com/test"), expanded.Server);
|
||||
expanded = await connStr.Expand(new Uri("http://toto.onion"), ExternalServiceTypes.Charge);
|
||||
expanded = await connStr.Expand(new Uri("http://toto.onion"), ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
Assert.Equal(new Uri("http://toto.onion/test"), expanded.Server);
|
||||
await Assert.ThrowsAsync<SecurityException>(() => connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge));
|
||||
await Assert.ThrowsAsync<SecurityException>(() => connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Mainnet));
|
||||
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Testnet);
|
||||
|
||||
// Make sure absolute paths are not expanded
|
||||
Assert.True(ExternalConnectionString.TryParse("server=https://tow/test", out connStr, out error));
|
||||
expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge);
|
||||
expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
Assert.Equal(new Uri("https://tow/test"), expanded.Server);
|
||||
|
||||
// Error if directory not exists
|
||||
Assert.True(ExternalConnectionString.TryParse($"server={unusedUri};macaroondirectorypath=pouet", out connStr, out error));
|
||||
await Assert.ThrowsAsync<DirectoryNotFoundException>(() => connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC));
|
||||
await Assert.ThrowsAsync<DirectoryNotFoundException>(() => connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest));
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge);
|
||||
await Assert.ThrowsAsync<DirectoryNotFoundException>(() => connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet));
|
||||
await Assert.ThrowsAsync<DirectoryNotFoundException>(() => connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet));
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
|
||||
var macaroonDirectory = CreateDirectory();
|
||||
Assert.True(ExternalConnectionString.TryParse($"server={unusedUri};macaroondirectorypath={macaroonDirectory}", out connStr, out error));
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest);
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
|
||||
Assert.NotNull(expanded.Macaroons);
|
||||
Assert.Null(expanded.MacaroonFilePath);
|
||||
Assert.Null(expanded.Macaroons.AdminMacaroon);
|
||||
@ -2696,7 +2701,7 @@ donation:
|
||||
File.WriteAllBytes($"{macaroonDirectory}/admin.macaroon", new byte[] { 0xaa });
|
||||
File.WriteAllBytes($"{macaroonDirectory}/invoice.macaroon", new byte[] { 0xab });
|
||||
File.WriteAllBytes($"{macaroonDirectory}/readonly.macaroon", new byte[] { 0xac });
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
|
||||
Assert.NotNull(expanded.Macaroons.AdminMacaroon);
|
||||
Assert.NotNull(expanded.Macaroons.InvoiceMacaroon);
|
||||
Assert.Equal("ab", expanded.Macaroons.InvoiceMacaroon.Hex);
|
||||
@ -2705,7 +2710,7 @@ donation:
|
||||
|
||||
Assert.True(ExternalConnectionString.TryParse($"server={unusedUri};cookiefilepath={macaroonDirectory}/charge.cookie", out connStr, out error));
|
||||
File.WriteAllText($"{macaroonDirectory}/charge.cookie", "apitoken");
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
Assert.Equal("apitoken", expanded.APIToken);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using System.IO;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
|
@ -67,11 +67,8 @@ services:
|
||||
- mysql
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
|
||||
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.0.0.48
|
||||
image: nicolasdorier/nbxplorer:2.0.0.55
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -227,7 +224,7 @@ services:
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.6-beta
|
||||
image: btcpayserver/lnd:v0.7.0-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
@ -258,7 +255,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.6-beta
|
||||
image: btcpayserver/lnd:v0.7.0-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
|
@ -0,0 +1,21 @@
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class AuthorizationCodeGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn
|
||||
{
|
||||
public AuthorizationCodeGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
identityOptions, userManager)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool IsValid(OpenIdConnectRequest request)
|
||||
{
|
||||
return request.IsAuthorizationCodeGrantType();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class AuthorizationEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleAuthorizationRequest>
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(
|
||||
OpenIddictServerEvents.HandleAuthorizationRequest notification)
|
||||
{
|
||||
if (!notification.Context.Request.IsAuthorizationRequest())
|
||||
{
|
||||
return OpenIddictServerEventState.Unhandled;
|
||||
}
|
||||
|
||||
var auth = await notification.Context.HttpContext.AuthenticateAsync();
|
||||
if (!auth.Succeeded)
|
||||
{
|
||||
// If the client application request promptless authentication,
|
||||
// return an error indicating that the user is not logged in.
|
||||
if (notification.Context.Request.HasPrompt(OpenIdConnectConstants.Prompts.None))
|
||||
{
|
||||
var properties = new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIdConnectConstants.Properties.Error] = OpenIdConnectConstants.Errors.LoginRequired,
|
||||
[OpenIdConnectConstants.Properties.ErrorDescription] = "The user is not logged in."
|
||||
});
|
||||
|
||||
|
||||
// Ask OpenIddict to return a login_required error to the client application.
|
||||
await notification.Context.HttpContext.ForbidAsync(properties);
|
||||
notification.Context.HandleResponse();
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
await notification.Context.HttpContext.ChallengeAsync();
|
||||
notification.Context.HandleResponse();
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
// Retrieve the profile of the logged in user.
|
||||
var user = await _userManager.GetUserAsync(auth.Principal);
|
||||
if (user == null)
|
||||
{
|
||||
notification.Context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "An internal error has occurred");
|
||||
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
// Create a new authentication ticket.
|
||||
var ticket = await CreateTicketAsync(notification.Context.Request, user);
|
||||
|
||||
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
|
||||
notification.Context.Validate(ticket);
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
public AuthorizationEventHandler(
|
||||
UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions) : base(signInManager, identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
}
|
||||
}
|
104
BTCPayServer/Authentication/OpenId/BaseOpenIdGrantHandler.cs
Normal file
104
BTCPayServer/Authentication/OpenId/BaseOpenIdGrantHandler.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Extensions;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public abstract class BaseOpenIdGrantHandler<T> : IOpenIddictServerEventHandler<T>
|
||||
where T : class, IOpenIddictServerEvent
|
||||
{
|
||||
protected readonly SignInManager<ApplicationUser> _signInManager;
|
||||
protected readonly IOptions<IdentityOptions> _identityOptions;
|
||||
|
||||
protected BaseOpenIdGrantHandler(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_identityOptions = identityOptions;
|
||||
}
|
||||
|
||||
protected async Task<AuthenticationTicket> CreateTicketAsync(
|
||||
OpenIdConnectRequest request, ApplicationUser user,
|
||||
AuthenticationProperties properties = null)
|
||||
{
|
||||
// Create a new ClaimsPrincipal containing the claims that
|
||||
// will be used to create an id_token, a token or a code.
|
||||
var principal = await _signInManager.CreateUserPrincipalAsync(user);
|
||||
|
||||
// Create a new authentication ticket holding the user identity.
|
||||
var ticket = new AuthenticationTicket(principal, properties,
|
||||
OpenIddictServerDefaults.AuthenticationScheme);
|
||||
|
||||
if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
|
||||
{
|
||||
// Note: in this sample, the granted scopes match the requested scope
|
||||
// but you may want to allow the user to uncheck specific scopes.
|
||||
// For that, simply restrict the list of scopes before calling SetScopes.
|
||||
ticket.SetScopes(request.GetScopes());
|
||||
}
|
||||
|
||||
foreach (var claim in ticket.Principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(claim, ticket));
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetDestinations(Claim claim, AuthenticationTicket ticket)
|
||||
{
|
||||
// Note: by default, claims are NOT automatically included in the access and identity tokens.
|
||||
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
|
||||
// whether they should be included in access tokens, in identity tokens or in both.
|
||||
|
||||
|
||||
switch (claim.Type)
|
||||
{
|
||||
case OpenIddictConstants.Claims.Name:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Profile))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
|
||||
case OpenIddictConstants.Claims.Email:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Email))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
|
||||
case OpenIddictConstants.Claims.Role:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Roles))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
default:
|
||||
if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType)
|
||||
{
|
||||
// Never include the security stamp in the access and identity tokens, as it's a secret value.
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task<OpenIddictServerEventState> HandleAsync(T notification);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Extensions;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class
|
||||
ClientCredentialsGrantTypeEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleTokenRequest>
|
||||
{
|
||||
private readonly OpenIddictApplicationManager<BTCPayOpenIdClient> _applicationManager;
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public ClientCredentialsGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(
|
||||
OpenIddictServerEvents.HandleTokenRequest notification)
|
||||
{
|
||||
var request = notification.Context.Request;
|
||||
if (!request.IsClientCredentialsGrantType())
|
||||
{
|
||||
// Allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Unhandled;
|
||||
}
|
||||
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId,
|
||||
notification.Context.HttpContext.RequestAborted);
|
||||
if (application == null)
|
||||
{
|
||||
notification.Context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidClient,
|
||||
description: "The client application was not found in the database.");
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(application.ApplicationUserId);
|
||||
|
||||
notification.Context.Validate(await CreateTicketAsync(request, user));
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
}
|
||||
}
|
32
BTCPayServer/Authentication/OpenId/LogoutEventHandler.cs
Normal file
32
BTCPayServer/Authentication/OpenId/LogoutEventHandler.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
|
||||
public class LogoutEventHandler: BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleLogoutRequest>
|
||||
{
|
||||
public LogoutEventHandler(SignInManager<ApplicationUser> signInManager, IOptions<IdentityOptions> identityOptions) : base(signInManager, identityOptions)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.HandleLogoutRequest notification)
|
||||
{
|
||||
// Ask ASP.NET Core Identity to delete the local and external cookies created
|
||||
// when the user agent is redirected from the external identity provider
|
||||
// after a successful authentication flow (e.g Google or Facebook).
|
||||
await _signInManager.SignOutAsync();
|
||||
|
||||
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
|
||||
// to the post_logout_redirect_uri specified by the client application.
|
||||
await notification.Context.HttpContext.SignOutAsync(OpenIddictServerDefaults.AuthenticationScheme);
|
||||
notification.Context.HandleResponse();
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public abstract class
|
||||
OpenIdGrantHandlerCheckCanSignIn : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleTokenRequest>
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
protected OpenIdGrantHandlerCheckCanSignIn(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
protected abstract bool IsValid(OpenIdConnectRequest request);
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(
|
||||
OpenIddictServerEvents.HandleTokenRequest notification)
|
||||
{
|
||||
var request = notification.Context.Request;
|
||||
if (!IsValid(request))
|
||||
{
|
||||
// Allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Unhandled;
|
||||
}
|
||||
|
||||
var scheme = notification.Context.Scheme.Name;
|
||||
var authenticateResult = (await notification.Context.HttpContext.AuthenticateAsync(scheme));
|
||||
|
||||
|
||||
var user = await _userManager.GetUserAsync(authenticateResult.Principal);
|
||||
if (user == null)
|
||||
{
|
||||
notification.Context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "The token is no longer valid.");
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
// Ensure the user is still allowed to sign in.
|
||||
if (!await _signInManager.CanSignInAsync(user))
|
||||
{
|
||||
notification.Context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "The user is no longer allowed to sign in.");
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
notification.Context.Validate(await CreateTicketAsync(request, user));
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.U2F;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class PasswordGrantTypeEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleTokenRequest>
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly U2FService _u2FService;
|
||||
|
||||
public PasswordGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IOptions<IdentityOptions> identityOptions, U2FService u2FService) : base(signInManager, identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_u2FService = u2FService;
|
||||
}
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(
|
||||
OpenIddictServerEvents.HandleTokenRequest notification)
|
||||
{
|
||||
var request = notification.Context.Request;
|
||||
if (!request.IsPasswordGrantType())
|
||||
{
|
||||
// Allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Unhandled;
|
||||
}
|
||||
|
||||
// Validate the user credentials.
|
||||
// Note: to mitigate brute force attacks, you SHOULD strongly consider
|
||||
// applying a key derivation function like PBKDF2 to slow down
|
||||
// the password validation process. You SHOULD also consider
|
||||
// using a time-constant comparer to prevent timing attacks.
|
||||
var user = await _userManager.FindByNameAsync(request.Username);
|
||||
if (user == null || await _u2FService.HasDevices(user.Id) ||
|
||||
!(await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true))
|
||||
.Succeeded)
|
||||
{
|
||||
notification.Context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "The specified credentials are invalid.");
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
notification.Context.Validate(await CreateTicketAsync(request, user));
|
||||
// Don't allow other handlers to process the event.
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class RefreshTokenGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn
|
||||
{
|
||||
public RefreshTokenGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
identityOptions, userManager)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool IsValid(OpenIdConnectRequest request)
|
||||
{
|
||||
return request.IsRefreshTokenGrantType();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Version.csproj" Condition="Exists('../Version.csproj')" />
|
||||
<Import Project="../Common.csproj" />
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
@ -30,11 +30,11 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.19" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.23" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.9.406" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="2.9.406" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="2.9.406" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.207" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
||||
<PackageReference Include="LedgerWallet" Version="2.0.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
@ -47,7 +47,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.0.0.9" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.2" />
|
||||
<PackageReference Include="OpenIddict" Version="2.0.0" />
|
||||
@ -146,6 +146,9 @@
|
||||
<Content Update="Views\Home\BitpayTranslator.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\DynamicDnsServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LightningChargeServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
@ -155,6 +158,9 @@
|
||||
<Content Update="Views\Server\P2PService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\DynamicDnsService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
@ -30,13 +31,16 @@ namespace BTCPayServer.Configuration
|
||||
/// Return a connectionString which does not depends on external resources or information like relative path or file path
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType)
|
||||
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, NetworkType network)
|
||||
{
|
||||
var connectionString = this.Clone();
|
||||
// Transform relative URI into absolute URI
|
||||
var serviceUri = connectionString.Server.IsAbsoluteUri ? connectionString.Server : ToRelative(absoluteUrlBase, connectionString.Server.ToString());
|
||||
if (!serviceUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) &&
|
||||
!serviceUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase))
|
||||
var isSecure = network != NetworkType.Mainnet ||
|
||||
serviceUri.Scheme == "https" ||
|
||||
serviceUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase) ||
|
||||
Extensions.IsLocalNetwork(serviceUri.DnsSafeHost);
|
||||
if (!isSecure)
|
||||
{
|
||||
throw new System.Security.SecurityException($"Insecure transport protocol to access this service, please use HTTPS or TOR");
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ namespace BTCPayServer.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(string returnUrl = null)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated && string.IsNullOrEmpty(returnUrl))
|
||||
return RedirectToLocal();
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
@ -181,7 +183,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Version = u2fChallenge[0].version,
|
||||
Challenge = u2fChallenge[0].challenge,
|
||||
Challenges = JsonConvert.SerializeObject(u2fChallenge),
|
||||
Challenges = u2fChallenge,
|
||||
AppId = u2fChallenge[0].appId,
|
||||
UserId = user.Id,
|
||||
RememberMe = rememberMe
|
||||
@ -645,9 +647,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult RedirectToLocal(string returnUrl)
|
||||
private IActionResult RedirectToLocal(string returnUrl = null)
|
||||
{
|
||||
if (Url.IsLocalUrl(returnUrl))
|
||||
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
|
||||
{
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
NotificationEmailWarning = !await IsEmailConfigured(app.StoreDataId),
|
||||
Title = settings.Title,
|
||||
StoreId = app.StoreDataId,
|
||||
Enabled = settings.Enabled,
|
||||
EnforceTargetAmount = settings.EnforceTargetAmount,
|
||||
StartDate = settings.StartDate,
|
||||
@ -131,7 +132,7 @@ namespace BTCPayServer.Controllers
|
||||
EnforceTargetAmount = vm.EnforceTargetAmount,
|
||||
StartDate = vm.StartDate?.ToUniversalTime(),
|
||||
TargetCurrency = vm.TargetCurrency,
|
||||
Description = _htmlSanitizer.Sanitize( vm.Description),
|
||||
Description = vm.Description,
|
||||
EndDate = vm.EndDate?.ToUniversalTime(),
|
||||
TargetAmount = vm.TargetAmount,
|
||||
CustomCSSLink = vm.CustomCSSLink,
|
||||
|
@ -96,6 +96,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
NotificationEmailWarning = !await IsEmailConfigured(app.StoreDataId),
|
||||
Id = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
Title = settings.Title,
|
||||
EnableShoppingCart = settings.EnableShoppingCart,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
@ -191,7 +192,7 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
await UpdateAppSettings(app);
|
||||
StatusMessage = "App updated";
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
|
||||
}
|
||||
|
||||
private async Task UpdateAppSettings(AppData app)
|
||||
|
@ -30,7 +30,6 @@ namespace BTCPayServer.Controllers
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
CurrencyNameTable currencies,
|
||||
HtmlSanitizer htmlSanitizer,
|
||||
EmailSenderFactory emailSenderFactory,
|
||||
AppService AppService)
|
||||
{
|
||||
@ -39,7 +38,6 @@ namespace BTCPayServer.Controllers
|
||||
_EventAggregator = eventAggregator;
|
||||
_NetworkProvider = networkProvider;
|
||||
_currencies = currencies;
|
||||
_htmlSanitizer = htmlSanitizer;
|
||||
_emailSenderFactory = emailSenderFactory;
|
||||
_AppService = AppService;
|
||||
}
|
||||
@ -49,7 +47,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
private BTCPayNetworkProvider _NetworkProvider;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly HtmlSanitizer _htmlSanitizer;
|
||||
private readonly EmailSenderFactory _emailSenderFactory;
|
||||
private AppService _AppService;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Models;
|
||||
@ -10,6 +11,7 @@ using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -25,37 +27,58 @@ namespace BTCPayServer.Controllers
|
||||
_cachedServerSettings = cachedServerSettings;
|
||||
}
|
||||
|
||||
private async Task<ViewResult> GoToApp(string appId, AppType? appType)
|
||||
{
|
||||
if (appType.HasValue && !string.IsNullOrEmpty(appId))
|
||||
{
|
||||
switch (appType.Value)
|
||||
{
|
||||
case AppType.Crowdfund:
|
||||
{
|
||||
var serviceProvider = HttpContext.RequestServices;
|
||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||
controller.Url = Url;
|
||||
controller.ControllerContext = ControllerContext;
|
||||
var res = await controller.ViewCrowdfund(appId, null) as ViewResult;
|
||||
if (res != null)
|
||||
{
|
||||
res.ViewName = "/Views/AppsPublic/ViewCrowdfund.cshtml";
|
||||
return res; // return
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AppType.PointOfSale:
|
||||
{
|
||||
var serviceProvider = HttpContext.RequestServices;
|
||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||
controller.Url = Url;
|
||||
controller.ControllerContext = ControllerContext;
|
||||
var res = await controller.ViewPointOfSale(appId) as ViewResult;
|
||||
if (res != null)
|
||||
{
|
||||
res.ViewName = "/Views/AppsPublic/ViewPointOfSale.cshtml";
|
||||
return res; // return
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
if (_cachedServerSettings.RootAppType is Services.Apps.AppType.Crowdfund)
|
||||
var matchedDomainMapping = _cachedServerSettings.DomainToAppMapping.FirstOrDefault(item =>
|
||||
item.Domain.Equals(Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (matchedDomainMapping != null)
|
||||
{
|
||||
var serviceProvider = HttpContext.RequestServices;
|
||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||
controller.Url = Url;
|
||||
controller.ControllerContext = ControllerContext;
|
||||
var res = await controller.ViewCrowdfund(_cachedServerSettings.RootAppId, null) as ViewResult;
|
||||
if (res != null)
|
||||
{
|
||||
res.ViewName = "/Views/AppsPublic/ViewCrowdfund.cshtml";
|
||||
return res; // return
|
||||
}
|
||||
}
|
||||
else if (_cachedServerSettings.RootAppType is Services.Apps.AppType.PointOfSale)
|
||||
{
|
||||
var serviceProvider = HttpContext.RequestServices;
|
||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||
controller.Url = Url;
|
||||
controller.ControllerContext = ControllerContext;
|
||||
var res = await controller.ViewPointOfSale(_cachedServerSettings.RootAppId) as ViewResult;
|
||||
if (res != null)
|
||||
{
|
||||
res.ViewName = "/Views/AppsPublic/ViewPointOfSale.cshtml";
|
||||
return res; // return
|
||||
}
|
||||
|
||||
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? View("Home");
|
||||
}
|
||||
|
||||
return View("Home");
|
||||
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? View("Home");
|
||||
}
|
||||
|
||||
[Route("translate")]
|
||||
@ -116,20 +139,6 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
public IActionResult About()
|
||||
{
|
||||
ViewData["Message"] = "Your application description page.";
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Contact()
|
||||
{
|
||||
ViewData["Message"] = "Your contact page.";
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
|
@ -111,23 +111,18 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
foreach (var payment in invoice.GetPayments())
|
||||
{
|
||||
var paymentNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(payment.GetCryptoCode());
|
||||
if (paymentNetwork == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var paymentData = payment.GetCryptoPaymentData();
|
||||
//TODO: abstract
|
||||
if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData)
|
||||
{
|
||||
var m = new InvoiceDetailsModel.Payment();
|
||||
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
||||
m.DepositAddress = onChainPaymentData.GetDestination(paymentNetwork);
|
||||
m.DepositAddress = onChainPaymentData.GetDestination();
|
||||
|
||||
int confirmationCount = onChainPaymentData.ConfirmationCount;
|
||||
if (confirmationCount >= paymentNetwork.MaxTrackedConfirmation)
|
||||
if (confirmationCount >= payment.Network.MaxTrackedConfirmation)
|
||||
{
|
||||
m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation);
|
||||
m.Confirmations = "At least " + (payment.Network.MaxTrackedConfirmation);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -136,7 +131,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId);
|
||||
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, payment.Network.BlockExplorerLink, m.TransactionId);
|
||||
m.Replaced = !payment.Accounted;
|
||||
model.OnChainPayments.Add(m);
|
||||
}
|
||||
@ -145,7 +140,7 @@ namespace BTCPayServer.Controllers
|
||||
var lightningPaymentData = (LightningLikePaymentData)paymentData;
|
||||
model.OffChainPayments.Add(new InvoiceDetailsModel.OffChainPayment()
|
||||
{
|
||||
Crypto = paymentNetwork.CryptoCode,
|
||||
Crypto = payment.Network.CryptoCode,
|
||||
BOLT11 = lightningPaymentData.BOLT11
|
||||
});
|
||||
}
|
||||
@ -242,7 +237,7 @@ namespace BTCPayServer.Controllers
|
||||
paymentMethodId = paymentMethodTemp.GetId();
|
||||
}
|
||||
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId, _NetworkProvider);
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
var dto = invoice.EntityToDTO();
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
@ -266,7 +261,8 @@ namespace BTCPayServer.Controllers
|
||||
(1m + (changelly.AmountMarkupPercentage / 100m)))
|
||||
: (decimal?)null;
|
||||
|
||||
var paymentMethodHandler = invoice.PaymentMethodHandlerDictionary[paymentMethodId];
|
||||
|
||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
CryptoCode = network.CryptoCode,
|
||||
@ -314,8 +310,7 @@ namespace BTCPayServer.Controllers
|
||||
.Select(kv =>
|
||||
{
|
||||
var availableCryptoPaymentMethodId = kv.GetId();
|
||||
var availableCryptoHandler =
|
||||
invoice.PaymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
|
||||
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
|
||||
return new PaymentModel.AvailableCrypto()
|
||||
{
|
||||
PaymentMethodId = kv.GetId().ToString(),
|
||||
@ -499,7 +494,7 @@ namespace BTCPayServer.Controllers
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> Export(string format, string searchTerm = null, int timezoneOffset = 0)
|
||||
{
|
||||
var model = new InvoiceExport(_NetworkProvider, _CurrencyNameTable);
|
||||
var model = new InvoiceExport(_CurrencyNameTable);
|
||||
|
||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
|
||||
invoiceQuery.Skip = 0;
|
||||
|
@ -67,6 +67,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!store.HasClaim(Policies.CanCreateInvoice.Key))
|
||||
throw new UnauthorizedAccessException();
|
||||
invoice.Currency = invoice.Currency?.ToUpperInvariant() ?? "USD";
|
||||
InvoiceLogs logs = new InvoiceLogs();
|
||||
logs.Write("Creation of invoice starting");
|
||||
var entity = _InvoiceRepository.CreateNewInvoice();
|
||||
@ -158,7 +159,7 @@ namespace BTCPayServer.Controllers
|
||||
var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken);
|
||||
var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair);
|
||||
var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId))
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId) && _paymentMethodHandlerDictionary.Support(s.PaymentId))
|
||||
.Select(c =>
|
||||
(Handler: _paymentMethodHandlerDictionary[c.PaymentId],
|
||||
SupportedPaymentMethod: c,
|
||||
@ -256,7 +257,7 @@ namespace BTCPayServer.Controllers
|
||||
paymentMethod.Network = network;
|
||||
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
|
||||
paymentMethod.Rate = rate.BidAsk.Bid;
|
||||
paymentMethod.PreferOnion = this.Request.IsOnion();
|
||||
paymentMethod.PreferOnion = Uri.TryCreate(entity.ServerUrl, UriKind.Absolute, out var u) && u.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
using (logs.Measure($"{logPrefix} Payment method details creation"))
|
||||
{
|
||||
@ -268,7 +269,7 @@ namespace BTCPayServer.Controllers
|
||||
handler
|
||||
.IsPaymentMethodAllowedBasedOnInvoiceAmount(storeBlob, fetchingByCurrencyPair,
|
||||
paymentMethod.Calculate().Due, supportedPaymentMethod.PaymentId);
|
||||
if (errorMessage != null)
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
logs.Write($"{logPrefix} {errorMessage}");
|
||||
return null;
|
||||
|
@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.ManageViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -11,6 +12,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class ManageController
|
||||
{
|
||||
private const string RecoveryCodesKey = nameof(RecoveryCodesKey);
|
||||
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
|
||||
[HttpGet]
|
||||
@ -80,18 +82,8 @@ namespace BTCPayServer.Controllers
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
if (string.IsNullOrEmpty(unformattedKey))
|
||||
{
|
||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
||||
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
}
|
||||
|
||||
var model = new EnableAuthenticatorViewModel
|
||||
{
|
||||
SharedKey = FormatKey(unformattedKey),
|
||||
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey)
|
||||
};
|
||||
var model = new EnableAuthenticatorViewModel();
|
||||
await LoadSharedKeyAndQrCodeUriAsync(user, model);
|
||||
|
||||
return View(model);
|
||||
}
|
||||
@ -100,32 +92,36 @@ namespace BTCPayServer.Controllers
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
await LoadSharedKeyAndQrCodeUriAsync(user, model);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// Strip spaces and hypens
|
||||
var verificationCode = model.Code.Replace(" ", string.Empty, StringComparison.InvariantCulture)
|
||||
.Replace("-", string.Empty, StringComparison.InvariantCulture);
|
||||
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
|
||||
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
|
||||
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
|
||||
|
||||
if (!is2faTokenValid)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.Code), "Verification code is invalid.");
|
||||
ModelState.AddModelError("Code", "Verification code is invalid.");
|
||||
await LoadSharedKeyAndQrCodeUriAsync(user, model);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _userManager.SetTwoFactorEnabledAsync(user, true);
|
||||
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
TempData[RecoveryCodesKey] = recoveryCodes.ToArray();
|
||||
|
||||
return RedirectToAction(nameof(GenerateRecoveryCodes));
|
||||
}
|
||||
|
||||
@ -153,7 +149,20 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateRecoveryCodes()
|
||||
public IActionResult GenerateRecoveryCodes()
|
||||
{
|
||||
var recoveryCodes = (string[])TempData[RecoveryCodesKey];
|
||||
if (recoveryCodes == null)
|
||||
{
|
||||
return RedirectToAction(nameof(TwoFactorAuthentication));
|
||||
}
|
||||
|
||||
var model = new GenerateRecoveryCodesViewModel {RecoveryCodes = recoveryCodes};
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateRecoveryCodesWarning()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
@ -163,16 +172,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException(
|
||||
$"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
|
||||
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled.");
|
||||
}
|
||||
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
var model = new GenerateRecoveryCodesViewModel {RecoveryCodes = recoveryCodes.ToArray()};
|
||||
|
||||
_logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);
|
||||
|
||||
return View(model);
|
||||
return View(nameof(GenerateRecoveryCodesWarning));
|
||||
}
|
||||
|
||||
private string GenerateQrCodeUri(string email, string unformattedKey)
|
||||
@ -201,5 +204,19 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
return result.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
|
||||
private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user, EnableAuthenticatorViewModel model)
|
||||
{
|
||||
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
if (string.IsNullOrEmpty(unformattedKey))
|
||||
{
|
||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
||||
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
}
|
||||
|
||||
model.SharedKey = FormatKey(unformattedKey);
|
||||
model.AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ namespace BTCPayServer.Controllers
|
||||
private readonly PaymentRequestService _PaymentRequestService;
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
private readonly CurrencyNameTable _Currencies;
|
||||
private readonly HtmlSanitizer _htmlSanitizer;
|
||||
private readonly InvoiceRepository _InvoiceRepository;
|
||||
|
||||
public PaymentRequestController(
|
||||
@ -52,7 +51,6 @@ namespace BTCPayServer.Controllers
|
||||
PaymentRequestService paymentRequestService,
|
||||
EventAggregator eventAggregator,
|
||||
CurrencyNameTable currencies,
|
||||
HtmlSanitizer htmlSanitizer,
|
||||
InvoiceRepository invoiceRepository)
|
||||
{
|
||||
_InvoiceController = invoiceController;
|
||||
@ -62,7 +60,6 @@ namespace BTCPayServer.Controllers
|
||||
_PaymentRequestService = paymentRequestService;
|
||||
_EventAggregator = eventAggregator;
|
||||
_Currencies = currencies;
|
||||
_htmlSanitizer = htmlSanitizer;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
}
|
||||
|
||||
@ -152,7 +149,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
blob.Title = viewModel.Title;
|
||||
blob.Email = viewModel.Email;
|
||||
blob.Description = _htmlSanitizer.Sanitize(viewModel.Description);
|
||||
blob.Description = viewModel.Description;
|
||||
blob.Amount = viewModel.Amount;
|
||||
blob.ExpiryDate = viewModel.ExpiryDate?.ToUniversalTime();
|
||||
blob.Currency = viewModel.Currency;
|
||||
@ -161,11 +158,15 @@ namespace BTCPayServer.Controllers
|
||||
blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts;
|
||||
|
||||
data.SetBlob(blob);
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
data.Created = DateTimeOffset.UtcNow;
|
||||
}
|
||||
data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data);
|
||||
_EventAggregator.Publish(new PaymentRequestUpdated()
|
||||
{
|
||||
Data = data,
|
||||
PaymentRequestId = data.Id
|
||||
PaymentRequestId = data.Id,
|
||||
});
|
||||
|
||||
return RedirectToAction("EditPaymentRequest", new {id = data.Id, StatusMessage = "Saved"});
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
@ -58,7 +59,17 @@ namespace BTCPayServer.Controllers
|
||||
RedirectURL = model.BrowserRedirect,
|
||||
FullNotifications = true
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
|
||||
return Redirect(invoice.Data.Url);
|
||||
if (string.IsNullOrEmpty(model.CheckoutQueryString))
|
||||
{
|
||||
return Redirect(invoice.Data.Url);
|
||||
}
|
||||
|
||||
var additionalParamValues = HttpUtility.ParseQueryString(model.CheckoutQueryString);
|
||||
var uriBuilder = new UriBuilder(invoice.Data.Url);
|
||||
var paramValues = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
paramValues.Add(additionalParamValues);
|
||||
uriBuilder.Query = paramValues.ToString();
|
||||
return Redirect(uriBuilder.Uri.AbsoluteUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
BTCPayServer/Controllers/RestApi/TestController.cs
Normal file
61
BTCPayServer/Controllers/RestApi/TestController.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenIddict.Validation;
|
||||
|
||||
namespace BTCPayServer.Controllers.RestApi
|
||||
{
|
||||
/// <summary>
|
||||
/// this controller serves as a testing endpoint for our OpenId unit tests
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)]
|
||||
public class TestController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public TestController(UserManager<ApplicationUser> userManager, StoreRepository storeRepository)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
[HttpGet("me/id")]
|
||||
public string GetCurrentUserId()
|
||||
{
|
||||
return _userManager.GetUserId(User);
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
public async Task<ApplicationUser> GetCurrentUser()
|
||||
{
|
||||
return await _userManager.GetUserAsync(User);
|
||||
}
|
||||
|
||||
[HttpGet("me/is-admin")]
|
||||
public bool AmIAnAdmin()
|
||||
{
|
||||
return User.IsInRole(Roles.ServerAdmin);
|
||||
}
|
||||
[HttpGet("me/stores")]
|
||||
public async Task<StoreData[]> GetCurrentUserStores()
|
||||
{
|
||||
return await _storeRepository.GetStoresByUserId(_userManager.GetUserId(User));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("me/stores/{storeId}/can-edit")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)]
|
||||
public bool CanEdit(string storeId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -235,6 +235,12 @@ namespace BTCPayServer.Controllers
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
|
||||
case FileSystemFileProviderService fileProviderService:
|
||||
if (data.Provider != BTCPayServer.Storage.Models.StorageProvider.FileSystem)
|
||||
{
|
||||
_ = await SaveStorageProvider(new FileSystemStorageConfiguration(),
|
||||
BTCPayServer.Storage.Models.StorageProvider.FileSystem);
|
||||
}
|
||||
|
||||
return View(nameof(EditFileSystemStorageProvider),
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using NBitcoin.DataEncoders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -33,6 +34,7 @@ using BTCPayServer.Storage.Services.Providers;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -42,12 +44,11 @@ namespace BTCPayServer.Controllers
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
private readonly NBXplorerDashboard _dashBoard;
|
||||
private RateFetcher _RateProviderFactory;
|
||||
private StoreRepository _StoreRepository;
|
||||
LightningConfigurationProvider _LnConfigProvider;
|
||||
private readonly TorServices _torServices;
|
||||
BTCPayServerOptions _Options;
|
||||
ApplicationDbContextFactory _ContextFactory;
|
||||
private BTCPayServerOptions _Options;
|
||||
private readonly AppService _AppService;
|
||||
private readonly StoredFileRepository _StoredFileRepository;
|
||||
private readonly FileService _FileService;
|
||||
private readonly IEnumerable<IStorageProviderService> _StorageProviderServices;
|
||||
@ -57,14 +58,13 @@ namespace BTCPayServer.Controllers
|
||||
FileService fileService,
|
||||
IEnumerable<IStorageProviderService> storageProviderServices,
|
||||
BTCPayServerOptions options,
|
||||
RateFetcher rateProviderFactory,
|
||||
SettingsRepository settingsRepository,
|
||||
NBXplorerDashboard dashBoard,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
LightningConfigurationProvider lnConfigProvider,
|
||||
TorServices torServices,
|
||||
StoreRepository storeRepository,
|
||||
ApplicationDbContextFactory contextFactory)
|
||||
AppService appService)
|
||||
{
|
||||
_Options = options;
|
||||
_StoredFileRepository = storedFileRepository;
|
||||
@ -74,11 +74,10 @@ namespace BTCPayServer.Controllers
|
||||
_SettingsRepository = settingsRepository;
|
||||
_dashBoard = dashBoard;
|
||||
HttpClientFactory = httpClientFactory;
|
||||
_RateProviderFactory = rateProviderFactory;
|
||||
_StoreRepository = storeRepository;
|
||||
_LnConfigProvider = lnConfigProvider;
|
||||
_torServices = torServices;
|
||||
_ContextFactory = contextFactory;
|
||||
_AppService = appService;
|
||||
}
|
||||
|
||||
[Route("server/rates")]
|
||||
@ -152,17 +151,20 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[Route("server/users")]
|
||||
public IActionResult ListUsers()
|
||||
public IActionResult ListUsers(int skip = 0, int count = 50)
|
||||
{
|
||||
var users = new UsersViewModel();
|
||||
users.StatusMessage = StatusMessage;
|
||||
users.Users
|
||||
= _UserManager.Users.Select(u => new UsersViewModel.UserViewModel()
|
||||
users.Users = _UserManager.Users.Skip(skip).Take(count)
|
||||
.Select(u => new UsersViewModel.UserViewModel
|
||||
{
|
||||
Name = u.UserName,
|
||||
Email = u.Email,
|
||||
Id = u.Id
|
||||
}).ToList();
|
||||
users.Skip = skip;
|
||||
users.Count = count;
|
||||
users.Total = _UserManager.Users.Count();
|
||||
return View(users);
|
||||
}
|
||||
|
||||
@ -459,43 +461,58 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> Policies()
|
||||
{
|
||||
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||
|
||||
// load display app dropdown
|
||||
using (var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
var userId = _UserManager.GetUserId(base.User);
|
||||
var selectList = ctx.Users.Where(user => user.Id == userId)
|
||||
.SelectMany(s => s.UserStores)
|
||||
.Select(s => s.StoreData)
|
||||
.SelectMany(s => s.Apps)
|
||||
.Select(a => new SelectListItem($"{a.AppType} - {a.Name}", a.Id)).ToList();
|
||||
selectList.Insert(0, new SelectListItem("(None)", null));
|
||||
ViewBag.AppsList = new SelectList(selectList, "Value", "Text", data.RootAppId);
|
||||
}
|
||||
|
||||
ViewBag.AppsList = await GetAppSelectList();
|
||||
return View(data);
|
||||
}
|
||||
|
||||
[Route("server/policies")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Policies(PoliciesSettings settings)
|
||||
public async Task<IActionResult> Policies(PoliciesSettings settings, string command = "")
|
||||
{
|
||||
if (!String.IsNullOrEmpty(settings.RootAppId))
|
||||
ViewBag.AppsList = await GetAppSelectList();
|
||||
if (command == "add-domain")
|
||||
{
|
||||
using (var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
var app = ctx.Apps.SingleOrDefault(a => a.Id == settings.RootAppId);
|
||||
if (app != null)
|
||||
settings.RootAppType = Enum.Parse<AppType>(app.AppType);
|
||||
else
|
||||
settings.RootAppType = null;
|
||||
}
|
||||
ModelState.Clear();
|
||||
settings.DomainToAppMapping.Add(new PoliciesSettings.DomainToAppMappingItem());
|
||||
return View(settings);
|
||||
}
|
||||
if (command.StartsWith("remove-domain", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ModelState.Clear();
|
||||
var index = int.Parse(command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture);
|
||||
settings.DomainToAppMapping.RemoveAt(index);
|
||||
return View(settings);
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(settings);
|
||||
}
|
||||
var appIdsToFetch = settings.DomainToAppMapping.Select(item => item.AppId).ToList();
|
||||
if (!string.IsNullOrEmpty(settings.RootAppId))
|
||||
{
|
||||
appIdsToFetch.Add(settings.RootAppId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not preserved on client side, but clearing it just in case
|
||||
settings.RootAppType = null;
|
||||
}
|
||||
|
||||
if (appIdsToFetch.Any())
|
||||
{
|
||||
var apps = (await _AppService.GetApps(appIdsToFetch.ToArray()))
|
||||
.ToDictionary(data => data.Id, data => Enum.Parse<AppType>(data.AppType));;
|
||||
if (!string.IsNullOrEmpty(settings.RootAppId))
|
||||
{
|
||||
settings.RootAppType = apps[settings.RootAppId];
|
||||
}
|
||||
|
||||
foreach (var domainToAppMappingItem in settings.DomainToAppMapping)
|
||||
{
|
||||
domainToAppMappingItem.AppType = apps[domainToAppMappingItem.AppId];
|
||||
}
|
||||
}
|
||||
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData["StatusMessage"] = "Policies updated successfully";
|
||||
return RedirectToAction(nameof(Policies));
|
||||
@ -522,6 +539,11 @@ namespace BTCPayServer.Controllers
|
||||
Link = this.Url.Action(nameof(SSHService))
|
||||
});
|
||||
}
|
||||
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
|
||||
{
|
||||
Name = "Dynamic DNS",
|
||||
Link = this.Url.Action(nameof(DynamicDnsServices))
|
||||
});
|
||||
foreach (var torService in _torServices.Services)
|
||||
{
|
||||
if (torService.VirtualPort == 80)
|
||||
@ -549,12 +571,20 @@ namespace BTCPayServer.Controllers
|
||||
var storageSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
result.ExternalStorageServices.Add(new ServicesViewModel.OtherExternalService()
|
||||
{
|
||||
Name = storageSettings == null? "Not set": storageSettings.Provider.ToString(),
|
||||
Name = storageSettings == null ? "Not set" : storageSettings.Provider.ToString(),
|
||||
Link = Url.Action("Storage")
|
||||
});
|
||||
return View(result);
|
||||
}
|
||||
|
||||
private async Task<List<SelectListItem>> GetAppSelectList()
|
||||
{
|
||||
var apps = (await _AppService.GetAllApps(null, true))
|
||||
.Select(a => new SelectListItem($"{a.AppType} - {a.AppName} - {a.StoreName}", a.Id)).ToList();
|
||||
apps.Insert(0, new SelectListItem("(None)", null));
|
||||
return apps;
|
||||
}
|
||||
|
||||
private static bool TryParseAsExternalService(TorService torService, out ExternalService externalService)
|
||||
{
|
||||
externalService = null;
|
||||
@ -604,7 +634,7 @@ namespace BTCPayServer.Controllers
|
||||
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
|
||||
});
|
||||
}
|
||||
var connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type);
|
||||
var connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type, _Options.NetworkType);
|
||||
switch (service.Type)
|
||||
{
|
||||
case ExternalServiceTypes.Charge:
|
||||
@ -720,7 +750,7 @@ namespace BTCPayServer.Controllers
|
||||
ExternalConnectionString connectionString = null;
|
||||
try
|
||||
{
|
||||
connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type);
|
||||
connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type, _Options.NetworkType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -762,6 +792,138 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(Service), new { cryptoCode = cryptoCode, serviceName = serviceName, nonce = nonce });
|
||||
}
|
||||
|
||||
|
||||
[Route("server/services/dynamic-dns")]
|
||||
public async Task<IActionResult> DynamicDnsServices()
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
return View(settings.Services.Select(s => new DynamicDnsViewModel()
|
||||
{
|
||||
Settings = s
|
||||
}).ToArray());
|
||||
}
|
||||
[Route("server/services/dynamic-dns/{hostname}")]
|
||||
public async Task<IActionResult> DynamicDnsServices(string hostname)
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
var service = settings.Services.FirstOrDefault(s => s.Hostname.Equals(hostname, StringComparison.OrdinalIgnoreCase));
|
||||
if (service == null)
|
||||
return NotFound();
|
||||
var vm = new DynamicDnsViewModel();
|
||||
vm.Modify = true;
|
||||
vm.Settings = service;
|
||||
return View(nameof(DynamicDnsService), vm);
|
||||
}
|
||||
[Route("server/services/dynamic-dns")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> DynamicDnsService(DynamicDnsViewModel viewModel, string command = null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
if (command == "Save")
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
var i = settings.Services.FindIndex(d => d.Hostname.Equals(viewModel.Settings.Hostname, StringComparison.OrdinalIgnoreCase));
|
||||
if (i != -1)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.Settings.Hostname), "This hostname already exists");
|
||||
return View(viewModel);
|
||||
}
|
||||
if (viewModel.Settings.Hostname != null)
|
||||
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
|
||||
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
|
||||
if (errorMessage == null)
|
||||
{
|
||||
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
|
||||
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
|
||||
settings.Services.Add(viewModel.Settings);
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
return RedirectToAction(nameof(DynamicDnsServices));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, errorMessage);
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return View(new DynamicDnsViewModel() { Settings = new DynamicDnsService() });
|
||||
}
|
||||
}
|
||||
[Route("server/services/dynamic-dns/{hostname}")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> DynamicDnsService(DynamicDnsViewModel viewModel, string hostname, string command = null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
|
||||
var i = settings.Services.FindIndex(d => d.Hostname.Equals(hostname, StringComparison.OrdinalIgnoreCase));
|
||||
if (i == -1)
|
||||
return NotFound();
|
||||
if (viewModel.Settings.Password == null)
|
||||
viewModel.Settings.Password = settings.Services[i].Password;
|
||||
if (viewModel.Settings.Hostname != null)
|
||||
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
|
||||
if (!viewModel.Settings.Enabled)
|
||||
{
|
||||
StatusMessage = $"The Dynamic DNS service has been disabled";
|
||||
viewModel.Settings.LastUpdated = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
|
||||
if (errorMessage == null)
|
||||
{
|
||||
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
|
||||
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, errorMessage);
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
settings.Services[i] = viewModel.Settings;
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
this.RouteData.Values.Remove(nameof(hostname));
|
||||
return RedirectToAction(nameof(DynamicDnsServices));
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("server/services/dynamic-dns/{hostname}/delete")]
|
||||
public async Task<IActionResult> DeleteDynamicDnsService(string hostname)
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
var i = settings.Services.FindIndex(d => d.Hostname.Equals(hostname, StringComparison.OrdinalIgnoreCase));
|
||||
if (i == -1)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete the dynamic dns service for " + hostname,
|
||||
Description = "BTCPayServer will stop updating this DNS record periodically",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("server/services/dynamic-dns/{hostname}/delete")]
|
||||
public async Task<IActionResult> DeleteDynamicDnsServicePost(string hostname)
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
var i = settings.Services.FindIndex(d => d.Hostname.Equals(hostname, StringComparison.OrdinalIgnoreCase));
|
||||
if (i == -1)
|
||||
return NotFound();
|
||||
settings.Services.RemoveAt(i);
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
StatusMessage = "Dynamic DNS service successfully removed";
|
||||
this.RouteData.Values.Remove(nameof(hostname));
|
||||
return RedirectToAction(nameof(DynamicDnsServices));
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
public IActionResult SSHService(bool downloadKeyFile = false)
|
||||
{
|
||||
@ -823,7 +985,8 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
||||
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
|
||||
await client.SendMailAsync(message);
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
@ -39,7 +40,8 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
||||
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
|
||||
await client.SendMailAsync(message);
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -35,7 +35,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("stores")]
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[AutoValidateAntiforgeryToken]
|
||||
public partial class StoresController : Controller
|
||||
{
|
||||
@ -479,23 +479,23 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
switch (paymentMethodId.PaymentType)
|
||||
{
|
||||
case PaymentTypes.BTCLike:
|
||||
case BitcoinPaymentType _:
|
||||
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
|
||||
{
|
||||
Crypto = paymentMethodId.CryptoCode,
|
||||
Value = strategy?.ToPrettyString() ?? string.Empty,
|
||||
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
|
||||
Enabled = !excludeFilters.Match(paymentMethodId)
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null
|
||||
});
|
||||
break;
|
||||
case PaymentTypes.LightningLike:
|
||||
case LightningPaymentType _:
|
||||
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode,
|
||||
Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty,
|
||||
Enabled = !excludeFilters.Match(paymentMethodId)
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && lightning?.GetLightningUrl() != null
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -590,6 +590,7 @@ namespace BTCPayServer.Controllers
|
||||
private CoinAverageExchange[] GetSupportedExchanges()
|
||||
{
|
||||
return _RateFactory.RateProviderFactory.GetSupportedExchanges()
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Value.Display))
|
||||
.Select(c => c.Value)
|
||||
.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
@ -44,6 +44,8 @@ namespace BTCPayServer.Controllers
|
||||
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
|
||||
if (psbt == null)
|
||||
throw new NotSupportedException("You need to update your version of NBXplorer");
|
||||
// Not supported by coldcard, remove when they do support it
|
||||
psbt.PSBT.GlobalXPubs.Clear();
|
||||
return psbt;
|
||||
}
|
||||
|
||||
@ -141,7 +143,11 @@ namespace BTCPayServer.Controllers
|
||||
var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId);
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
|
||||
try
|
||||
{
|
||||
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
|
||||
}
|
||||
catch { return BadRequest(); }
|
||||
return View(nameof(WalletPSBTReady), vm);
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ namespace BTCPayServer.Controllers
|
||||
public partial class WalletsController : Controller
|
||||
{
|
||||
public StoreRepository Repository { get; }
|
||||
public WalletRepository WalletRepository { get; }
|
||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
||||
public ExplorerClientProvider ExplorerClientProvider { get; }
|
||||
|
||||
@ -54,6 +55,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
CurrencyNameTable _currencyTable;
|
||||
public WalletsController(StoreRepository repo,
|
||||
WalletRepository walletRepository,
|
||||
CurrencyNameTable currencyTable,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
@ -66,6 +68,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
Repository = repo;
|
||||
WalletRepository = walletRepository;
|
||||
RateFetcher = rateProvider;
|
||||
NetworkProvider = networkProvider;
|
||||
_userManager = userManager;
|
||||
@ -76,6 +79,112 @@ namespace BTCPayServer.Controllers
|
||||
_walletProvider = walletProvider;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
string[] LabelColorScheme = new string[]
|
||||
{
|
||||
"#fbca04",
|
||||
"#0e8a16",
|
||||
"#ff7619",
|
||||
"#84b6eb",
|
||||
"#5319e7",
|
||||
"#000000",
|
||||
"#cc317c",
|
||||
};
|
||||
|
||||
const int MaxLabelSize = 20;
|
||||
const int MaxCommentSize = 200;
|
||||
[HttpPost]
|
||||
[Route("{walletId}")]
|
||||
public async Task<IActionResult> ModifyTransaction(
|
||||
// We need addlabel and addlabelclick. addlabel is the + button if the label does not exists,
|
||||
// addlabelclick is if the user click on existing label. For some reason, reusing the same name attribute for both
|
||||
// does not work
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string transactionId,
|
||||
string addlabel = null,
|
||||
string addlabelclick = null,
|
||||
string addcomment = null,
|
||||
string removelabel = null)
|
||||
{
|
||||
addlabel = addlabel ?? addlabelclick;
|
||||
// Hack necessary when the user enter a empty comment and submit.
|
||||
// For some reason asp.net consider addcomment null instead of empty string...
|
||||
try
|
||||
{
|
||||
if (addcomment == null && Request?.Form?.TryGetValue(nameof(addcomment), out _) is true)
|
||||
{
|
||||
addcomment = string.Empty;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
/////////
|
||||
|
||||
DerivationSchemeSettings paymentMethod = await GetDerivationSchemeSettings(walletId);
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
|
||||
var walletBlobInfoAsync = WalletRepository.GetWalletInfo(walletId);
|
||||
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId);
|
||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
||||
var walletBlobInfo = await walletBlobInfoAsync;
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
if (addlabel != null)
|
||||
{
|
||||
addlabel = addlabel.Trim().ToLowerInvariant().Replace(',',' ').Truncate(MaxLabelSize);
|
||||
var labels = walletBlobInfo.GetLabels();
|
||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
}
|
||||
if (!labels.Any(l => l.Value.Equals(addlabel, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
List<string> allColors = new List<string>();
|
||||
allColors.AddRange(LabelColorScheme);
|
||||
allColors.AddRange(labels.Select(l => l.Color));
|
||||
var chosenColor =
|
||||
allColors
|
||||
.GroupBy(k => k)
|
||||
.OrderBy(k => k.Count())
|
||||
.ThenBy(k => Array.IndexOf(LabelColorScheme, k.Key))
|
||||
.First().Key;
|
||||
walletBlobInfo.LabelColors.Add(addlabel, chosenColor);
|
||||
await WalletRepository.SetWalletInfo(walletId, walletBlobInfo);
|
||||
}
|
||||
if (walletTransactionInfo.Labels.Add(addlabel))
|
||||
{
|
||||
await WalletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||
}
|
||||
}
|
||||
else if (removelabel != null)
|
||||
{
|
||||
removelabel = removelabel.Trim().ToLowerInvariant().Truncate(MaxLabelSize);
|
||||
if (walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
if (walletTransactionInfo.Labels.Remove(removelabel))
|
||||
{
|
||||
var canDelete = !walletTransactionsInfo.SelectMany(txi => txi.Value.Labels).Any(l => l == removelabel);
|
||||
if (canDelete)
|
||||
{
|
||||
walletBlobInfo.LabelColors.Remove(removelabel);
|
||||
await WalletRepository.SetWalletInfo(walletId, walletBlobInfo);
|
||||
}
|
||||
await WalletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (addcomment != null)
|
||||
{
|
||||
addcomment = addcomment.Trim().Truncate(MaxCommentSize);
|
||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
}
|
||||
walletTransactionInfo.Comment = addcomment;
|
||||
await WalletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||
}
|
||||
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> ListWallets()
|
||||
{
|
||||
var wallets = new ListWalletsViewModel();
|
||||
@ -118,31 +227,48 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{walletId}")]
|
||||
public async Task<IActionResult> WalletTransactions(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId)
|
||||
WalletId walletId, string labelFilter = null)
|
||||
{
|
||||
DerivationSchemeSettings paymentMethod = await GetDerivationSchemeSettings(walletId);
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
|
||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
||||
var walletBlobAsync = WalletRepository.GetWalletInfo(walletId);
|
||||
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId);
|
||||
var transactions = await wallet.FetchTransactions(paymentMethod.AccountDerivation);
|
||||
|
||||
var walletBlob = await walletBlobAsync;
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
var model = new ListTransactionsViewModel();
|
||||
foreach (var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions))
|
||||
foreach (var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions).ToArray())
|
||||
{
|
||||
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
||||
model.Transactions.Add(vm);
|
||||
vm.Id = tx.TransactionId.ToString();
|
||||
vm.Link = string.Format(CultureInfo.InvariantCulture, paymentMethod.Network.BlockExplorerLink, vm.Id);
|
||||
vm.Timestamp = tx.Timestamp;
|
||||
vm.Positive = tx.BalanceChange >= Money.Zero;
|
||||
vm.Balance = tx.BalanceChange.ToString();
|
||||
vm.IsConfirmed = tx.Confirmations != 0;
|
||||
|
||||
if (walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||
{
|
||||
var labels = walletBlob.GetLabels(transactionInfo);
|
||||
vm.Labels.AddRange(labels);
|
||||
model.Labels.AddRange(labels);
|
||||
vm.Comment = transactionInfo.Comment;
|
||||
}
|
||||
|
||||
if (labelFilter == null || vm.Labels.Any(l => l.Value.Equals(labelFilter, StringComparison.OrdinalIgnoreCase)))
|
||||
model.Transactions.Add(vm);
|
||||
}
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private static string GetLabelTarget(WalletId walletId, uint256 txId)
|
||||
{
|
||||
return $"{walletId}:{txId}";
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}/send")]
|
||||
@ -410,7 +536,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath);
|
||||
ModelState.Remove(nameof(viewModel.PSBT));
|
||||
return await WalletPSBTReady(walletId, psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath.ToString());
|
||||
return await WalletPSBTReady(walletId, psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath?.ToString());
|
||||
}
|
||||
|
||||
private string ValueToString(Money v, BTCPayNetworkBase network)
|
||||
@ -488,7 +614,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/rescan")]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings.Key)]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
public async Task<IActionResult> WalletRescan(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, RescanWalletModel vm)
|
||||
|
@ -61,6 +61,9 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<WalletData> Wallets { get; set; }
|
||||
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
|
||||
|
||||
public DbSet<StoreData> Stores
|
||||
{
|
||||
get; set;
|
||||
@ -224,10 +227,25 @@ namespace BTCPayServer.Data
|
||||
.HasOne(o => o.StoreData)
|
||||
.WithMany(i => i.PaymentRequests)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<PaymentRequestData>()
|
||||
.Property(e => e.Created)
|
||||
.HasDefaultValue(NBitcoin.Utils.UnixTimeToDateTime(0));
|
||||
|
||||
builder.Entity<PaymentRequestData>()
|
||||
.HasIndex(o => o.Status);
|
||||
|
||||
builder.Entity<WalletTransactionData>()
|
||||
.HasKey(o => new
|
||||
{
|
||||
o.WalletDataId,
|
||||
#pragma warning disable CS0618
|
||||
o.TransactionId
|
||||
#pragma warning restore CS0618
|
||||
});
|
||||
builder.Entity<WalletTransactionData>()
|
||||
.HasOne(o => o.WalletData)
|
||||
.WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
|
||||
|
||||
}
|
||||
|
@ -28,9 +28,6 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public class StoreData
|
||||
{
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
|
||||
public string Id
|
||||
{
|
||||
get;
|
||||
@ -67,17 +64,15 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
public IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethods(BTCPayNetworkProvider networks)
|
||||
{
|
||||
networks = networks.UnfilteredNetworks;
|
||||
#pragma warning disable CS0618
|
||||
bool btcReturned = false;
|
||||
|
||||
// Legacy stuff which should go away
|
||||
if (!string.IsNullOrEmpty(DerivationStrategy))
|
||||
{
|
||||
if (networks.BTC != null)
|
||||
{
|
||||
btcReturned = true;
|
||||
yield return DerivationSchemeSettings.Parse(DerivationStrategy, networks.BTC);
|
||||
}
|
||||
btcReturned = true;
|
||||
yield return DerivationSchemeSettings.Parse(DerivationStrategy, networks.BTC);
|
||||
}
|
||||
|
||||
|
||||
@ -95,8 +90,7 @@ namespace BTCPayServer.Data
|
||||
if (strat.Value.Type == JTokenType.Null)
|
||||
continue;
|
||||
yield return
|
||||
PaymentMethodHandlerDictionary[paymentMethodId]
|
||||
.DeserializeSupportedPaymentMethod(paymentMethodId, strat.Value);
|
||||
paymentMethodId.PaymentType.DeserializeSupportedPaymentMethod(network, strat.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,7 +146,7 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
|
||||
if (!existing && supportedPaymentMethod == null && supportedPaymentMethod.PaymentId.IsBTCOnChain)
|
||||
if (!existing && supportedPaymentMethod == null && paymentMethodId.IsBTCOnChain)
|
||||
{
|
||||
DerivationStrategy = null;
|
||||
}
|
||||
@ -304,7 +298,7 @@ namespace BTCPayServer.Data
|
||||
public StoreBlob()
|
||||
{
|
||||
InvoiceExpiration = 15;
|
||||
MonitoringExpiration = 60;
|
||||
MonitoringExpiration = 1440;
|
||||
PaymentTolerance = 0;
|
||||
RequiresRefundEmail = true;
|
||||
}
|
||||
|
103
BTCPayServer/Data/WalletData.cs
Normal file
103
BTCPayServer/Data/WalletData.cs
Normal file
@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WalletData
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Key]
|
||||
public string Id { get; set; }
|
||||
|
||||
public List<WalletTransactionData> WalletTransactions { get; set; }
|
||||
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
public WalletBlobInfo GetBlobInfo()
|
||||
{
|
||||
if (Blob == null || Blob.Length == 0)
|
||||
{
|
||||
return new WalletBlobInfo();
|
||||
}
|
||||
var blobInfo = JsonConvert.DeserializeObject<WalletBlobInfo>(ZipUtils.Unzip(Blob));
|
||||
return blobInfo;
|
||||
}
|
||||
public void SetBlobInfo(WalletBlobInfo blobInfo)
|
||||
{
|
||||
if (blobInfo == null)
|
||||
{
|
||||
Blob = Array.Empty<byte>();
|
||||
return;
|
||||
}
|
||||
Blob = ZipUtils.Zip(JsonConvert.SerializeObject(blobInfo));
|
||||
}
|
||||
}
|
||||
|
||||
public class Label
|
||||
{
|
||||
public Label(string value, string color)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
if (color == null)
|
||||
throw new ArgumentNullException(nameof(color));
|
||||
Value = value;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
public string Color { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
Label item = obj as Label;
|
||||
if (item == null)
|
||||
return false;
|
||||
return Value.Equals(item.Value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
public static bool operator ==(Label a, Label b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.Value == b.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(Label a, Label b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode(StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public class WalletBlobInfo
|
||||
{
|
||||
public Dictionary<string, string> LabelColors { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public IEnumerable<Label> GetLabels(WalletTransactionInfo transactionInfo)
|
||||
{
|
||||
foreach (var label in transactionInfo.Labels)
|
||||
{
|
||||
if (LabelColors.TryGetValue(label, out var color))
|
||||
{
|
||||
yield return new Label(label, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Label> GetLabels()
|
||||
{
|
||||
foreach (var kv in LabelColors)
|
||||
{
|
||||
yield return new Label(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
BTCPayServer/Data/WalletTransactionData.cs
Normal file
51
BTCPayServer/Data/WalletTransactionData.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class WalletTransactionData
|
||||
{
|
||||
public string WalletDataId { get; set; }
|
||||
public WalletData WalletData { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public string Labels { get; set; }
|
||||
public byte[] Blob { get; set; }
|
||||
|
||||
public WalletTransactionInfo GetBlobInfo()
|
||||
{
|
||||
if (Blob == null || Blob.Length == 0)
|
||||
{
|
||||
return new WalletTransactionInfo();
|
||||
}
|
||||
var blobInfo = JsonConvert.DeserializeObject<WalletTransactionInfo>(ZipUtils.Unzip(Blob));
|
||||
if (!string.IsNullOrEmpty(Labels))
|
||||
{
|
||||
blobInfo.Labels.AddRange(Labels.Split(',', StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
return blobInfo;
|
||||
}
|
||||
public void SetBlobInfo(WalletTransactionInfo blobInfo)
|
||||
{
|
||||
if (blobInfo == null)
|
||||
{
|
||||
Labels = string.Empty;
|
||||
Blob = Array.Empty<byte>();
|
||||
return;
|
||||
}
|
||||
if (blobInfo.Labels.Any(l => l.Contains(',', StringComparison.OrdinalIgnoreCase)))
|
||||
throw new ArgumentException(paramName: nameof(blobInfo), message: "Labels must not contains ','");
|
||||
Labels = String.Join(',', blobInfo.Labels);
|
||||
Blob = ZipUtils.Zip(JsonConvert.SerializeObject(blobInfo));
|
||||
}
|
||||
}
|
||||
|
||||
public class WalletTransactionInfo
|
||||
{
|
||||
public string Comment { get; set; } = string.Empty;
|
||||
[JsonIgnore]
|
||||
public HashSet<string> Labels { get; set; } = new HashSet<string>();
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ namespace BTCPayServer
|
||||
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Cookie file is {(setting.CookieFile ?? "not set")}");
|
||||
if (setting.ExplorerUri != null)
|
||||
{
|
||||
_Clients.TryAdd(setting.CryptoCode, CreateExplorerClient(httpClientFactory.CreateClient($"NBXPLORER_{setting.CryptoCode}"), _NetworkProviders.GetNetwork<BTCPayNetwork>(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
|
||||
_Clients.TryAdd(setting.CryptoCode, CreateExplorerClient(httpClientFactory.CreateClient(nameof(ExplorerClientProvider)), _NetworkProviders.GetNetwork<BTCPayNetwork>(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,35 @@ using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string Truncate(this string value, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
return value.Length <= maxLength ? value : value.Substring(0, maxLength);
|
||||
}
|
||||
public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
|
||||
where T : class, IStartupTask
|
||||
=> services.AddTransient<IStartupTask, T>();
|
||||
public static async Task StartWithTasksAsync(this IWebHost webHost, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Load all tasks from DI
|
||||
var startupTasks = webHost.Services.GetServices<IStartupTask>();
|
||||
|
||||
// Execute all the tasks
|
||||
foreach (var startupTask in startupTasks)
|
||||
{
|
||||
await startupTask.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Start the tasks as normal
|
||||
await webHost.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public static string PrettyPrint(this TimeSpan expiration)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@ -81,7 +105,7 @@ namespace BTCPayServer
|
||||
}
|
||||
public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info)
|
||||
{
|
||||
return new PaymentMethodId(info.CryptoCode, Enum.Parse<PaymentTypes>(info.PaymentType));
|
||||
return new PaymentMethodId(info.CryptoCode, PaymentTypes.Parse(info.PaymentType));
|
||||
}
|
||||
public static async Task CloseSocket(this WebSocket webSocket)
|
||||
{
|
||||
@ -185,7 +209,7 @@ namespace BTCPayServer
|
||||
}
|
||||
if(IPAddress.TryParse(server, out var ip))
|
||||
{
|
||||
return ip.IsLocal();
|
||||
return ip.IsLocal() || ip.IsRFC1918();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -333,10 +357,15 @@ namespace BTCPayServer
|
||||
NBitcoin.Extensions.TryAdd(ctx.Items, "BitpayAuth", value);
|
||||
}
|
||||
|
||||
public static (string Signature, String Id, String Authorization) GetBitpayAuth(this HttpContext ctx)
|
||||
public static bool TryGetBitpayAuth(this HttpContext ctx, out (string Signature, String Id, String Authorization) result)
|
||||
{
|
||||
ctx.Items.TryGetValue("BitpayAuth", out object obj);
|
||||
return ((string Signature, String Id, String Authorization))obj;
|
||||
if (ctx.Items.TryGetValue("BitpayAuth", out object obj))
|
||||
{
|
||||
result = ((string Signature, String Id, String Authorization))obj;
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static StoreData GetStoreData(this HttpContext ctx)
|
||||
|
@ -10,28 +10,35 @@ namespace BTCPayServer
|
||||
{
|
||||
public static class OpenIddictExtensions
|
||||
{
|
||||
public static OpenIddictServerBuilder ConfigureSigningKey(this OpenIddictServerBuilder builder,
|
||||
IConfiguration configuration)
|
||||
private static SecurityKey _key = null;
|
||||
public static SecurityKey GetSigningKey(IConfiguration configuration)
|
||||
{
|
||||
|
||||
if (_key != null)
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
var file = Path.Combine(configuration.GetDataDir(), "rsaparams");
|
||||
|
||||
|
||||
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048);
|
||||
RsaSecurityKey key = null;
|
||||
|
||||
|
||||
if (File.Exists(file))
|
||||
{
|
||||
RSA.FromXmlString2( File.ReadAllText(file));
|
||||
RSA.FromXmlString2(File.ReadAllText(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
var contents = RSA.ToXmlString2(true);
|
||||
File.WriteAllText(file,contents );
|
||||
File.WriteAllText(file, contents);
|
||||
}
|
||||
|
||||
RSAParameters KeyParam = RSA.ExportParameters(true);
|
||||
key = new RsaSecurityKey(KeyParam);
|
||||
return builder.AddSigningKey(key);
|
||||
_key = new RsaSecurityKey(KeyParam);
|
||||
return _key;
|
||||
}
|
||||
public static OpenIddictServerBuilder ConfigureSigningKey(this OpenIddictServerBuilder builder,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
return builder.AddSigningKey(GetSigningKey(configuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ namespace BTCPayServer.HostedServices
|
||||
public AppType? RootAppType { get; set; }
|
||||
public string RootAppId { get; set; }
|
||||
|
||||
public List<PoliciesSettings.DomainToAppMappingItem> DomainToAppMapping { get; set; }
|
||||
|
||||
internal void Update(PoliciesSettings data)
|
||||
{
|
||||
ShowRegister = !data.LockSubscription;
|
||||
@ -59,6 +61,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
RootAppType = data.RootAppType;
|
||||
RootAppId = data.RootAppId;
|
||||
DomainToAppMapping = data.DomainToAppMapping;
|
||||
}
|
||||
}
|
||||
|
||||
|
73
BTCPayServer/HostedServices/DynamicDnsHostedService.cs
Normal file
73
BTCPayServer/HostedServices/DynamicDnsHostedService.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class DynamicDnsHostedService : BaseAsyncService
|
||||
{
|
||||
public DynamicDnsHostedService(IHttpClientFactory httpClientFactory, SettingsRepository settingsRepository)
|
||||
{
|
||||
HttpClientFactory = httpClientFactory;
|
||||
SettingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
public IHttpClientFactory HttpClientFactory { get; }
|
||||
public SettingsRepository SettingsRepository { get; }
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
CreateLoopTask(UpdateRecord)
|
||||
};
|
||||
}
|
||||
|
||||
TimeSpan Period = TimeSpan.FromMinutes(60);
|
||||
async Task UpdateRecord()
|
||||
{
|
||||
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(Cancellation))
|
||||
{
|
||||
var settings = await SettingsRepository.GetSettingAsync<DynamicDnsSettings>() ?? new DynamicDnsSettings();
|
||||
foreach (var service in settings.Services)
|
||||
{
|
||||
if (service?.Enabled is true && (service.LastUpdated is null ||
|
||||
(DateTimeOffset.UtcNow - service.LastUpdated) > Period))
|
||||
{
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(20.0));
|
||||
try
|
||||
{
|
||||
var errorMessage = await service.SendUpdateRequest(HttpClientFactory.CreateClient());
|
||||
if (errorMessage == null)
|
||||
{
|
||||
Logs.PayServer.LogInformation("Dynamic DNS service successfully refresh the DNS record");
|
||||
service.LastUpdated = DateTimeOffset.UtcNow;
|
||||
await SettingsRepository.UpdateSetting(settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logs.PayServer.LogWarning($"Dynamic DNS service is enabled but the request to the provider failed: {errorMessage}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (timeout.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using (var delayCancel = CancellationTokenSource.CreateLinkedTokenSource(Cancellation))
|
||||
{
|
||||
var delay = Task.Delay(Period, delayCancel.Token);
|
||||
var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(Cancellation);
|
||||
await Task.WhenAny(delay, changed);
|
||||
delayCancel.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,18 +36,15 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
EventAggregator _EventAggregator;
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
ExplorerClientProvider _ExplorerClientProvider;
|
||||
|
||||
public InvoiceWatcher(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
ExplorerClientProvider explorerClientProvider)
|
||||
{
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_NetworkProvider = networkProvider;
|
||||
_ExplorerClientProvider = explorerClientProvider;
|
||||
}
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
@ -69,10 +66,9 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray();
|
||||
var allPaymentMethods = invoice.GetPaymentMethods();
|
||||
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting, _NetworkProvider);
|
||||
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting);
|
||||
if (paymentMethod == null)
|
||||
return;
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethod.GetId().CryptoCode);
|
||||
if (invoice.Status == InvoiceStatus.New || invoice.Status == InvoiceStatus.Expired)
|
||||
{
|
||||
if (accounting.Paid >= accounting.MinimumTotalDue)
|
||||
@ -125,7 +121,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
if (invoice.Status == InvoiceStatus.Paid)
|
||||
{
|
||||
var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
|
||||
var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy));
|
||||
|
||||
if (// Is after the monitoring deadline
|
||||
(invoice.MonitoringExpiration < DateTimeOffset.UtcNow)
|
||||
@ -149,7 +145,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
if (invoice.Status == InvoiceStatus.Confirmed)
|
||||
{
|
||||
var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network));
|
||||
var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p));
|
||||
if (completedAccounting.Paid >= accounting.MinimumTotalDue)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1006, InvoiceEvent.Completed));
|
||||
@ -160,15 +156,13 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
}
|
||||
|
||||
public static PaymentMethod GetNearestClearedPayment(PaymentMethodDictionary allPaymentMethods, out PaymentMethodAccounting accounting, BTCPayNetworkProvider networkProvider)
|
||||
public static PaymentMethod GetNearestClearedPayment(PaymentMethodDictionary allPaymentMethods, out PaymentMethodAccounting accounting)
|
||||
{
|
||||
PaymentMethod result = null;
|
||||
accounting = null;
|
||||
decimal nearestToZero = 0.0m;
|
||||
foreach (var paymentMethod in allPaymentMethods)
|
||||
{
|
||||
if (networkProvider != null && networkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethod.GetId().CryptoCode) == null)
|
||||
continue;
|
||||
var currentAccounting = paymentMethod.Calculate();
|
||||
var distanceFromZero = Math.Abs(currentAccounting.DueUncapped.ToDecimal(MoneyUnit.BTC));
|
||||
if (result == null || distanceFromZero < nearestToZero)
|
||||
@ -318,12 +312,11 @@ namespace BTCPayServer.HostedServices
|
||||
.GetPayments()
|
||||
.Select<PaymentEntity, Task<PaymentEntity>>(async payment =>
|
||||
{
|
||||
var paymentNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(payment.GetCryptoCode());
|
||||
var paymentData = payment.GetCryptoPaymentData();
|
||||
if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData)
|
||||
{
|
||||
// Do update if confirmation count in the paymentData is not up to date
|
||||
if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted)
|
||||
if ((onChainPaymentData.ConfirmationCount < payment.Network.MaxTrackedConfirmation && payment.Accounted)
|
||||
&& (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
{
|
||||
var transactionResult = await _ExplorerClientProvider.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash);
|
||||
@ -332,7 +325,7 @@ namespace BTCPayServer.HostedServices
|
||||
payment.SetCryptoPaymentData(onChainPaymentData);
|
||||
|
||||
// we want to extend invoice monitoring until we reach max confirmations on all onchain payment methods
|
||||
if (confirmationCount < paymentNetwork.MaxTrackedConfirmation)
|
||||
if (confirmationCount < payment.Network.MaxTrackedConfirmation)
|
||||
extendInvoiceMonitoring = true;
|
||||
|
||||
return payment;
|
||||
|
@ -22,14 +22,12 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.HostedServices;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
@ -37,16 +35,25 @@ using BTCPayServer.Payments.Changelly;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.PaymentRequests;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NicolasDorier.RateLimits;
|
||||
using Npgsql;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
using BTCPayServer.Services.U2F;
|
||||
using BundlerMinifier.TagHelpers;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public static class BTCPayServerServices
|
||||
@ -60,6 +67,10 @@ namespace BTCPayServer.Hosting
|
||||
o.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
|
||||
});
|
||||
services.AddHttpClient();
|
||||
services.AddHttpClient(nameof(ExplorerClientProvider), httpClient =>
|
||||
{
|
||||
httpClient.Timeout = Timeout.InfiniteTimeSpan;
|
||||
});
|
||||
services.TryAddSingleton<SettingsRepository>();
|
||||
services.TryAddSingleton<TorServices>();
|
||||
services.TryAddSingleton<SocketFactory>();
|
||||
@ -67,19 +78,19 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<InvoicePaymentNotification>();
|
||||
services.TryAddSingleton<BTCPayServerOptions>(o =>
|
||||
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
||||
services.AddStartupTask<MigrationStartupTask>();
|
||||
services.TryAddSingleton<InvoiceRepository>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
|
||||
var paymentMethodHandlerDictionary = o.GetService<PaymentMethodHandlerDictionary>();
|
||||
var dbpath = Path.Combine(opts.DataDir, "InvoiceDB");
|
||||
if (!Directory.Exists(dbpath))
|
||||
Directory.CreateDirectory(dbpath);
|
||||
return new InvoiceRepository(dbContext, dbpath, o.GetRequiredService<BTCPayNetworkProvider>(),
|
||||
paymentMethodHandlerDictionary);
|
||||
return new InvoiceRepository(dbContext, dbpath, o.GetRequiredService<BTCPayNetworkProvider>());
|
||||
});
|
||||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
services.TryAddSingleton<WalletRepository>();
|
||||
services.TryAddSingleton<EventAggregator>();
|
||||
services.TryAddSingleton<PaymentRequestService>();
|
||||
services.TryAddSingleton<U2FService>();
|
||||
@ -115,6 +126,7 @@ namespace BTCPayServer.Hosting
|
||||
});
|
||||
|
||||
services.TryAddSingleton<AppService>();
|
||||
services.TryAddTransient<Safe>();
|
||||
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
|
||||
{
|
||||
|
||||
@ -175,7 +187,6 @@ namespace BTCPayServer.Hosting
|
||||
o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(DerivationStrategyBase)));
|
||||
});
|
||||
services.AddSingleton<IHostedService, CssThemeManagerHostedService>();
|
||||
services.AddSingleton<IHostedService, MigratorHostedService>();
|
||||
|
||||
services.AddSingleton<IHostedService, HostedServices.CheckConfigurationHostedService>();
|
||||
|
||||
@ -197,6 +208,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<IHostedService, RatesHostedService>();
|
||||
services.AddSingleton<IHostedService, BackgroundJobSchedulerHostedService>();
|
||||
services.AddSingleton<IHostedService, AppHubStreamer>();
|
||||
services.AddSingleton<IHostedService, DynamicDnsHostedService>();
|
||||
services.AddSingleton<IHostedService, TorServicesHostedService>();
|
||||
services.AddSingleton<IHostedService, PaymentRequestStreamer>();
|
||||
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
||||
@ -245,8 +257,9 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton(rateLimits);
|
||||
return services;
|
||||
}
|
||||
|
||||
private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services, IConfiguration configuration)
|
||||
|
||||
private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
|
||||
@ -254,9 +267,51 @@ namespace BTCPayServer.Hosting
|
||||
services.AddAuthentication()
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
// options.RequireHttpsMetadata = false;
|
||||
// options.TokenValidationParameters.ValidateAudience = false;
|
||||
//Disabled so that Tor works witt JWT auth
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.TokenValidationParameters.ValidateAudience = false;
|
||||
//we do not validate the issuer directly because btcpay can be accessed through multiple urls that we cannot predetermine
|
||||
options.TokenValidationParameters.ValidateIssuer = false;
|
||||
options.TokenValidationParameters.IssuerSigningKey =
|
||||
OpenIddictExtensions.GetSigningKey(configuration);
|
||||
options.IncludeErrorDetails = true;
|
||||
options.Events = new JwtBearerEvents()
|
||||
{
|
||||
OnTokenValidated = async context =>
|
||||
{
|
||||
var routeData = context.HttpContext.GetRouteData();
|
||||
var identity = ((ClaimsIdentity)context.Principal.Identity);
|
||||
if (context.Principal.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
identity.AddClaim(new Claim(Policies.CanModifyServerSettings.Key, "true"));
|
||||
}
|
||||
|
||||
if (context.HttpContext.GetStoreData() != null ||
|
||||
!routeData.Values.TryGetValue("storeId", out var storeId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var userManager = context.HttpContext.RequestServices
|
||||
.GetService<UserManager<ApplicationUser>>();
|
||||
var storeRepository = context.HttpContext.RequestServices
|
||||
.GetService<StoreRepository>();
|
||||
var userid = userManager.GetUserId(context.Principal);
|
||||
|
||||
if (!string.IsNullOrEmpty(userid))
|
||||
{
|
||||
var store = await storeRepository.FindStore((string)storeId, userid);
|
||||
if (store == null)
|
||||
{
|
||||
context.Fail("Could not authorize you against store access");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.HttpContext.SetStoreData(store);
|
||||
identity.AddClaims(store.GetClaims());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.AddCookie()
|
||||
.AddBitpayAuthentication();
|
||||
@ -264,37 +319,9 @@ namespace BTCPayServer.Hosting
|
||||
|
||||
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
||||
{
|
||||
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
|
||||
{
|
||||
//Wait the DB is ready
|
||||
Retry(() =>
|
||||
{
|
||||
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
|
||||
});
|
||||
}
|
||||
|
||||
app.UseMiddleware<BTCPayMiddleware>();
|
||||
return app;
|
||||
}
|
||||
|
||||
static void Retry(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(1000);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
act();
|
||||
return;
|
||||
}
|
||||
// Starting up
|
||||
catch (PostgresException ex) when (ex.SqlState == "57P03") { Thread.Sleep(1000); }
|
||||
catch when (!cts.IsCancellationRequested)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.EntityFrameworkCore.Models;
|
||||
using System.Net;
|
||||
using BTCPayServer.Authentication.OpenId;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Storage;
|
||||
@ -94,6 +95,10 @@ namespace BTCPayServer.Hosting
|
||||
string httpsCertificateFilePath = Configuration.GetOrDefault<string>("HttpsCertificateFilePath", null);
|
||||
bool useDefaultCertificate = Configuration.GetOrDefault<bool>("HttpsUseDefaultCertificate", false);
|
||||
bool hasCertPath = !String.IsNullOrEmpty(httpsCertificateFilePath);
|
||||
services.Configure<KestrelServerOptions>(kestrel =>
|
||||
{
|
||||
kestrel.Limits.MaxRequestLineSize = 8_192 * 10 * 5; // Around 500K, transactions passed in URI should not be bigger than this
|
||||
});
|
||||
if (hasCertPath || useDefaultCertificate)
|
||||
{
|
||||
var bindAddress = Configuration.GetOrDefault<IPAddress>("bind", IPAddress.Any);
|
||||
@ -142,6 +147,9 @@ namespace BTCPayServer.Hosting
|
||||
})
|
||||
.AddServer(options =>
|
||||
{
|
||||
|
||||
//Disabled so that Tor works with OpenIddict too
|
||||
options.DisableHttpsRequirement();
|
||||
// Register the ASP.NET Core MVC binder used by OpenIddict.
|
||||
// Note: if you don't call this method, you won't be able to
|
||||
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
|
||||
@ -150,8 +158,11 @@ namespace BTCPayServer.Hosting
|
||||
// Enable the token endpoint (required to use the password flow).
|
||||
options.EnableTokenEndpoint("/connect/token");
|
||||
options.EnableAuthorizationEndpoint("/connect/authorize");
|
||||
options.EnableAuthorizationEndpoint("/connect/logout");
|
||||
options.EnableLogoutEndpoint("/connect/logout");
|
||||
|
||||
//we do not care about these granular controls for now
|
||||
options.DisableScopeValidation();
|
||||
options.IgnoreEndpointPermissions();
|
||||
// Allow client applications various flows
|
||||
options.AllowImplicitFlow();
|
||||
options.AllowClientCredentialsFlow();
|
||||
@ -167,6 +178,12 @@ namespace BTCPayServer.Hosting
|
||||
OpenIdConnectConstants.Scopes.Email,
|
||||
OpenIdConnectConstants.Scopes.Profile,
|
||||
OpenIddictConstants.Scopes.Roles);
|
||||
options.AddEventHandler<PasswordGrantTypeEventHandler>();
|
||||
options.AddEventHandler<AuthorizationCodeGrantTypeEventHandler>();
|
||||
options.AddEventHandler<RefreshTokenGrantTypeEventHandler>();
|
||||
options.AddEventHandler<ClientCredentialsGrantTypeEventHandler>();
|
||||
options.AddEventHandler<AuthorizationEventHandler>();
|
||||
options.AddEventHandler<LogoutEventHandler>();
|
||||
|
||||
options.ConfigureSigningKey(Configuration);
|
||||
});
|
||||
|
13
BTCPayServer/IStartupTask.cs
Normal file
13
BTCPayServer/IStartupTask.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public interface IStartupTask
|
||||
{
|
||||
Task ExecuteAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -8,16 +8,18 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Logging;
|
||||
using System.Threading;
|
||||
using Npgsql;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class MigratorHostedService : BaseAsyncService
|
||||
public class MigrationStartupTask : IStartupTask
|
||||
{
|
||||
private ApplicationDbContextFactory _DBContextFactory;
|
||||
private StoreRepository _StoreRepository;
|
||||
private BTCPayNetworkProvider _NetworkProvider;
|
||||
private SettingsRepository _Settings;
|
||||
public MigratorHostedService(
|
||||
public MigrationStartupTask(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
StoreRepository storeRepository,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
@ -28,18 +30,11 @@ namespace BTCPayServer.HostedServices
|
||||
_NetworkProvider = networkProvider;
|
||||
_Settings = settingsRepository;
|
||||
}
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Update()
|
||||
};
|
||||
}
|
||||
|
||||
private async Task Update()
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Migrate(cancellationToken);
|
||||
var settings = (await _Settings.GetSettingAsync<MigrationSettings>()) ?? new MigrationSettings();
|
||||
if (!settings.DeprecatedLightningConnectionStringCheck)
|
||||
{
|
||||
@ -80,11 +75,34 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, "Error on the MigratorHostedService");
|
||||
Logs.PayServer.LogError(ex, "Error on the MigrationStartupTask");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Migrate(CancellationToken cancellationToken)
|
||||
{
|
||||
using (CancellationTokenSource timeout = new CancellationTokenSource(10_000))
|
||||
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
|
||||
{
|
||||
retry:
|
||||
try
|
||||
{
|
||||
await _DBContextFactory.CreateContext().Database.MigrateAsync();
|
||||
}
|
||||
// Starting up
|
||||
catch when (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(1000, cts.Token);
|
||||
}
|
||||
catch { }
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConvertConvertWalletKeyPathRoots()
|
||||
{
|
||||
bool save = false;
|
||||
@ -93,7 +111,6 @@ namespace BTCPayServer.HostedServices
|
||||
foreach (var store in await ctx.Stores.ToArrayAsync())
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
_StoreRepository.PrepareEntity(store);
|
||||
var blob = store.GetStoreBlob();
|
||||
if (blob.WalletKeyPathRoots == null)
|
||||
continue;
|
||||
@ -139,7 +156,6 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
foreach (var store in await ctx.Stores.ToArrayAsync())
|
||||
{
|
||||
_StoreRepository.PrepareEntity(store);
|
||||
var blob = store.GetStoreBlob();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (blob.NetworkFeeDisabled != null)
|
||||
@ -160,7 +176,6 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
foreach (var store in await ctx.Stores.ToArrayAsync())
|
||||
{
|
||||
_StoreRepository.PrepareEntity(store);
|
||||
var blob = store.GetStoreBlob();
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
decimal multiplier = 1.0m;
|
||||
@ -191,7 +206,6 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
foreach (var store in await ctx.Stores.ToArrayAsync())
|
||||
{
|
||||
_StoreRepository.PrepareEntity(store);
|
||||
foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType<Payments.Lightning.LightningSupportedPaymentMethod>())
|
||||
{
|
||||
var lightning = method.GetLightningUrl();
|
850
BTCPayServer/Migrations/20190701082105_sort_paymentrequests.Designer.cs
generated
Normal file
850
BTCPayServer/Migrations/20190701082105_sort_paymentrequests.Designer.cs
generated
Normal file
@ -0,0 +1,850 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20190701082105_sort_paymentrequests")]
|
||||
partial class sort_paymentrequests
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.11-servicing-32099");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationId");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("Scopes");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(450);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationId", "Status", "Subject", "Type");
|
||||
|
||||
b.ToTable("OpenIddictAuthorizations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<string>("ClientSecret");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("ConsentType");
|
||||
|
||||
b.Property<string>("DisplayName");
|
||||
|
||||
b.Property<string>("Permissions");
|
||||
|
||||
b.Property<string>("PostLogoutRedirectUris");
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("RedirectUris");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OpenIddictApplications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdToken", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationId");
|
||||
|
||||
b.Property<string>("AuthorizationId");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<DateTimeOffset?>("CreationDate");
|
||||
|
||||
b.Property<DateTimeOffset?>("ExpirationDate");
|
||||
|
||||
b.Property<string>("Payload");
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("ReferenceId")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(450);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorizationId");
|
||||
|
||||
b.HasIndex("ReferenceId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ApplicationId", "Status", "Subject", "Type");
|
||||
|
||||
b.ToTable("OpenIddictTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("AppType");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Settings");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<bool>("TagAllInvoices");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Apps");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<string>("CryptoCode");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("UniqueId");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp");
|
||||
|
||||
b.HasKey("InvoiceDataId", "UniqueId");
|
||||
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Accounted");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DefaultCrypto");
|
||||
|
||||
b.Property<string>("DerivationStrategies");
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValue(new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PaymentRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.U2F.Models.U2FDevice", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<byte[]>("AttestationCert")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int>("Counter");
|
||||
|
||||
b.Property<byte[]>("KeyHandle")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<byte[]>("PublicKey")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("U2FDevices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("FileName");
|
||||
|
||||
b.Property<string>("StorageFileName");
|
||||
|
||||
b.Property<DateTime>("Timestamp");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictScope<string>", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<string>("DisplayName");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("Resources");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OpenIddictScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", "Application")
|
||||
.WithMany("Authorizations")
|
||||
.HasForeignKey("ApplicationId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("OpenIdClients")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdToken", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", "Application")
|
||||
.WithMany("Tokens")
|
||||
.HasForeignKey("ApplicationId");
|
||||
|
||||
b.HasOne("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", "Authorization")
|
||||
.WithMany("Tokens")
|
||||
.HasForeignKey("AuthorizationId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("APIKeys")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Apps")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Invoices")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Events")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("PairedSINs")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("PendingInvoices")
|
||||
.HasForeignKey("Id")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("PaymentRequests")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.U2F.Models.U2FDevice", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("U2FDevices")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class sort_paymentrequests : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "Created",
|
||||
table: "PaymentRequests",
|
||||
nullable: false,
|
||||
defaultValue: new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Created",
|
||||
table: "PaymentRequests");
|
||||
}
|
||||
}
|
||||
}
|
885
BTCPayServer/Migrations/20190802142637_WalletData.Designer.cs
generated
Normal file
885
BTCPayServer/Migrations/20190802142637_WalletData.Designer.cs
generated
Normal file
@ -0,0 +1,885 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20190802142637_WalletData")]
|
||||
partial class WalletData
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.11-servicing-32099");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationId");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("Scopes");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(450);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationId", "Status", "Subject", "Type");
|
||||
|
||||
b.ToTable("OpenIddictAuthorizations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<string>("ClientSecret");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("ConsentType");
|
||||
|
||||
b.Property<string>("DisplayName");
|
||||
|
||||
b.Property<string>("Permissions");
|
||||
|
||||
b.Property<string>("PostLogoutRedirectUris");
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("RedirectUris");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OpenIddictApplications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdToken", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationId");
|
||||
|
||||
b.Property<string>("AuthorizationId");
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<DateTimeOffset?>("CreationDate");
|
||||
|
||||
b.Property<DateTimeOffset?>("ExpirationDate");
|
||||
|
||||
b.Property<string>("Payload");
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("ReferenceId")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(450);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorizationId");
|
||||
|
||||
b.HasIndex("ReferenceId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ApplicationId", "Status", "Subject", "Type");
|
||||
|
||||
b.ToTable("OpenIddictTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("AppType");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Settings");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<bool>("TagAllInvoices");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Apps");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<string>("CryptoCode");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("UniqueId");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp");
|
||||
|
||||
b.HasKey("InvoiceDataId", "UniqueId");
|
||||
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Accounted");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DefaultCrypto");
|
||||
|
||||
b.Property<string>("DerivationStrategies");
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Wallets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
{
|
||||
b.Property<string>("WalletDataId");
|
||||
|
||||
b.Property<string>("TransactionId");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("Labels");
|
||||
|
||||
b.HasKey("WalletDataId", "TransactionId");
|
||||
|
||||
b.ToTable("WalletTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValue(new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PaymentRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.U2F.Models.U2FDevice", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<byte[]>("AttestationCert")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int>("Counter");
|
||||
|
||||
b.Property<byte[]>("KeyHandle")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<byte[]>("PublicKey")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("U2FDevices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("FileName");
|
||||
|
||||
b.Property<string>("StorageFileName");
|
||||
|
||||
b.Property<DateTime>("Timestamp");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictScope<string>", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyToken")
|
||||
.IsConcurrencyToken()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<string>("DisplayName");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
b.Property<string>("Properties");
|
||||
|
||||
b.Property<string>("Resources");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OpenIddictScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", "Application")
|
||||
.WithMany("Authorizations")
|
||||
.HasForeignKey("ApplicationId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("OpenIdClients")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdToken", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdClient", "Application")
|
||||
.WithMany("Tokens")
|
||||
.HasForeignKey("ApplicationId");
|
||||
|
||||
b.HasOne("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", "Authorization")
|
||||
.WithMany("Tokens")
|
||||
.HasForeignKey("AuthorizationId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("APIKeys")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Apps")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Invoices")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Events")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("PairedSINs")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("PendingInvoices")
|
||||
.HasForeignKey("Id")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WalletData", "WalletData")
|
||||
.WithMany("WalletTransactions")
|
||||
.HasForeignKey("WalletDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("PaymentRequests")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.U2F.Models.U2FDevice", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("U2FDevices")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
52
BTCPayServer/Migrations/20190802142637_WalletData.cs
Normal file
52
BTCPayServer/Migrations/20190802142637_WalletData.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class WalletData : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Wallets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Blob = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Wallets", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WalletTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
WalletDataId = table.Column<string>(nullable: false),
|
||||
TransactionId = table.Column<string>(nullable: false),
|
||||
Labels = table.Column<string>(nullable: true),
|
||||
Blob = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WalletTransactions", x => new { x.WalletDataId, x.TransactionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_WalletTransactions_Wallets_WalletDataId",
|
||||
column: x => x.WalletDataId,
|
||||
principalTable: "Wallets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "WalletTransactions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Wallets");
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.8-servicing-32085");
|
||||
.HasAnnotation("ProductVersion", "2.1.11-servicing-32099");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Authentication.OpenId.Models.BTCPayOpenIdAuthorization", b =>
|
||||
{
|
||||
@ -399,6 +399,33 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Wallets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
{
|
||||
b.Property<string>("WalletDataId");
|
||||
|
||||
b.Property<string>("TransactionId");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("Labels");
|
||||
|
||||
b.HasKey("WalletDataId", "TransactionId");
|
||||
|
||||
b.ToTable("WalletTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -458,6 +485,10 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValue(new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
@ -772,6 +803,14 @@ namespace BTCPayServer.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.WalletTransactionData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.WalletData", "WalletData")
|
||||
.WithMany("WalletTransactions")
|
||||
.HasForeignKey("WalletDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
|
@ -9,6 +9,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
{
|
||||
public class UpdateCrowdfundViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
[Required] [MaxLength(30)] public string Title { get; set; }
|
||||
|
||||
[MaxLength(50)] public string Tagline { get; set; }
|
||||
|
@ -7,6 +7,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
{
|
||||
public class UpdatePointOfSaleViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
public string Title { get; set; }
|
||||
@ -60,6 +61,9 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
[Display(Name = "Redirect invoice to redirect url automatically after paid")]
|
||||
public string RedirectAutomatically { get; set; } = string.Empty;
|
||||
|
||||
public string AppId { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
|
||||
public SelectList RedirectAutomaticallySelectList =>
|
||||
new SelectList(new List< SelectListItem>()
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Models.ManageViewModels
|
||||
{
|
||||
@ -15,9 +16,10 @@ namespace BTCPayServer.Models.ManageViewModels
|
||||
[Display(Name = "Verification Code")]
|
||||
public string Code { get; set; }
|
||||
|
||||
[ReadOnly(true)]
|
||||
[BindNever]
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
[BindNever]
|
||||
public string AuthenticatorUri { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,8 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
EmbeddedCSS = blob.EmbeddedCSS;
|
||||
CustomCSSLink = blob.CustomCSSLink;
|
||||
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts;
|
||||
if (!string.IsNullOrEmpty(EmbeddedCSS))
|
||||
EmbeddedCSS = $"<style>{EmbeddedCSS}</style>";
|
||||
switch (data.Status)
|
||||
{
|
||||
case PaymentRequestData.PaymentRequestStatus.Pending:
|
||||
|
43
BTCPayServer/Models/ServerViewModels/DynamicDnsViewModel.cs
Normal file
43
BTCPayServer/Models/ServerViewModels/DynamicDnsViewModel.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class DynamicDnsViewModel
|
||||
{
|
||||
public class WellKnownService
|
||||
{
|
||||
public WellKnownService(string name, string url)
|
||||
{
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
public bool Modify { get; set; }
|
||||
public DynamicDnsService Settings { get; set; }
|
||||
public string LastUpdated
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Settings?.LastUpdated is DateTimeOffset date)
|
||||
{
|
||||
return Views.ViewsRazor.ToTimeAgo(date);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public WellKnownService[] KnownServices { get; set; } = new []
|
||||
{
|
||||
new WellKnownService("noip", "https://dynupdate.no-ip.com/nic/update"),
|
||||
new WellKnownService("dyndns", "https://members.dyndns.org/v3/update"),
|
||||
new WellKnownService("duckdns", "https://www.duckdns.org/v3/update"),
|
||||
new WellKnownService("google", "https://domains.google.com/nic/update"),
|
||||
};
|
||||
}
|
||||
}
|
@ -10,25 +10,16 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public class UserViewModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Email
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
}
|
||||
|
||||
public int Skip { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int Total { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<UserViewModel> Users
|
||||
{
|
||||
get; set;
|
||||
} = new List<UserViewModel>();
|
||||
public List<UserViewModel> Users { get; set; } = new List<UserViewModel>();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public decimal Max { get; set; }
|
||||
public decimal Step { get; set; }
|
||||
|
||||
// Custom Amount properties (ButtonType = 1)
|
||||
public bool SimpleInput { get; set; }
|
||||
public bool FitButtonInline { get; set; }
|
||||
|
||||
[Url]
|
||||
public string ServerIpn { get; set; }
|
||||
[Url]
|
||||
@ -30,6 +34,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string NotifyEmail { get; set; }
|
||||
|
||||
public string StoreId { get; set; }
|
||||
public string CheckoutQueryString { get; set; }
|
||||
|
||||
// Data that influences Pay Button UI, but not invoice creation
|
||||
public string UrlRoot { get; set; }
|
||||
|
@ -2,6 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
@ -11,11 +13,14 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public bool IsConfirmed { get; set; }
|
||||
public string Comment { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Link { get; set; }
|
||||
public bool Positive { get; set; }
|
||||
public string Balance { get; set; }
|
||||
public HashSet<Label> Labels { get; set; } = new HashSet<Label>();
|
||||
}
|
||||
public HashSet<Label> Labels { get; set; } = new HashSet<Label>();
|
||||
public List<TransactionViewModel> Transactions { get; set; } = new List<TransactionViewModel>();
|
||||
}
|
||||
}
|
||||
|
@ -157,8 +157,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
data.GetValue(),
|
||||
invoiceEvent.Payment.GetCryptoCode(),
|
||||
Enum.GetName(typeof(PaymentTypes),
|
||||
invoiceEvent.Payment.GetPaymentMethodId().PaymentType)
|
||||
invoiceEvent.Payment.GetPaymentMethodId().PaymentType.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -110,11 +110,11 @@ namespace BTCPayServer.PaymentRequest
|
||||
var paymentMethodId = paymentEntity.GetPaymentMethodId();
|
||||
|
||||
string txId = paymentData.GetPaymentId();
|
||||
string link = paymentEntity.PaymentMethodHandlerDictionary[paymentMethodId].GetTransactionLink(paymentMethodId, txId);
|
||||
string link = GetTransactionLink(paymentMethodId, txId);
|
||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment()
|
||||
{
|
||||
Amount = paymentData.GetValue(),
|
||||
PaymentMethod = paymentEntity.GetPaymentMethodId().ToString(),
|
||||
PaymentMethod = paymentMethodId.ToString(),
|
||||
Link = link,
|
||||
Id = txId
|
||||
};
|
||||
@ -122,5 +122,13 @@ namespace BTCPayServer.PaymentRequest
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private string GetTransactionLink(PaymentMethodId paymentMethodId, string txId)
|
||||
{
|
||||
var network = _BtcPayNetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
||||
if (network == null)
|
||||
return null;
|
||||
return paymentMethodId.PaymentType.GetTransactionLink(network, txId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
public class BitcoinLikeOnChainPaymentMethod : IPaymentMethodDetails
|
||||
{
|
||||
public PaymentTypes GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.BTCLike;
|
||||
}
|
||||
public PaymentType GetPaymentType() => PaymentTypes.BTCLike;
|
||||
|
||||
public string GetPaymentDestination()
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
|
||||
public class BitcoinLikePaymentData : CryptoPaymentData
|
||||
{
|
||||
public PaymentTypes GetPaymentType()
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.BTCLike;
|
||||
}
|
||||
@ -27,6 +27,8 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
RBF = rbf;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public BTCPayNetworkBase Network { get; set; }
|
||||
[JsonIgnore]
|
||||
public OutPoint Outpoint { get; set; }
|
||||
[JsonIgnore]
|
||||
public TxOut Output { get; set; }
|
||||
@ -54,12 +56,12 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
return Output.Value.ToDecimal(MoneyUnit.BTC);
|
||||
}
|
||||
|
||||
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetworkBase network)
|
||||
public bool PaymentCompleted(PaymentEntity entity)
|
||||
{
|
||||
return ConfirmationCount >= network.MaxTrackedConfirmation;
|
||||
return ConfirmationCount >= Network.MaxTrackedConfirmation;
|
||||
}
|
||||
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetworkBase network)
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy)
|
||||
{
|
||||
if (speedPolicy == SpeedPolicy.HighSpeed)
|
||||
{
|
||||
@ -80,14 +82,14 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
return false;
|
||||
}
|
||||
|
||||
public BitcoinAddress GetDestination(BTCPayNetworkBase network)
|
||||
public BitcoinAddress GetDestination()
|
||||
{
|
||||
return Output.ScriptPubKey.GetDestinationAddress(((BTCPayNetwork)network).NBitcoinNetwork);
|
||||
return Output.ScriptPubKey.GetDestinationAddress(((BTCPayNetwork)Network).NBitcoinNetwork);
|
||||
}
|
||||
|
||||
string CryptoPaymentData.GetDestination(BTCPayNetworkBase network)
|
||||
string CryptoPaymentData.GetDestination()
|
||||
{
|
||||
return GetDestination(network).ToString();
|
||||
return GetDestination().ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
@ -10,9 +10,6 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
@ -72,25 +69,19 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
public override async Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
|
||||
Dictionary<CurrencyPair, Task<RateResult>> rate, Money amount, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
if (storeBlob.OnChainMinValue == null)
|
||||
if (storeBlob.OnChainMinValue != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var limitValueRate =
|
||||
await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.OnChainMinValue.Currency)];
|
||||
|
||||
if (limitValueRate.BidAsk != null)
|
||||
{
|
||||
var limitValueCrypto = Money.Coins(storeBlob.OnChainMinValue.Value / limitValueRate.BidAsk.Bid);
|
||||
|
||||
if (amount > limitValueCrypto)
|
||||
var currentRateToCrypto = await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.OnChainMinValue.Currency)];
|
||||
if (currentRateToCrypto?.BidAsk != null)
|
||||
{
|
||||
return null;
|
||||
var limitValueCrypto = Money.Coins(storeBlob.OnChainMinValue.Value / currentRateToCrypto.BidAsk.Bid);
|
||||
if (amount < limitValueCrypto)
|
||||
{
|
||||
return "The amount of the invoice is too low to be paid on chain";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "The amount of the invoice is too low to be paid on chain";
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
@ -104,14 +95,6 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
return network.DisplayName;
|
||||
}
|
||||
|
||||
|
||||
public override string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args)
|
||||
{
|
||||
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, args);
|
||||
}
|
||||
|
||||
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
@ -123,7 +106,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
};
|
||||
}
|
||||
|
||||
public override PaymentTypes PaymentType => PaymentTypes.BTCLike;
|
||||
public override PaymentType PaymentType => PaymentTypes.BTCLike;
|
||||
|
||||
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
|
||||
DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
|
||||
@ -149,53 +132,5 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
|
||||
return onchainMethod;
|
||||
}
|
||||
|
||||
public override void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
|
||||
InvoiceCryptoInfo invoiceCryptoInfo,
|
||||
PaymentMethodAccounting accounting, PaymentMethod info)
|
||||
{
|
||||
var scheme = info.Network.UriScheme;
|
||||
|
||||
var minerInfo = new MinerFeeInfo();
|
||||
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
||||
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)info.GetPaymentMethodDetails()).FeeRate
|
||||
.GetFee(1).Satoshi;
|
||||
invoiceResponse.MinerFees.TryAdd(invoiceCryptoInfo.CryptoCode, minerInfo);
|
||||
invoiceCryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
||||
{
|
||||
BIP21 = $"{scheme}:{invoiceCryptoInfo.Address}?amount={invoiceCryptoInfo.Due}",
|
||||
};
|
||||
|
||||
#pragma warning disable 618
|
||||
if (info.CryptoCode == "BTC")
|
||||
{
|
||||
invoiceResponse.BTCPrice = invoiceCryptoInfo.Price;
|
||||
invoiceResponse.Rate = invoiceCryptoInfo.Rate;
|
||||
invoiceResponse.ExRates = invoiceCryptoInfo.ExRates;
|
||||
invoiceResponse.BitcoinAddress = invoiceCryptoInfo.Address;
|
||||
invoiceResponse.BTCPaid = invoiceCryptoInfo.Paid;
|
||||
invoiceResponse.BTCDue = invoiceCryptoInfo.Due;
|
||||
invoiceResponse.PaymentUrls = invoiceCryptoInfo.PaymentUrls;
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
if (value is JObject jobj)
|
||||
{
|
||||
var scheme = network.NBXplorerNetwork.Serializer.ToObject<DerivationSchemeSettings>(jobj);
|
||||
scheme.Network = network;
|
||||
return scheme;
|
||||
}
|
||||
// Legacy
|
||||
return DerivationSchemeSettings.Parse(((JValue)value).Value<string>(), network);
|
||||
}
|
||||
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(jobj.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
invoice = (await UpdatePaymentStates(wallet, invoice.Id));
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var paymentMethod = invoice.GetPaymentMethod(wallet.Network, PaymentTypes.BTCLike, _ExplorerClients.NetworkProviders);
|
||||
var paymentMethod = invoice.GetPaymentMethod(wallet.Network, PaymentTypes.BTCLike);
|
||||
if (paymentMethod != null &&
|
||||
paymentMethod.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod btc &&
|
||||
btc.GetDepositAddress(wallet.Network.NBitcoinNetwork).ScriptPubKey == paymentData.Output.ScriptPubKey &&
|
||||
|
@ -16,7 +16,7 @@ namespace BTCPayServer.Payments
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetPaymentDestination();
|
||||
PaymentTypes GetPaymentType();
|
||||
PaymentType GetPaymentType();
|
||||
/// <summary>
|
||||
/// Returns fee that the merchant charge to the customer for the next payment
|
||||
/// </summary>
|
||||
|
@ -7,8 +7,6 @@ using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceResponse = BTCPayServer.Models.InvoiceResponse;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
@ -39,11 +37,6 @@ namespace BTCPayServer.Payments
|
||||
/// <returns></returns>
|
||||
object PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store, BTCPayNetworkBase network);
|
||||
|
||||
void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
|
||||
InvoiceCryptoInfo invoiceCryptoInfo,
|
||||
PaymentMethodAccounting accounting, PaymentMethod info);
|
||||
|
||||
|
||||
void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse);
|
||||
string GetCryptoImage(PaymentMethodId paymentMethodId);
|
||||
string GetPaymentMethodName(PaymentMethodId paymentMethodId);
|
||||
@ -53,10 +46,6 @@ namespace BTCPayServer.Payments
|
||||
Money amount, PaymentMethodId paymentMethodId);
|
||||
|
||||
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
||||
|
||||
ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value);
|
||||
IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj);
|
||||
string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args);
|
||||
}
|
||||
|
||||
public interface IPaymentMethodHandler<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler
|
||||
@ -72,15 +61,12 @@ namespace BTCPayServer.Payments
|
||||
where TSupportedPaymentMethod : ISupportedPaymentMethod
|
||||
where TBTCPayNetwork : BTCPayNetworkBase
|
||||
{
|
||||
public abstract PaymentTypes PaymentType { get; }
|
||||
public abstract PaymentType PaymentType { get; }
|
||||
|
||||
public abstract Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
|
||||
TSupportedPaymentMethod supportedPaymentMethod,
|
||||
PaymentMethod paymentMethod, StoreData store, TBTCPayNetwork network, object preparePaymentObject);
|
||||
|
||||
public abstract void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
|
||||
InvoiceCryptoInfo invoiceCryptoInfo, PaymentMethodAccounting accounting, PaymentMethod info);
|
||||
|
||||
public abstract void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse);
|
||||
public abstract string GetCryptoImage(PaymentMethodId paymentMethodId);
|
||||
public abstract string GetPaymentMethodName(PaymentMethodId paymentMethodId);
|
||||
@ -90,11 +76,6 @@ namespace BTCPayServer.Payments
|
||||
|
||||
public abstract IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
||||
|
||||
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value);
|
||||
public abstract IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj);
|
||||
public abstract string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args);
|
||||
|
||||
|
||||
public virtual object PreparePayment(TSupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
|
@ -14,13 +14,15 @@ namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public class LightningLikePaymentData : CryptoPaymentData
|
||||
{
|
||||
[JsonIgnore]
|
||||
public BTCPayNetworkBase Network { get; set; }
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string BOLT11 { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 PaymentHash { get; set; }
|
||||
|
||||
public string GetDestination(BTCPayNetworkBase network)
|
||||
public string GetDestination()
|
||||
{
|
||||
return BOLT11;
|
||||
}
|
||||
@ -34,7 +36,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
return PaymentHash?.ToString() ?? BOLT11;
|
||||
}
|
||||
|
||||
public PaymentTypes GetPaymentType()
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.LightningLike;
|
||||
}
|
||||
@ -49,12 +51,12 @@ namespace BTCPayServer.Payments.Lightning
|
||||
return Amount.ToDecimal(LightMoneyUnit.BTC);
|
||||
}
|
||||
|
||||
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetworkBase network)
|
||||
public bool PaymentCompleted(PaymentEntity entity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetworkBase network)
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -13,9 +13,6 @@ using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
@ -40,7 +37,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
_socketFactory = socketFactory;
|
||||
}
|
||||
|
||||
public override PaymentTypes PaymentType => PaymentTypes.LightningLike;
|
||||
public override PaymentType PaymentType => PaymentTypes.LightningLike;
|
||||
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
|
||||
LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
|
||||
BTCPayNetwork network, object preparePaymentObject)
|
||||
@ -149,47 +146,20 @@ namespace BTCPayServer.Payments.Lightning
|
||||
public override async Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
|
||||
Dictionary<CurrencyPair, Task<RateResult>> rate, Money amount, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
if (storeBlob.OnChainMinValue == null)
|
||||
if (storeBlob.LightningMaxValue != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var currentRateToCrypto = await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.LightningMaxValue.Currency)];
|
||||
|
||||
var limitValueRate = await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.OnChainMinValue.Currency)];
|
||||
|
||||
if (limitValueRate.BidAsk != null)
|
||||
{
|
||||
var limitValueCrypto = Money.Coins(storeBlob.OnChainMinValue.Value / limitValueRate.BidAsk.Bid);
|
||||
|
||||
if (amount < limitValueCrypto)
|
||||
if (currentRateToCrypto?.BidAsk != null)
|
||||
{
|
||||
return null;
|
||||
var limitValueCrypto = Money.Coins(storeBlob.LightningMaxValue.Value / currentRateToCrypto.BidAsk.Bid);
|
||||
if (amount > limitValueCrypto)
|
||||
{
|
||||
return "The amount of the invoice is too high to be paid with lightning";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "The amount of the invoice is too high to be paid with lightning";
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<LightningSupportedPaymentMethod>(value.ToString());
|
||||
}
|
||||
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<LightningLikePaymentMethodDetails>(jobj.ToString());
|
||||
}
|
||||
|
||||
public override string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
|
||||
InvoiceCryptoInfo invoiceCryptoInfo, PaymentMethodAccounting accounting, PaymentMethod info)
|
||||
{
|
||||
invoiceCryptoInfo.PaymentUrls = new InvoicePaymentUrls()
|
||||
{
|
||||
BOLT11 = $"lightning:{invoiceCryptoInfo.Address}"
|
||||
};
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse)
|
||||
|
@ -17,7 +17,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
return BOLT11;
|
||||
}
|
||||
|
||||
public PaymentTypes GetPaymentType()
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.LightningLike;
|
||||
}
|
||||
|
@ -11,10 +11,12 @@ namespace BTCPayServer.Payments
|
||||
/// </summary>
|
||||
public class PaymentMethodId
|
||||
{
|
||||
public PaymentMethodId(string cryptoCode, PaymentTypes paymentType)
|
||||
public PaymentMethodId(string cryptoCode, PaymentType paymentType)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(cryptoCode));
|
||||
if (paymentType == null)
|
||||
throw new ArgumentNullException(nameof(paymentType));
|
||||
PaymentType = paymentType;
|
||||
CryptoCode = cryptoCode.ToUpperInvariant();
|
||||
}
|
||||
@ -29,7 +31,7 @@ namespace BTCPayServer.Payments
|
||||
}
|
||||
|
||||
public string CryptoCode { get; private set; }
|
||||
public PaymentTypes PaymentType { get; private set; }
|
||||
public PaymentType PaymentType { get; private set; }
|
||||
|
||||
|
||||
public override bool Equals(object obj)
|
||||
@ -66,22 +68,9 @@ namespace BTCPayServer.Payments
|
||||
return PaymentType == PaymentTypes.BTCLike ? CryptoCode : $"{CryptoCode}_{PaymentType}";
|
||||
}
|
||||
|
||||
public string ToPrettyPaymentType()
|
||||
{
|
||||
switch (PaymentType)
|
||||
{
|
||||
case PaymentTypes.BTCLike:
|
||||
return "On-Chain";
|
||||
case PaymentTypes.LightningLike:
|
||||
return "Off-Chain";
|
||||
default:
|
||||
return PaymentType.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string ToPrettyString()
|
||||
{
|
||||
return $"{CryptoCode} ({ToPrettyPaymentType()})";
|
||||
return $"{CryptoCode} ({PaymentType.ToPrettyString()})";
|
||||
}
|
||||
|
||||
public static bool TryParse(string str, out PaymentMethodId paymentMethodId)
|
||||
@ -90,28 +79,11 @@ namespace BTCPayServer.Payments
|
||||
var parts = str.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 0 || parts.Length > 2)
|
||||
return false;
|
||||
PaymentTypes type = PaymentTypes.BTCLike;
|
||||
PaymentType type = PaymentTypes.BTCLike;
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var typePart = parts[1].ToLowerInvariant();
|
||||
switch (typePart)
|
||||
{
|
||||
case "btclike":
|
||||
case "onchain":
|
||||
type = PaymentTypes.BTCLike;
|
||||
break;
|
||||
case "lightninglike":
|
||||
case "offchain":
|
||||
type = PaymentTypes.LightningLike;
|
||||
break;
|
||||
default:
|
||||
if (!Enum.TryParse(typePart, true, out type ))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
if (!PaymentTypes.TryParse(parts[1], out type))
|
||||
return false;
|
||||
}
|
||||
paymentMethodId = new PaymentMethodId(parts[0], type);
|
||||
return true;
|
||||
|
60
BTCPayServer/Payments/PaymentTypes.Bitcoin.cs
Normal file
60
BTCPayServer/Payments/PaymentTypes.Bitcoin.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public class BitcoinPaymentType : PaymentType
|
||||
{
|
||||
public static BitcoinPaymentType Instance { get; } = new BitcoinPaymentType();
|
||||
private BitcoinPaymentType()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override string ToPrettyString() => "On-Chain";
|
||||
public override string GetId() => "BTCLike";
|
||||
|
||||
public override CryptoPaymentData DeserializePaymentData(string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<BitcoinLikePaymentData>(str);
|
||||
}
|
||||
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(str);
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
var net = (BTCPayNetwork)network;
|
||||
if (value is JObject jobj)
|
||||
{
|
||||
var scheme = net.NBXplorerNetwork.Serializer.ToObject<DerivationSchemeSettings>(jobj);
|
||||
scheme.Network = net;
|
||||
return scheme;
|
||||
}
|
||||
// Legacy
|
||||
return DerivationSchemeSettings.Parse(((JValue)value).Value<string>(), net);
|
||||
}
|
||||
|
||||
public override string GetTransactionLink(BTCPayNetworkBase network, string txId)
|
||||
{
|
||||
if (txId == null)
|
||||
throw new ArgumentNullException(nameof(txId));
|
||||
if (network?.BlockExplorerLink == null)
|
||||
return null;
|
||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||
}
|
||||
}
|
||||
}
|
43
BTCPayServer/Payments/PaymentTypes.Lightning.cs
Normal file
43
BTCPayServer/Payments/PaymentTypes.Lightning.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public class LightningPaymentType : PaymentType
|
||||
{
|
||||
public static LightningPaymentType Instance { get; } = new LightningPaymentType();
|
||||
private LightningPaymentType()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override string ToPrettyString() => "Off-Chain";
|
||||
public override string GetId() => "LightningLike";
|
||||
|
||||
public override CryptoPaymentData DeserializePaymentData(string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<Payments.Lightning.LightningLikePaymentData>(str);
|
||||
}
|
||||
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<Payments.Lightning.LightningLikePaymentMethodDetails>(str);
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<LightningSupportedPaymentMethod>(value.ToString());
|
||||
}
|
||||
|
||||
public override string GetTransactionLink(BTCPayNetworkBase network, string txId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,21 +2,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
/// <summary>
|
||||
/// The different ways to pay an invoice
|
||||
/// </summary>
|
||||
public enum PaymentTypes
|
||||
public static class PaymentTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// On-Chain UTXO based, bitcoin compatible
|
||||
/// </summary>
|
||||
BTCLike,
|
||||
public static BitcoinPaymentType BTCLike => BitcoinPaymentType.Instance;
|
||||
/// <summary>
|
||||
/// Lightning payment
|
||||
/// </summary>
|
||||
LightningLike
|
||||
public static LightningPaymentType LightningLike => LightningPaymentType.Instance;
|
||||
|
||||
public static bool TryParse(string paymentType, out PaymentType type)
|
||||
{
|
||||
switch (paymentType.ToLowerInvariant())
|
||||
{
|
||||
case "btclike":
|
||||
case "onchain":
|
||||
type = PaymentTypes.BTCLike;
|
||||
break;
|
||||
case "lightninglike":
|
||||
case "offchain":
|
||||
type = PaymentTypes.LightningLike;
|
||||
break;
|
||||
default:
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static PaymentType Parse(string paymentType)
|
||||
{
|
||||
if (!TryParse(paymentType, out var result))
|
||||
throw new FormatException("Invalid payment type");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PaymentType
|
||||
{
|
||||
public abstract string ToPrettyString();
|
||||
public override string ToString()
|
||||
{
|
||||
return GetId();
|
||||
}
|
||||
|
||||
public abstract string GetId();
|
||||
public abstract CryptoPaymentData DeserializePaymentData(string str);
|
||||
public abstract IPaymentMethodDetails DeserializePaymentMethodDetails(string str);
|
||||
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value);
|
||||
|
||||
public abstract string GetTransactionLink(BTCPayNetworkBase network, string txId);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ namespace BTCPayServer
|
||||
l.AddFilter("Microsoft", LogLevel.Error);
|
||||
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
|
||||
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
|
||||
l.AddFilter("AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerHandler", LogLevel.Error);
|
||||
l.AddProvider(new CustomConsoleLogProvider(processor));
|
||||
|
||||
// Use Serilog for debug log file.
|
||||
@ -68,7 +69,7 @@ namespace BTCPayServer
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
host.StartAsync().GetAwaiter().GetResult();
|
||||
host.StartWithTasksAsync().GetAwaiter().GetResult();
|
||||
var urls = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
|
||||
foreach (var url in urls)
|
||||
{
|
||||
|
15
BTCPayServer/Security/AuthenticationExtensions.cs
Normal file
15
BTCPayServer/Security/AuthenticationExtensions.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
public static AuthenticationBuilder AddBitpayAuthentication(this AuthenticationBuilder builder)
|
||||
{
|
||||
builder.AddScheme<BitpayAuthenticationOptions, BitpayAuthenticationHandler>(Policies.BitpayAuthentication, o => { });
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@ -29,12 +29,9 @@ namespace BTCPayServer.Security
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
var principal = context.HttpContext.User;
|
||||
if (context.HttpContext.GetIsBitpayAPI())
|
||||
{
|
||||
if (context.HttpContext.User?.Identity?.AuthenticationType != Policies.CookieAuthentication)
|
||||
return;
|
||||
}
|
||||
|
||||
var principal = context.HttpContext.User;
|
||||
var identity = ((ClaimsIdentity)principal.Identity);
|
||||
if (principal.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
@ -55,10 +52,7 @@ namespace BTCPayServer.Security
|
||||
else
|
||||
{
|
||||
context.HttpContext.SetStoreData(store);
|
||||
if (store != null)
|
||||
{
|
||||
identity.AddClaims(store.GetClaims());
|
||||
}
|
||||
identity.AddClaims(store.GetClaims());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
173
BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs
Normal file
173
BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs
Normal file
@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using NBitpayClient.Extensions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
|
||||
namespace BTCPayServer.Security.Bitpay
|
||||
{
|
||||
public class BitpayAuthenticationHandler : AuthenticationHandler<BitpayAuthenticationOptions>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
TokenRepository _TokenRepository;
|
||||
public BitpayAuthenticationHandler(
|
||||
TokenRepository tokenRepository,
|
||||
StoreRepository storeRepository,
|
||||
IOptionsMonitor<BitpayAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
{
|
||||
_TokenRepository = tokenRepository;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
List<Claim> claims = new List<Claim>();
|
||||
if (!Context.Request.HttpContext.TryGetBitpayAuth(out var bitpayAuth))
|
||||
return AuthenticateResult.NoResult();
|
||||
string storeId = null;
|
||||
bool anonymous = true;
|
||||
bool? success = null;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
|
||||
storeId = result.StoreId;
|
||||
success = result.SuccessAuth;
|
||||
anonymous = false;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
|
||||
success = storeId != null;
|
||||
anonymous = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Context.Request.HttpContext.Request.Query.TryGetValue("storeId", out var storeIdStringValues))
|
||||
{
|
||||
storeId = storeIdStringValues.FirstOrDefault() ?? string.Empty;
|
||||
success = true;
|
||||
anonymous = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (success is true)
|
||||
{
|
||||
if (storeId != null)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
if (store == null ||
|
||||
(anonymous && !store.GetStoreBlob().AnyoneCanInvoice))
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
store.AdditionalClaims.AddRange(claims);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
else if (success is false)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
private async Task<(string StoreId, bool SuccessAuth)> CheckBitId(HttpContext httpContext, string sig, string id, List<Claim> claims)
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
|
||||
string storeId = null;
|
||||
string body = string.Empty;
|
||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = reader.ReadToEnd();
|
||||
}
|
||||
httpContext.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
var url = httpContext.Request.GetEncodedUrl();
|
||||
try
|
||||
{
|
||||
var key = new PubKey(id);
|
||||
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||
{
|
||||
var sin = key.GetBitIDSIN();
|
||||
claims.Add(new Claim(Claims.SIN, sin));
|
||||
|
||||
string token = null;
|
||||
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
|
||||
{
|
||||
token = tokenValues[0];
|
||||
}
|
||||
|
||||
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
|
||||
{
|
||||
try
|
||||
{
|
||||
token = JObject.Parse(body)?.Property("token", StringComparison.OrdinalIgnoreCase)?.Value?.Value<string>();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
var bitToken = (await _TokenRepository.GetTokens(sin)).FirstOrDefault();
|
||||
if (bitToken == null)
|
||||
{
|
||||
return (null, false);
|
||||
}
|
||||
storeId = bitToken.StoreId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (storeId, false);
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
return (storeId, true);
|
||||
}
|
||||
|
||||
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
||||
{
|
||||
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string apiKey = null;
|
||||
try
|
||||
{
|
||||
apiKey = Encoders.ASCII.EncodeData(Encoders.Base64.DecodeData(splitted[1]));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||
}
|
||||
}
|
||||
}
|
12
BTCPayServer/Security/Bitpay/BitpayAuthenticationOptions.cs
Normal file
12
BTCPayServer/Security/Bitpay/BitpayAuthenticationOptions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace BTCPayServer.Security.Bitpay
|
||||
{
|
||||
public class BitpayAuthenticationOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using NBitpayClient.Extensions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public class BitpayAuthentication
|
||||
{
|
||||
public class BitpayAuthOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
|
||||
}
|
||||
class BitpayAuthHandler : AuthenticationHandler<BitpayAuthOptions>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
TokenRepository _TokenRepository;
|
||||
public BitpayAuthHandler(
|
||||
TokenRepository tokenRepository,
|
||||
StoreRepository storeRepository,
|
||||
IOptionsMonitor<BitpayAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
{
|
||||
_TokenRepository = tokenRepository;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (Context.Request.HttpContext.GetIsBitpayAPI())
|
||||
{
|
||||
List<Claim> claims = new List<Claim>();
|
||||
var bitpayAuth = Context.Request.HttpContext.GetBitpayAuth();
|
||||
string storeId = null;
|
||||
bool anonymous = true;
|
||||
bool? success = null;
|
||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||
{
|
||||
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
|
||||
storeId = result.StoreId;
|
||||
success = result.SuccessAuth;
|
||||
anonymous = false;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||
{
|
||||
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
|
||||
success = storeId != null;
|
||||
anonymous = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Context.Request.HttpContext.Request.Query.TryGetValue("storeId", out var storeIdStringValues))
|
||||
{
|
||||
storeId = storeIdStringValues.FirstOrDefault() ?? string.Empty;
|
||||
success = true;
|
||||
anonymous = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (success is true)
|
||||
{
|
||||
if (storeId != null)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
|
||||
var store = await _StoreRepository.FindStore(storeId);
|
||||
if (store == null ||
|
||||
(anonymous && !store.GetStoreBlob().AnyoneCanInvoice))
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
store.AdditionalClaims.AddRange(claims);
|
||||
Context.Request.HttpContext.SetStoreData(store);
|
||||
}
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||
}
|
||||
else if (success is false)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid credentials");
|
||||
}
|
||||
// else if (success is null)
|
||||
}
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
private async Task<(string StoreId, bool SuccessAuth)> CheckBitId(HttpContext httpContext, string sig, string id, List<Claim> claims)
|
||||
{
|
||||
httpContext.Request.EnableRewind();
|
||||
|
||||
string storeId = null;
|
||||
string body = string.Empty;
|
||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = reader.ReadToEnd();
|
||||
}
|
||||
httpContext.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
var url = httpContext.Request.GetEncodedUrl();
|
||||
try
|
||||
{
|
||||
var key = new PubKey(id);
|
||||
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||
{
|
||||
var sin = key.GetBitIDSIN();
|
||||
claims.Add(new Claim(Claims.SIN, sin));
|
||||
|
||||
string token = null;
|
||||
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
|
||||
{
|
||||
token = tokenValues[0];
|
||||
}
|
||||
|
||||
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
|
||||
{
|
||||
try
|
||||
{
|
||||
token = JObject.Parse(body)?.Property("token", StringComparison.OrdinalIgnoreCase)?.Value?.Value<string>();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
var bitToken = (await _TokenRepository.GetTokens(sin)).FirstOrDefault();
|
||||
if (bitToken == null)
|
||||
{
|
||||
return (null, false);
|
||||
}
|
||||
storeId = bitToken.StoreId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (storeId, false);
|
||||
}
|
||||
}
|
||||
catch (FormatException) { }
|
||||
return (storeId, true);
|
||||
}
|
||||
|
||||
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
||||
{
|
||||
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string apiKey = null;
|
||||
try
|
||||
{
|
||||
apiKey = Encoders.ASCII.EncodeData(Encoders.Base64.DecodeData(splitted[1]));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||
}
|
||||
}
|
||||
public static void AddAuthentication(AuthenticationBuilder builder, Action<BitpayAuthOptions> bitpayAuth = null)
|
||||
{
|
||||
bitpayAuth = bitpayAuth ?? new Action<BitpayAuthOptions>((o) => { });
|
||||
builder.AddScheme<BitpayAuthOptions, BitpayAuthHandler>(Policies.BitpayAuthentication, bitpayAuth);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public static class BitpayAuthenticationExtensions
|
||||
{
|
||||
public static AuthenticationBuilder AddBitpayAuthentication(this AuthenticationBuilder builder,
|
||||
Action<BitpayAuthentication.BitpayAuthOptions> bitpayAuth = null)
|
||||
{
|
||||
BitpayAuthentication.AddAuthentication(builder,bitpayAuth);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@ -56,8 +56,7 @@ namespace BTCPayServer.Services.Apps
|
||||
{
|
||||
data.GetValue(),
|
||||
invoiceEvent.Payment.GetCryptoCode(),
|
||||
Enum.GetName(typeof(PaymentTypes),
|
||||
invoiceEvent.Payment.GetPaymentMethodId().PaymentType)
|
||||
invoiceEvent.Payment.GetPaymentMethodId().PaymentType.ToString()
|
||||
}, cancellationToken);
|
||||
}
|
||||
await InfoUpdated(appId);
|
||||
|
@ -37,11 +37,9 @@ namespace BTCPayServer.Services.Apps
|
||||
CurrencyNameTable _Currencies;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly HtmlSanitizer _HtmlSanitizer;
|
||||
private readonly BTCPayNetworkProvider _Networks;
|
||||
public CurrencyNameTable Currencies => _Currencies;
|
||||
public AppService(ApplicationDbContextFactory contextFactory,
|
||||
InvoiceRepository invoiceRepository,
|
||||
BTCPayNetworkProvider networks,
|
||||
CurrencyNameTable currencies,
|
||||
StoreRepository storeRepository,
|
||||
HtmlSanitizer htmlSanitizer)
|
||||
@ -51,7 +49,6 @@ namespace BTCPayServer.Services.Apps
|
||||
_Currencies = currencies;
|
||||
_storeRepository = storeRepository;
|
||||
_HtmlSanitizer = htmlSanitizer;
|
||||
_Networks = networks;
|
||||
}
|
||||
|
||||
public async Task<object> GetAppInfo(string appId)
|
||||
@ -99,11 +96,12 @@ namespace BTCPayServer.Services.Apps
|
||||
var invoices = await GetInvoicesForApp(appData, lastResetDate);
|
||||
var completeInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed).ToArray();
|
||||
var pendingInvoices = invoices.Where(entity => !(entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed)).ToArray();
|
||||
var paidInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed || entity.Status == InvoiceStatus.Paid).ToArray();
|
||||
|
||||
var pendingPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, pendingInvoices, !settings.EnforceTargetAmount);
|
||||
var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
||||
|
||||
var perkCount = invoices
|
||||
var perkCount = paidInvoices
|
||||
.Where(entity => !string.IsNullOrEmpty(entity.ProductInformation.ItemCode))
|
||||
.GroupBy(entity => entity.ProductInformation.ItemCode)
|
||||
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
||||
@ -152,7 +150,7 @@ namespace BTCPayServer.Services.Apps
|
||||
CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true),
|
||||
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
|
||||
{
|
||||
TotalContributors = invoices.Length,
|
||||
TotalContributors = paidInvoices.Length,
|
||||
ProgressPercentage = (currentPayments.TotalCurrency / settings.TargetAmount) * 100,
|
||||
PendingProgressPercentage = (pendingPayments.TotalCurrency / settings.TargetAmount) * 100,
|
||||
LastUpdated = DateTime.Now,
|
||||
@ -233,7 +231,21 @@ namespace BTCPayServer.Services.Apps
|
||||
.ToArrayAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false)
|
||||
{
|
||||
using (var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
var query = ctx.Apps
|
||||
.Where(us => appIds.Contains(us.Id));
|
||||
|
||||
if (includeStore)
|
||||
{
|
||||
query = query.Include(data => data.StoreData);
|
||||
}
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
|
||||
{
|
||||
@ -271,10 +283,10 @@ namespace BTCPayServer.Services.Apps
|
||||
.Where(kv => kv.Value != null)
|
||||
.Select(c => new ViewPointOfSaleViewModel.Item()
|
||||
{
|
||||
Description = _HtmlSanitizer.Sanitize(c.GetDetailString("description")),
|
||||
Description = c.GetDetailString("description"),
|
||||
Id = c.Key,
|
||||
Image = _HtmlSanitizer.Sanitize(c.GetDetailString("image")),
|
||||
Title = _HtmlSanitizer.Sanitize(c.GetDetailString("title") ?? c.Key),
|
||||
Image = c.GetDetailString("image"),
|
||||
Title = c.GetDetailString("title") ?? c.Key,
|
||||
Price = c.GetDetail("price")
|
||||
.Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice()
|
||||
{
|
||||
@ -325,7 +337,7 @@ namespace BTCPayServer.Services.Apps
|
||||
var paymentMethodContribution = new Contribution();
|
||||
paymentMethodContribution.PaymentMehtodId = pay.GetPaymentMethodId();
|
||||
paymentMethodContribution.Value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
||||
var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMehtodId, _Networks).Rate;
|
||||
var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMehtodId).Rate;
|
||||
paymentMethodContribution.CurrencyValue = rate * paymentMethodContribution.Value;
|
||||
return paymentMethodContribution;
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
91
BTCPayServer/Services/DynamicDnsSettings.cs
Normal file
91
BTCPayServer/Services/DynamicDnsSettings.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Hosting;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public class DynamicDnsSettings
|
||||
{
|
||||
public List<DynamicDnsService> Services { get; set; } = new List<DynamicDnsService>();
|
||||
}
|
||||
public class DynamicDnsService
|
||||
{
|
||||
[Display(Name = "Url of the Dynamic DNS service you are using")]
|
||||
[Required]
|
||||
public string ServiceUrl { get; set; }
|
||||
public string Login { get; set; }
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
[Display(Name = "Your dynamic DNS hostname")]
|
||||
[Required]
|
||||
public string Hostname { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? LastUpdated { get; set; }
|
||||
|
||||
public async Task<string> SendUpdateRequest(HttpClient httpClient)
|
||||
{
|
||||
string errorMessage = null;
|
||||
try
|
||||
{
|
||||
var result = await httpClient.SendAsync(CreateUpdateRequest());
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
errorMessage = await result.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch { }
|
||||
errorMessage = $"Error: Invalid return code {result.StatusCode}, expected 200 ({errorMessage.Trim()}) for hostname '{Hostname}'";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Error: While querying the Dynamic DNS service ({ex.Message}) for hostname '{Hostname}'";
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
public HttpRequestMessage CreateUpdateRequest()
|
||||
{
|
||||
HttpRequestMessage webRequest = new HttpRequestMessage();
|
||||
if (!Uri.TryCreate(ServiceUrl, UriKind.Absolute, out var uri) || uri.HostNameType == UriHostNameType.Unknown)
|
||||
{
|
||||
throw new FormatException($"Invalid service url");
|
||||
}
|
||||
|
||||
var builder = new UriBuilder(uri);
|
||||
if (!string.IsNullOrEmpty(Login))
|
||||
{
|
||||
builder.UserName = Login;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Password))
|
||||
{
|
||||
builder.Password = Password;
|
||||
}
|
||||
builder.UserName = builder.UserName ?? string.Empty;
|
||||
builder.Password = builder.Password ?? string.Empty;
|
||||
builder.Query = $"hostname={Hostname}";
|
||||
webRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", Encoders.Base64.EncodeData(new UTF8Encoding(false).GetBytes($"{builder.UserName}:{builder.Password}")));
|
||||
webRequest.Headers.TryAddWithoutValidation("User-Agent", $"BTCPayServer/{GetVersion()} btcpayserver@gmail.com");
|
||||
webRequest.Method = HttpMethod.Get;
|
||||
builder.UserName = string.Empty;
|
||||
builder.Password = string.Empty;
|
||||
webRequest.RequestUri = builder.Uri;
|
||||
return webRequest;
|
||||
}
|
||||
|
||||
private string GetVersion()
|
||||
{
|
||||
return typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
||||
}
|
||||
}
|
||||
}
|
@ -38,10 +38,10 @@ namespace BTCPayServer.Services
|
||||
List<KeyPath> derivations = new List<KeyPath>();
|
||||
if (network.NBitcoinNetwork.Consensus.SupportSegwit)
|
||||
{
|
||||
if (derivation.Redeem?.IsWitness is true ||
|
||||
derivation.ScriptPubKey.IsWitness) // Native or p2sh segwit
|
||||
if (derivation.Redeem?.IsScriptType(ScriptType.Witness) is true ||
|
||||
derivation.ScriptPubKey.IsScriptType(ScriptType.Witness)) // Native or p2sh segwit
|
||||
derivations.Add(new KeyPath("49'"));
|
||||
if (derivation.Redeem == null && derivation.ScriptPubKey.IsWitness) // Native segwit
|
||||
if (derivation.Redeem == null && derivation.ScriptPubKey.IsScriptType(ScriptType.Witness)) // Native segwit
|
||||
derivations.Add(new KeyPath("84'"));
|
||||
}
|
||||
derivations.Add(new KeyPath("44'"));
|
||||
|
@ -16,9 +16,8 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
public BTCPayNetworkProvider Networks { get; }
|
||||
public CurrencyNameTable Currencies { get; }
|
||||
|
||||
public InvoiceExport(BTCPayNetworkProvider networks, CurrencyNameTable currencies)
|
||||
public InvoiceExport(CurrencyNameTable currencies)
|
||||
{
|
||||
Networks = networks;
|
||||
Currencies = currencies;
|
||||
}
|
||||
public string Process(InvoiceEntity[] invoices, string fileFormat)
|
||||
@ -68,7 +67,7 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
|
||||
var pdata = payment.GetCryptoPaymentData();
|
||||
|
||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), Networks);
|
||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId());
|
||||
var paidAfterNetworkFees = pdata.GetValue() - payment.NetworkFee;
|
||||
invoiceDue -= paidAfterNetworkFees * pmethod.Rate;
|
||||
|
||||
@ -78,8 +77,8 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||
PaymentId = pdata.GetPaymentId(),
|
||||
CryptoCode = cryptoCode,
|
||||
ConversionRate = pmethod.Rate,
|
||||
PaymentType = payment.GetPaymentMethodId().ToPrettyPaymentType(),
|
||||
Destination = payment.GetCryptoPaymentData().GetDestination(Networks.GetNetwork<BTCPayNetworkBase>(cryptoCode)),
|
||||
PaymentType = payment.GetPaymentMethodId().PaymentType.ToPrettyString(),
|
||||
Destination = pdata.GetDestination(),
|
||||
Paid = pdata.GetValue().ToString(CultureInfo.InvariantCulture),
|
||||
PaidCurrency = Math.Round(pdata.GetValue() * pmethod.Rate, currency.NumberDecimalDigits).ToString(CultureInfo.InvariantCulture),
|
||||
// Adding NetworkFee because Paid doesn't take into account network fees
|
||||
|
@ -15,6 +15,7 @@ using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Payments;
|
||||
using NBitpayClient;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
@ -114,9 +115,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
public class InvoiceEntity
|
||||
{
|
||||
|
||||
[JsonIgnore]
|
||||
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
|
||||
[JsonIgnore]
|
||||
public BTCPayNetworkProvider Networks { get; set; }
|
||||
public const int InternalTagSupport_Version = 1;
|
||||
@ -222,8 +220,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
if (network == Networks.BTC && paymentMethodId.PaymentType == PaymentTypes.BTCLike)
|
||||
btcReturned = true;
|
||||
yield return PaymentMethodHandlerDictionary[paymentMethodId]
|
||||
.DeserializeSupportedPaymentMethod(paymentMethodId, strat.Value);
|
||||
yield return paymentMethodId.PaymentType.DeserializeSupportedPaymentMethod(network, strat.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -436,15 +433,49 @@ namespace BTCPayServer.Services.Invoices
|
||||
Id = data.GetPaymentId(),
|
||||
Fee = entity.NetworkFee,
|
||||
Value = data.GetValue(),
|
||||
Completed = data.PaymentCompleted(entity, info.Network),
|
||||
Confirmed = data.PaymentConfirmed(entity, SpeedPolicy, info.Network),
|
||||
Destination = data.GetDestination(info.Network),
|
||||
Completed = data.PaymentCompleted(entity),
|
||||
Confirmed = data.PaymentConfirmed(entity, SpeedPolicy),
|
||||
Destination = data.GetDestination(),
|
||||
PaymentType = data.GetPaymentType().ToString(),
|
||||
ReceivedDate = entity.ReceivedTime.DateTime
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
PaymentMethodHandlerDictionary[paymentId].PrepareInvoiceDto(dto, this, cryptoInfo, accounting, info);
|
||||
|
||||
if (paymentId.PaymentType == PaymentTypes.LightningLike)
|
||||
{
|
||||
cryptoInfo.PaymentUrls = new InvoicePaymentUrls()
|
||||
{
|
||||
BOLT11 = $"lightning:{cryptoInfo.Address}"
|
||||
};
|
||||
}
|
||||
else if (paymentId.PaymentType == PaymentTypes.BTCLike)
|
||||
{
|
||||
var scheme = info.Network.UriScheme;
|
||||
|
||||
var minerInfo = new MinerFeeInfo();
|
||||
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
||||
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)info.GetPaymentMethodDetails()).FeeRate
|
||||
.GetFee(1).Satoshi;
|
||||
dto.MinerFees.TryAdd(cryptoInfo.CryptoCode, minerInfo);
|
||||
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
|
||||
{
|
||||
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
|
||||
};
|
||||
|
||||
#pragma warning disable 618
|
||||
if (info.CryptoCode == "BTC")
|
||||
{
|
||||
dto.BTCPrice = cryptoInfo.Price;
|
||||
dto.Rate = cryptoInfo.Rate;
|
||||
dto.ExRates = cryptoInfo.ExRates;
|
||||
dto.BitcoinAddress = cryptoInfo.Address;
|
||||
dto.BTCPaid = cryptoInfo.Paid;
|
||||
dto.BTCDue = cryptoInfo.Due;
|
||||
dto.PaymentUrls = cryptoInfo.PaymentUrls;
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
dto.CryptoInfo.Add(cryptoInfo);
|
||||
dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls);
|
||||
@ -489,14 +520,14 @@ namespace BTCPayServer.Services.Invoices
|
||||
return rates.TryGet(paymentMethodId) != null;
|
||||
}
|
||||
|
||||
public PaymentMethod GetPaymentMethod(PaymentMethodId paymentMethodId, BTCPayNetworkProvider networkProvider)
|
||||
public PaymentMethod GetPaymentMethod(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
GetPaymentMethods().TryGetValue(paymentMethodId, out var data);
|
||||
return data;
|
||||
}
|
||||
public PaymentMethod GetPaymentMethod(BTCPayNetworkBase network, PaymentTypes paymentType, BTCPayNetworkProvider networkProvider)
|
||||
public PaymentMethod GetPaymentMethod(BTCPayNetworkBase network, PaymentType paymentType)
|
||||
{
|
||||
return GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentType), networkProvider);
|
||||
return GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentType));
|
||||
}
|
||||
|
||||
public PaymentMethodDictionary GetPaymentMethods()
|
||||
@ -513,9 +544,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
r.CryptoCode = paymentMethodId.CryptoCode;
|
||||
r.PaymentType = paymentMethodId.PaymentType.ToString();
|
||||
r.ParentEntity = this;
|
||||
r.Network = Networks?.GetNetwork<BTCPayNetworkBase>(r.CryptoCode);
|
||||
if (r.Network != null || Networks == null)
|
||||
paymentMethods.Add(r);
|
||||
r.Network = Networks?.UnfilteredNetworks.GetNetwork<BTCPayNetworkBase>(r.CryptoCode);
|
||||
paymentMethods.Add(r);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@ -723,7 +753,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
public PaymentMethodId GetId()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return new PaymentMethodId(CryptoCode, string.IsNullOrEmpty(PaymentType) ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(PaymentType));
|
||||
return new PaymentMethodId(CryptoCode, string.IsNullOrEmpty(PaymentType) ? PaymentTypes.BTCLike : PaymentTypes.Parse(PaymentType));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
@ -756,8 +786,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
else
|
||||
{
|
||||
var details = ParentEntity.PaymentMethodHandlerDictionary[GetId()]
|
||||
.DeserializePaymentMethodDetails(PaymentMethodDetails);
|
||||
IPaymentMethodDetails details = GetId().PaymentType.DeserializePaymentMethodDetails(PaymentMethodDetails.ToString());
|
||||
if (details is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod btcLike)
|
||||
{
|
||||
btcLike.NextNetworkFee = NextNetworkFee;
|
||||
@ -870,8 +899,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public class PaymentEntity
|
||||
{
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
public int Version { get; set; }
|
||||
public DateTimeOffset ReceivedTime
|
||||
{
|
||||
@ -911,37 +941,33 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public CryptoPaymentData GetCryptoPaymentData()
|
||||
{
|
||||
var paymentMethodId = GetPaymentMethodId();
|
||||
if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
|
||||
CryptoPaymentData paymentData = null;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return JsonConvert.DeserializeObject<Payments.Lightning.LightningLikePaymentData>(CryptoPaymentData);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
if (string.IsNullOrEmpty(CryptoPaymentData))
|
||||
{
|
||||
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
|
||||
var bitcoin = new BitcoinLikePaymentData();
|
||||
bitcoin.Network = Network;
|
||||
bitcoin.Outpoint = Outpoint;
|
||||
bitcoin.Output = Output;
|
||||
bitcoin.RBF = true;
|
||||
bitcoin.ConfirmationCount = 0;
|
||||
bitcoin.Legacy = true;
|
||||
bitcoin.Output = Output;
|
||||
bitcoin.Outpoint = Outpoint;
|
||||
paymentData = bitcoin;
|
||||
}
|
||||
else
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
|
||||
BitcoinLikePaymentData paymentData;
|
||||
if (string.IsNullOrEmpty(CryptoPaymentDataType))
|
||||
paymentData = GetPaymentMethodId().PaymentType.DeserializePaymentData(CryptoPaymentData);
|
||||
paymentData.Network = Network;
|
||||
if (paymentData is BitcoinLikePaymentData bitcoin)
|
||||
{
|
||||
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
|
||||
paymentData = new BitcoinLikePaymentData();
|
||||
paymentData.Outpoint = Outpoint;
|
||||
paymentData.Output = Output;
|
||||
paymentData.RBF = true;
|
||||
paymentData.ConfirmationCount = 0;
|
||||
paymentData.Legacy = true;
|
||||
return paymentData;
|
||||
bitcoin.Output = Output;
|
||||
bitcoin.Outpoint = Outpoint;
|
||||
}
|
||||
|
||||
paymentData =
|
||||
JsonConvert.DeserializeObject<BitcoinLikePaymentData>(CryptoPaymentData);
|
||||
// legacy
|
||||
paymentData.Output = Output;
|
||||
paymentData.Outpoint = Outpoint;
|
||||
#pragma warning restore CS0618
|
||||
return paymentData;
|
||||
}
|
||||
|
||||
return paymentData;
|
||||
}
|
||||
|
||||
public PaymentEntity SetCryptoPaymentData(CryptoPaymentData cryptoPaymentData)
|
||||
@ -978,7 +1004,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
public PaymentMethodId GetPaymentMethodId()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return new PaymentMethodId(CryptoCode ?? "BTC", string.IsNullOrEmpty(CryptoPaymentDataType) ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(CryptoPaymentDataType));
|
||||
return new PaymentMethodId(CryptoCode ?? "BTC", string.IsNullOrEmpty(CryptoPaymentDataType) ? PaymentTypes.BTCLike : PaymentTypes.Parse(CryptoPaymentDataType));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
@ -994,6 +1020,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
/// </summary>
|
||||
public interface CryptoPaymentData
|
||||
{
|
||||
[JsonIgnore]
|
||||
BTCPayNetworkBase Network { get; set; }
|
||||
/// <summary>
|
||||
/// Returns an identifier which uniquely identify the payment
|
||||
/// </summary>
|
||||
@ -1010,10 +1038,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
/// </summary>
|
||||
/// <returns>The amount paid</returns>
|
||||
decimal GetValue();
|
||||
bool PaymentCompleted(PaymentEntity entity, BTCPayNetworkBase network);
|
||||
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetworkBase network);
|
||||
bool PaymentCompleted(PaymentEntity entity);
|
||||
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy);
|
||||
|
||||
PaymentTypes GetPaymentType();
|
||||
string GetDestination(BTCPayNetworkBase network);
|
||||
PaymentType GetPaymentType();
|
||||
string GetDestination();
|
||||
}
|
||||
}
|
||||
|
@ -38,11 +38,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
private ApplicationDbContextFactory _ContextFactory;
|
||||
private readonly BTCPayNetworkProvider _Networks;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private CustomThreadPool _IndexerThread;
|
||||
|
||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath,
|
||||
BTCPayNetworkProvider networks, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
|
||||
BTCPayNetworkProvider networks)
|
||||
{
|
||||
int retryCount = 0;
|
||||
retry:
|
||||
@ -53,15 +52,13 @@ retry:
|
||||
catch when (retryCount++ < 5) { goto retry; }
|
||||
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
|
||||
_ContextFactory = contextFactory;
|
||||
_Networks = networks;
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
_Networks = networks.UnfilteredNetworks;
|
||||
}
|
||||
|
||||
public InvoiceEntity CreateNewInvoice()
|
||||
{
|
||||
return new InvoiceEntity()
|
||||
{
|
||||
PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary,
|
||||
Networks = _Networks,
|
||||
Version = InvoiceEntity.Lastest_Version,
|
||||
InvoiceTime = DateTimeOffset.UtcNow,
|
||||
@ -153,7 +150,6 @@ retry:
|
||||
{
|
||||
List<string> textSearch = new List<string>();
|
||||
invoice = ToObject(ToBytes(invoice));
|
||||
invoice.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
|
||||
invoice.Networks = _Networks;
|
||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||
#pragma warning disable CS0618
|
||||
@ -252,7 +248,7 @@ retry:
|
||||
return false;
|
||||
|
||||
var invoiceEntity = ToObject(invoice.Blob);
|
||||
var currencyData = invoiceEntity.GetPaymentMethod(network, paymentMethod.GetPaymentType(), null);
|
||||
var currencyData = invoiceEntity.GetPaymentMethod(network, paymentMethod.GetPaymentType());
|
||||
if (currencyData == null)
|
||||
return false;
|
||||
|
||||
@ -445,8 +441,8 @@ retry:
|
||||
entity.Payments = invoice.Payments.Select(p =>
|
||||
{
|
||||
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
|
||||
paymentEntity.Network = _Networks.GetNetwork<BTCPayNetwork>(paymentEntity.CryptoCode);
|
||||
paymentEntity.Accounted = p.Accounted;
|
||||
paymentEntity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
|
||||
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
|
||||
// We want to hide this legacy detail in InvoiceRepository, so we fetch the fee from the PaymentMethod and assign it to the PaymentEntity.
|
||||
if (paymentEntity.Version == 0)
|
||||
@ -651,8 +647,7 @@ retry:
|
||||
if (invoice == null)
|
||||
return null;
|
||||
InvoiceEntity invoiceEntity = ToObject(invoice.Blob);
|
||||
invoiceEntity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
|
||||
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null);
|
||||
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
|
||||
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
PaymentEntity entity = new PaymentEntity
|
||||
{
|
||||
@ -663,7 +658,7 @@ retry:
|
||||
ReceivedTime = date.UtcDateTime,
|
||||
Accounted = accounted,
|
||||
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
||||
PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary
|
||||
Network = network as BTCPayNetwork
|
||||
};
|
||||
entity.SetCryptoPaymentData(paymentData);
|
||||
|
||||
@ -720,7 +715,6 @@ retry:
|
||||
private InvoiceEntity ToObject(byte[] value)
|
||||
{
|
||||
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(value), null);
|
||||
entity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
|
||||
entity.Networks = _Networks;
|
||||
return entity;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user