Compare commits

...

169 Commits

Author SHA1 Message Date
aab7c08500 Unify StatusMessage handling 2019-11-07 18:59:51 +09:00
ff8de47e69 Fix tests 2019-11-07 18:36:45 +09:00
3ff9cc85ef Fix tests 2019-11-07 18:35:47 +09:00
0a2440e14c Fix test 2019-11-07 18:29:52 +09:00
d37abb53f0 Fix status message when a model is passed 2019-11-07 18:20:17 +09:00
656a3956b1 Fix strange message in PaymentRequests (Fix #1130) 2019-11-07 17:59:35 +09:00
c31e7593b6 Add label to FullyNoded 2019-11-07 17:08:24 +09:00
7be9083220 Update sponsor 2019-11-07 16:46:10 +09:00
b85667a191 Fix status message of PaymentRequest 2019-11-07 16:19:36 +09:00
892d906ac7 Fix tx link in payment request 2019-11-07 16:12:05 +09:00
0277708b2a Fix tor service parsing 2019-11-07 15:58:03 +09:00
d757aa2978 Fix typo 2019-11-07 15:48:56 +09:00
cf7bba9600 Fix services if there is more than one tor external services 2019-11-07 15:41:39 +09:00
a0d498812d bump 2019-11-07 14:35:18 +09:00
29e8783beb Update translations 2019-11-07 14:34:34 +09:00
57b87a55bc Show Bitcoin RPC service 2019-11-07 14:33:10 +09:00
9ff9377bc7 Make sure the recommended fees are different from the network fee rate 2019-11-07 13:45:45 +09:00
163ef031b9 Make sure the recommended fees are different from the network fee rate 2019-11-07 13:35:47 +09:00
1cfdd1489b Merge pull request #1127 from bolatovumar/master
Make recommended fee confirmation target configurable
2019-11-07 13:27:03 +09:00
0a9290a980 Make recommended fee confirmation target configurable
Address #1110
2019-11-06 16:21:33 -08:00
f0c2fb62df Select all link when clicked 2019-11-07 00:13:00 +09:00
24d6532dac Add onion link to login/register 2019-11-06 23:58:04 +09:00
fee1fe22a7 Use better svg version of the logo 2019-11-06 23:25:56 +09:00
d8103f7293 Fix webfont 2019-11-06 21:57:43 +09:00
3d3446e46a Improve design of register/login on mobile 2019-11-06 21:51:10 +09:00
22af4d2836 Because links are not reusable in LightningWalletService, it should not open in a new tab 2019-11-06 21:17:50 +09:00
06b2a3383d Merge pull request #1125 from NicolasDorier/fix/maintainence
Improve message in Maintainance page
2019-11-06 16:41:11 +09:00
06c3f01e41 Improve message in Maintainance page 2019-11-06 16:17:03 +09:00
d392880102 Merge pull request #1121 from NicolasDorier/ux/login-register
Add sponsor and new design to the login and registration page
2019-11-06 15:29:36 +09:00
55dd8da284 Redirect first run to register, redirect unlogged to login 2019-11-06 15:19:14 +09:00
e76f5b19b1 bump lightning package 2019-11-06 13:20:57 +09:00
5650b8560d Refactor the Header in its own partial view 2019-11-06 13:15:26 +09:00
2d80dbfa8f Custom logo and custom url for checkout page should accept any string (relative path) (Fix #1124) 2019-11-06 12:02:13 +09:00
c62aeb670a Merge pull request #1120 from rockstardev/cifixrefactor
Fixing CircleCi to run external test only in repo context, small refactor of LndServiceViewModel
2019-11-05 09:41:43 -06:00
1a407a2da3 Add sponsor and new design to the login and registration page 2019-11-05 18:01:43 +09:00
72d7e2dc5b Merge pull request #1116 from bolatovumar/issue-1111
Improve pay button display options section
2019-11-04 12:04:14 +09:00
a126ec1718 Renaming to LndServiceViewModel, used both by grpc and rest 2019-11-03 13:18:09 -06:00
bf59114a00 Run CircleCi external tests only in context of btcpayserver user & repo 2019-11-03 13:16:21 -06:00
6b587bfc31 Improve pay button display options section
address #1111
2019-11-03 09:53:09 -08:00
28a2017b65 Merge pull request #1108 from dennisreimann/paybutton
More pay button improvements
2019-11-03 20:26:55 +09:00
c3a6e9a799 Merge pull request #1112 from Kukks/fix-monero-qr
fix qr code amounts for monero
2019-11-03 20:26:35 +09:00
45c66a167b Merge pull request #1113 from Kukks/payment-request-fixes2
fix Payment request "Cancel invoice" button always showing
2019-11-03 20:25:46 +09:00
380629a961 Update BTCPayServer.Lightning 2019-11-03 16:34:14 +09:00
74fdd1358c Update translations 2019-11-03 16:31:01 +09:00
e9d63650b9 bump 2019-11-03 16:18:33 +09:00
9ecb27a9bd Fix and test /api-tokens (fix https://github.com/btcpayserver/woocommerce-plugin/issues/34) 2019-11-03 16:17:28 +09:00
b0ae878ef6 Merge pull request #1107 from NicolasDorier/ui/new-confirm
Redesign confirm forms
2019-11-03 15:37:33 +09:00
28cc4facd4 Redesign confirm form 2019-11-03 15:20:25 +09:00
749146ad3c fix Payment request "Cancel invoice" button always showing
The Cancel current invoice button always showed even when there was active invoice
2019-11-02 08:49:19 +01:00
a4ba93d249 fix qr code amounts for monero 2019-11-02 08:41:48 +01:00
7eae1f3b3c More pay button improvements 2019-10-31 11:31:30 +01:00
d66e8f2d13 Removing obsolete StatusMessage 2019-10-31 15:19:38 +09:00
058b768b0a update lightning charge in tests 2019-10-31 14:31:44 +09:00
3425f84507 Merge pull request #1106 from NicolasDorier/refactor/statusmessage
[WIP] Refactor StatusMessage and remove ExternalLogin
2019-10-31 14:12:05 +09:00
aad586232c Refactor StatusMessage and remove ExternalLogin 2019-10-31 14:11:33 +09:00
b514533814 Merge pull request #1104 from dennisreimann/paybutton
Improve pay button display
2019-10-31 14:01:38 +09:00
3b1f851f8a Improve pay button display
These changes should provide a more solid CSS approach to display the pay button in different scenarios. Also it fixes minor functionality issues like preselecting the merchant-chosen currency in the custom amount and slider dropdown.
2019-10-29 21:08:08 +01:00
99095f25d9 bump clightning 2019-10-29 16:46:22 +09:00
d79a28ac6f Merge pull request #1103 from rockstardev/lndseedless
Updating reference to new LND docker image and walletunlock.json path
2019-10-29 15:38:44 +09:00
120fe1ba85 Updating reference to new LND docker image and walletunlock.json path 2019-10-28 23:47:55 -05:00
ad8d8c31ab Merge pull request #1099 from Kukks/modal-close-fix
Fix: Close button for modal is broken
2019-10-28 14:26:20 +09:00
3e5ab70583 Fix: Close button for modal is broken
closes #1093
2019-10-27 08:13:30 +01:00
cd5b334a00 Make sure renci ssh does not prevent btcpay from closing 2019-10-26 23:40:35 +09:00
9a99b3fdc2 Fix bug: Could not delete store 2019-10-26 23:35:55 +09:00
2249ee195a Catch websockets exceptions 2019-10-23 16:11:20 +09:00
93cdbf7a1a Only push the released tag 2019-10-23 15:44:49 +09:00
22fa43f8d6 Version bump 2019-10-23 15:29:37 +09:00
39a6bd15d1 Update translations 2019-10-23 15:12:28 +09:00
1d8fe9fb93 Bump dbriize 2019-10-23 14:57:08 +09:00
565cac34b0 Update NBXplorer 2019-10-23 14:56:59 +09:00
1cb02b2913 Bump bundlerminifier 2019-10-23 14:56:48 +09:00
1174178771 Remove reliance on ambient routing values 2019-10-23 13:52:22 +09:00
f1a2cc2b65 Merge pull request #1096 from sam-sla/patch-2
Fix typo in SECURITY.md
2019-10-22 23:50:22 +09:00
36d97d1e74 Fix typo in SECURITY.md 2019-10-21 22:05:29 +02:00
12264d8e74 Make sure SSHClient Disconnect does not hang if a cancellationToken is passed 2019-10-21 18:43:53 +09:00
4d68b12080 Rewrite some EF queries to make EF3.0 happy 2019-10-21 18:38:57 +09:00
3c7137830e Make sure that the SSL connection does not prevent btcpayserver from restarting 2019-10-21 16:34:26 +09:00
4f1b4131cb Add more logs on hosted service exit 2019-10-21 14:03:55 +09:00
ccb45e3a99 Remove empty folder 2019-10-21 13:31:43 +09:00
f3461bfbe6 Show success message when derivation scheme is updated 2019-10-21 13:24:13 +09:00
22b05500f1 Reactivate ndax 2019-10-20 17:52:30 +09:00
66a2383ad1 Make CanHandleRefundEmailForm less capricious 2019-10-20 16:47:48 +09:00
7d6fb21a8c Fix test 2019-10-20 16:45:40 +09:00
5054e76d64 Fix build 2019-10-20 16:24:11 +09:00
9adb825c30 Do not ask for email in checkout by default (Fix #1084) 2019-10-20 15:46:34 +09:00
9a28dc5121 Pass cancellationToken around in the Kraken Provider 2019-10-20 15:24:07 +09:00
c82c67359c Merge pull request #1061 from reablaz/master
updated AppsUpdate view with correct GET request
2019-10-19 18:27:56 +09:00
59afc05661 Log hosted services exiting 2019-10-19 14:12:43 +09:00
c05820224e Log paytester dispose 2019-10-19 14:09:19 +09:00
fee106abef Remove possible NRE when app start 2019-10-19 13:50:52 +09:00
48fa11759f Missing files 2019-10-19 00:54:43 +09:00
eac4c91820 Move Bitpay authentication class in BTCPayServer.Security 2019-10-19 00:54:20 +09:00
037fcf93f5 Merge branch 'refactor/remove-scopes' 2019-10-18 23:58:43 +09:00
dabe805602 Remove dead link 2019-10-18 23:58:25 +09:00
dacf5c1e16 Go back to .net 7.3 2019-10-18 23:50:18 +09:00
da2e8665a1 Remove unused scope, assert policy on store listing 2019-10-18 23:42:06 +09:00
8643c04a39 Additional fixes for 3.0 2019-10-18 21:46:34 +09:00
c5ba063edf Move OpenId folder 2019-10-18 21:36:32 +09:00
9648836e2d Merge pull request #1077 from NicolasDorier/migration/openiddict30
Update to OpenIddict3.0
2019-10-18 21:30:27 +09:00
3c9b58916b Update to OpenIddict3.0 2019-10-18 19:02:23 +09:00
d56a5ad86e Merge pull request #1076 from bolatovumar/feat-1036
Add option to show recommeded fee on checkout invoice
2019-10-18 12:13:39 +09:00
cd5cc6435c make lightning scheme lowercase (Fix #1091) 2019-10-17 00:52:19 +09:00
c908301b84 Add option to show recommeded fee on checkout invoice
Address #1036
2019-10-14 10:09:26 -07:00
e68d76b56d Add timeout to tests 2019-10-14 23:50:51 +09:00
8339ec59b7 Merge pull request #1090 from bolatovumar/issue-1089
Make UI checkboxes inline with their labels
2019-10-14 22:47:29 +09:00
c5cfd7921a Merge pull request #1086 from NicolasDorier/refactor/authorization
Refactor authorizations
2019-10-14 22:46:26 +09:00
e584004b23 Make UI checkboxes inline with their labels
address #1089
2019-10-13 20:07:41 -07:00
281a2461ad Refactor authorizations 2019-10-14 00:24:41 +09:00
bd94b5f84e Temporarily remove docker image cache for circleci 2019-10-12 20:38:41 +09:00
2b040b3465 Merge pull request #1085 from Eskyee/patch-1
Update launchSettings.json
2019-10-12 12:28:28 +09:00
0e452c7cdf Update launchSettings.json
I`m  the Removing Docker-Regtest-https-monero as this loads as a default, which I didn't ask for, it  default in my builds config, launchSettings json, also I think since this json was changed, it gave my Mac VS a indent bug somehow, ?? But i may be wrong there, 
can Nicolas please review ?? this is the correct way I would remove Docker-Regtest-https-monero from my folk ?? thanks
2019-10-12 03:43:30 +01:00
2acdc77289 Update lang 2019-10-10 19:46:29 +09:00
fda6a1a77b Use ClaimTransformer instead of Authentication's JWT 2019-10-10 19:46:29 +09:00
7e5c593e09 Merge pull request #1081 from bolatovumar/fix-1078
[PoS app] Show card scrollbar only when necessary
2019-10-10 15:22:29 +09:00
40b191ef49 Skip HeadersOverrideMiddleware if on onion 2019-10-10 14:10:01 +09:00
a92f0fe289 [PoS app] Show card scrollbar only when necessary
fix #1078
2019-10-09 20:41:24 -07:00
ca17efbc29 Add missing file 2019-10-10 09:49:03 +09:00
5025e0dd4d Allow xforwardedproto to be override via configuration 2019-10-09 22:26:54 +09:00
1325c5d441 Add TestTimeout to some tests 2019-10-08 16:32:22 +09:00
30585d2cc1 Merge pull request #1075 from rockstardev/master
Replacing donation widget with link new donate page
2019-10-08 12:19:42 +09:00
98468f4eb0 Replacing donation widget with link new donate page
Resolves #1073

Co-authored-by: vswee <vswee@users.noreply.github.com>
2019-10-07 17:30:20 -05:00
39876dea07 Use random ports in container tests 2019-10-07 16:25:27 +09:00
03917ec806 Remove possible crash at shutdown 2019-10-07 16:06:36 +09:00
1c9a91140b Asyncify tests 2019-10-07 16:04:25 +09:00
3417556f5c Update pomelo on .netcore3.0 2019-10-07 15:12:22 +09:00
0cc2fa962d Prepare Startup.cs for netcoreapp30 2019-10-07 13:03:50 +09:00
8ba1303968 Move BitpayMiddleware up the stack 2019-10-07 12:43:17 +09:00
ae2b055fb5 Fix build 2019-10-06 23:41:27 +09:00
a919d3ddec Move TryGetSolutionDirectoryInfo in test utils 2019-10-06 23:38:57 +09:00
102b38b5a8 Make test CanUsePaymentMethodDropdown more reliable 2019-10-06 23:13:42 +09:00
56a363adf9 Add more test timeout 2019-10-06 22:51:01 +09:00
5c8dcb0292 Add test timeout 2019-10-06 22:49:28 +09:00
0fd5c722f6 Add test timeout for some selenium tests 2019-10-06 22:24:28 +09:00
b86befbdaf Merge pull request #1072 from pavlenex/security-md
Add Security.md
2019-10-06 22:20:26 +09:00
53310dee8a Add Security.md 2019-10-06 12:25:40 +02:00
78b86ce0ea Fix build in netcoreapp21 2019-10-06 18:47:49 +09:00
3bdc7c102a Fix Startup in netcoreapp3.0 2019-10-06 16:00:38 +09:00
f9714f0be0 Fix build 2019-10-06 15:54:19 +09:00
536f98b566 Fix entity framework queries to work in netcoreapp3.0 2019-10-06 15:48:12 +09:00
f4977e7f9f Prepare AuthenticationTests for .netcoreapp3.0 2019-10-05 20:45:09 +09:00
68807bae37 Do not use AsAsyncEnumerable() 2019-10-04 22:55:38 +09:00
ccc2d0e13c Exclude Google Cloud Storage for .netcoreapp3.0, it depends on System.Interactive.Linq which create namespace conflict with the new AsyncEnumerable 2019-10-04 17:56:08 +09:00
c2032ee15b Prepare the Authentication controller for .netcoreapp3.0 2019-10-04 17:21:53 +09:00
724a5b5460 Prepare code to move to netcoreapp3.0 2019-10-04 17:17:11 +09:00
c9ec0f9d3c Prepare startup.cs for netcoreapp3.0 2019-10-03 18:46:09 +09:00
411fe90b8c Can compile tests in netcoreapp3.0 2019-10-03 18:25:07 +09:00
a56004fbef Remove a warning on .netcoreapp3.0 2019-10-03 18:15:08 +09:00
e75edac3c1 Make .netcoreapp 3.0 build happy 2019-10-03 18:00:07 +09:00
aaa05eb5ec Fix build 2019-10-03 17:37:10 +09:00
8d0d80e086 Fix build 2019-10-03 17:14:07 +09:00
4d84343a80 Prepare BTCPayServer for .netcore 3.0 2019-10-03 17:06:49 +09:00
275fbc81e7 Prepare BTCPayServer.Data for .netcore 3.0 2019-10-03 16:36:02 +09:00
d23adfbd78 Prepare BTCPayServer.Common and BTCPayServer.Rating for .netcore 3.0 2019-10-03 16:13:12 +09:00
f7b85babfe Merge pull request #1068 from rockstardev/master
Cleanup U2F namespace to correspond to folder path
2019-10-03 12:57:53 +09:00
56e85b68d9 Csproj cleanup, reorganizing namespaces, fixing tests 2019-10-02 22:41:53 -05:00
755a6bf8e6 Cleaning up references to old U2F\Services folder 2019-10-02 12:38:06 -05:00
7282199c31 Cleanup U2F namespace to correspond to folder path 2019-10-02 12:32:41 -05:00
639f5d2fc4 Make sure calling monero related controllers can't be done if the shitcoin is not supported 2019-10-01 15:30:27 +09:00
8c8ef9d3ca Rename shitcoins to altcoins 2019-09-30 20:43:15 +09:00
2c5c6d28e3 Fix broken getting started link in readme (#1060) 2019-09-30 18:42:35 +09:00
fd78d02576 Moving Monero classes into BTCPayServer.Common 2019-09-30 17:58:41 +09:00
3a0328d0be Moving shitcoin code in shitcoin folder 2019-09-30 17:51:47 +09:00
d66b111121 xmr (#1044) 2019-09-30 17:32:43 +09:00
8cbc58ea2f updated AppsUpdate view with correct GET request
So user can request invoice data by invoice id from btcpayserver, to verify callback
2019-09-29 15:12:35 +03:00
3366c86b16 Authorize granular permissions (#1057)
* granular scope permissions for api

* final fixes and styling

* prettify code

* fix missing policy
2019-09-29 16:23:31 +09:00
c7e3241a85 Update info link in ListTokens 2019-09-29 16:21:25 +09:00
1c2c3ede80 Update help link 2019-09-29 16:12:03 +09:00
0f46da2e6b Update c-lightning 2019-09-29 13:53:18 +09:00
514386ecdd Remove NDax support 2019-09-29 11:58:16 +09:00
371 changed files with 5810 additions and 2685 deletions

View File

@ -2,7 +2,7 @@ version: 2
jobs:
fast_tests:
machine:
docker_layer_caching: true
enabled: true
steps:
- checkout
- run:
@ -10,7 +10,7 @@ jobs:
cd .circleci && ./run-tests.sh "Fast=Fast"
selenium_tests:
machine:
docker_layer_caching: true
enabled: true
steps:
- checkout
- run:
@ -18,7 +18,7 @@ jobs:
cd .circleci && ./run-tests.sh "Selenium=Selenium"
integration_tests:
machine:
docker_layer_caching: true
enabled: true
steps:
- checkout
- run:
@ -26,18 +26,22 @@ jobs:
cd .circleci && ./run-tests.sh "Integration=Integration"
external_tests:
machine:
docker_layer_caching: true
enabled: true
steps:
- checkout
- run:
command: |
cd .circleci && ./run-tests.sh "ExternalIntegration=ExternalIntegration"
if [ "$CIRCLE_PROJECT_USERNAME" == "btcpayserver" ] && [ "$CIRCLE_PROJECT_REPONAME" == "btcpayserver" ]; then
cd .circleci && ./run-tests.sh "ExternalIntegration=ExternalIntegration"
else
echo "Skipping running ExternalIntegration tests outside of context of main user and repository that have access to secrets"
fi
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
amd64:
machine:
docker_layer_caching: true
enabled: true
steps:
- checkout
- run:
@ -50,7 +54,7 @@ jobs:
arm32v7:
machine:
docker_layer_caching: true
enabled: true
steps:
- checkout
- run:

View File

@ -20,7 +20,7 @@ namespace BTCPayServer
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoinplus",
DefaultRateRules = new[]
DefaultRateRules = new[]
{
"XBC_X = XBC_BTC * BTC_X",
"XBC_BTC = cryptopia(XBC_BTC)"

View File

@ -56,6 +56,8 @@ namespace BTCPayServer
InitFeathercoin();
InitGroestlcoin();
InitViacoin();
InitMonero();
// Assume that electrum mappings are same as BTC if not specified
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
{

View File

@ -0,0 +1,21 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitMonero()
{
Add(new MoneroLikeSpecificBtcPayNetwork()
{
CryptoCode = "XMR",
DisplayName = "Monero",
BlockExplorerLink =
NetworkType == NetworkType.Mainnet
? "https://www.exploremonero.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
CryptoImagePath = "/imlegacy/monero.svg"
});
}
}
}

View File

@ -0,0 +1,7 @@
namespace BTCPayServer
{
public class MoneroLikeSpecificBtcPayNetwork : BTCPayNetworkBase
{
public int MaxTrackedConfirmation = 10;
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace BTCPayServer.Services.Altcoins.Monero.RPC
{
public class JsonRpcClient
{
private readonly Uri _address;
private readonly string _username;
private readonly string _password;
private readonly HttpClient _httpClient;
public JsonRpcClient(Uri address, string username, string password, HttpClient client = null)
{
_address = address;
_username = username;
_password = password;
_httpClient = client ?? new HttpClient();
}
public async Task<TResponse> SendCommandAsync<TRequest, TResponse>(string method, TRequest data,
CancellationToken cts = default(CancellationToken))
{
var jsonSerializer = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var httpRequest = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri(_address, "json_rpc"),
Content = new StringContent(
JsonConvert.SerializeObject(new JsonRpcCommand<TRequest>(method, data), jsonSerializer),
Encoding.UTF8, "application/json")
};
httpRequest.Headers.Accept.Clear();
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.Default.GetBytes($"{_username}:{_password}")));
var rawResult = await _httpClient.SendAsync(httpRequest, cts);
var rawJson = await rawResult.Content.ReadAsStringAsync();
rawResult.EnsureSuccessStatusCode();
JsonRpcResult<TResponse> response;
try
{
response = JsonConvert.DeserializeObject<JsonRpcResult<TResponse>>(rawJson, jsonSerializer);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(rawJson);
throw;
}
if (response.Error != null)
{
throw new JsonRpcApiException()
{
Error = response.Error
};
}
return response.Result;
}
public class NoRequestModel
{
public static NoRequestModel Instance = new NoRequestModel();
}
internal class JsonRpcApiException : Exception
{
public JsonRpcResultError Error { get; set; }
public override string Message => Error?.Message;
}
public class JsonRpcResultError
{
[JsonProperty("code")] public int Code { get; set; }
[JsonProperty("message")] public string Message { get; set; }
[JsonProperty("data")] dynamic Data { get; set; }
}
internal class JsonRpcResult<T>
{
[JsonProperty("result")] public T Result { get; set; }
[JsonProperty("error")] public JsonRpcResultError Error { get; set; }
[JsonProperty("id")] public string Id { get; set; }
}
internal class JsonRpcCommand<T>
{
[JsonProperty("jsonRpc")] public string JsonRpc { get; set; } = "2.0";
[JsonProperty("id")] public string Id { get; set; } = Guid.NewGuid().ToString();
[JsonProperty("method")] public string Method { get; set; }
[JsonProperty("params")] public T Parameters { get; set; }
public JsonRpcCommand()
{
}
public JsonRpcCommand(string method, T parameters)
{
Method = method;
Parameters = parameters;
}
}
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAccountRequest
{
[JsonProperty("label")] public string Label { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAccountResponse
{
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("address")] public string Address { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAddressRequest
{
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("label")] public string Label { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAddressResponse
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("address_index")] public long AddressIndex { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetAccountsRequest
{
[JsonProperty("tag")] public string Tag { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetAccountsResponse
{
[JsonProperty("subaddress_accounts")] public List<SubaddressAccount> SubaddressAccounts { get; set; }
[JsonProperty("total_balance")] public decimal TotalBalance { get; set; }
[JsonProperty("total_unlocked_balance")]
public decimal TotalUnlockedBalance { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public class GetFeeEstimateRequest
{
[JsonProperty("grace_blocks")] public int? GraceBlocks { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public class GetFeeEstimateResponse
{
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("untrusted")] public bool Untrusted { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetHeightResponse
{
[JsonProperty("height")] public long Height { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public class GetTransferByTransactionIdRequest
{
[JsonProperty("txid")] public string TransactionId { get; set; }
[JsonProperty("account_index")] public long AccountIndex { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetTransferByTransactionIdResponse
{
[JsonProperty("transfer")] public TransferItem Transfer { get; set; }
[JsonProperty("transfers")] public IEnumerable<TransferItem> Transfers { get; set; }
public partial class TransferItem
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("confirmations")] public long Confirmations { get; set; }
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("note")] public string Note { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
[JsonProperty("suggested_confirmations_threshold")]
public long SuggestedConfirmationsThreshold { get; set; }
[JsonProperty("timestamp")] public long Timestamp { get; set; }
[JsonProperty("txid")] public string Txid { get; set; }
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
}
}
}

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetTransfersRequest
{
[JsonProperty("in")] public bool In { get; set; }
[JsonProperty("out")] public bool Out { get; set; }
[JsonProperty("pending")] public bool Pending { get; set; }
[JsonProperty("failed")] public bool Failed { get; set; }
[JsonProperty("pool")] public bool Pool { get; set; }
[JsonProperty("filter_by_height ")] public bool FilterByHeight { get; set; }
[JsonProperty("min_height")] public long MinHeight { get; set; }
[JsonProperty("max_height")] public long MaxHeight { get; set; }
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("subaddr_indices")] public List<long> SubaddrIndices { get; set; }
}
}

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetTransfersResponse
{
[JsonProperty("in")] public List<GetTransfersResponseItem> In { get; set; }
[JsonProperty("out")] public List<GetTransfersResponseItem> Out { get; set; }
[JsonProperty("pending")] public List<GetTransfersResponseItem> Pending { get; set; }
[JsonProperty("failed")] public List<GetTransfersResponseItem> Failed { get; set; }
[JsonProperty("pool")] public List<GetTransfersResponseItem> Pool { get; set; }
public partial class GetTransfersResponseItem
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("confirmations")] public long Confirmations { get; set; }
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("note")] public string Note { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
[JsonProperty("suggested_confirmations_threshold")]
public long SuggestedConfirmationsThreshold { get; set; }
[JsonProperty("timestamp")] public long Timestamp { get; set; }
[JsonProperty("txid")] public string Txid { get; set; }
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
}
}
}

View File

@ -0,0 +1,33 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class Info
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("avg_download")] public long AvgDownload { get; set; }
[JsonProperty("avg_upload")] public long AvgUpload { get; set; }
[JsonProperty("connection_id")] public string ConnectionId { get; set; }
[JsonProperty("current_download")] public long CurrentDownload { get; set; }
[JsonProperty("current_upload")] public long CurrentUpload { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("host")] public string Host { get; set; }
[JsonProperty("incoming")] public bool Incoming { get; set; }
[JsonProperty("ip")] public string Ip { get; set; }
[JsonProperty("live_time")] public long LiveTime { get; set; }
[JsonProperty("local_ip")] public bool LocalIp { get; set; }
[JsonProperty("localhost")] public bool Localhost { get; set; }
[JsonProperty("peer_id")] public string PeerId { get; set; }
[JsonProperty("port")]
[JsonConverter(typeof(ParseStringConverter))]
public long Port { get; set; }
[JsonProperty("recv_count")] public long RecvCount { get; set; }
[JsonProperty("recv_idle_time")] public long RecvIdleTime { get; set; }
[JsonProperty("send_count")] public long SendCount { get; set; }
[JsonProperty("send_idle_time")] public long SendIdleTime { get; set; }
[JsonProperty("state")] public string State { get; set; }
[JsonProperty("support_flags")] public long SupportFlags { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class MakeUriRequest
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }
[JsonProperty("tx_description")] public string TxDescription { get; set; }
[JsonProperty("recipient_name")] public string RecipientName { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class MakeUriResponse
{
[JsonProperty("uri")] public string Uri { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
internal class ParseStringConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = serializer.Deserialize<string>(reader);
long l;
if (Int64.TryParse(value, out l))
{
return l;
}
throw new Exception("Cannot unmarshal type long");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (long)untypedValue;
serializer.Serialize(writer, value.ToString(CultureInfo.InvariantCulture));
return;
}
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class Peer
{
[JsonProperty("info")] public Info Info { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class SubaddrIndex
{
[JsonProperty("major")] public long Major { get; set; }
[JsonProperty("minor")] public long Minor { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class SubaddressAccount
{
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("balance")] public decimal Balance { get; set; }
[JsonProperty("base_address")] public string BaseAddress { get; set; }
[JsonProperty("label")] public string Label { get; set; }
[JsonProperty("tag")] public string Tag { get; set; }
[JsonProperty("unlocked_balance")] public decimal UnlockedBalance { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class SyncInfoResponse
{
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("peers")] public List<Peer> Peers { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("target_height")] public long? TargetHeight { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.Globalization;
namespace BTCPayServer.Services.Altcoins.Monero.Utils
{
public class MoneroMoney
{
public static decimal Convert(long atoms)
{
var amt = atoms.ToString(CultureInfo.InvariantCulture).PadLeft(12, '0');
amt = amt.Length == 12 ? $"0.{amt}" : amt.Insert(amt.Length - 12, ".");
return decimal.Parse(amt, CultureInfo.InvariantCulture);
}
public static long Convert(decimal monero)
{
return System.Convert.ToInt64(monero);
}
}
}

View File

@ -52,6 +52,7 @@ namespace BTCPayServer
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
public int MaxTrackedConfirmation { get; internal set; } = 6;
@ -103,7 +104,6 @@ namespace BTCPayServer
public abstract class BTCPayNetworkBase
{
public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; }
public string DisplayName { get; set; }
@ -131,7 +131,7 @@ namespace BTCPayServer
public virtual string ToString<T>(T obj)
{
return JsonConvert.SerializeObject(obj);
return NBitcoin.JsonConverters.Serializer.ToString(obj, null);
}
}
}

View File

@ -3,8 +3,8 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" />
<PackageReference Include="NBitcoin" Version="4.2.4" />
<PackageReference Include="NBXplorer.Client" Version="2.0.0.19" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="NBXplorer.Client" Version="2.0.0.21" />
</ItemGroup>
</Project>

View File

@ -4,7 +4,6 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions.Internal;
using Microsoft.Extensions.Logging.Console;

View File

@ -0,0 +1,259 @@
#if !NETCOREAPP21
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Extensions.Logging.Console.Internal
{
/// <summary>
/// For non-Windows platform consoles which understand the ANSI escape code sequences to represent color
/// </summary>
internal class AnsiLogConsole : IConsole
{
private readonly StringBuilder _outputBuilder;
private readonly IAnsiSystemConsole _systemConsole;
public AnsiLogConsole(IAnsiSystemConsole systemConsole)
{
_outputBuilder = new StringBuilder();
_systemConsole = systemConsole;
}
public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
{
// Order: backgroundcolor, foregroundcolor, Message, reset foregroundcolor, reset backgroundcolor
if (background.HasValue)
{
_outputBuilder.Append(GetBackgroundColorEscapeCode(background.Value));
}
if (foreground.HasValue)
{
_outputBuilder.Append(GetForegroundColorEscapeCode(foreground.Value));
}
_outputBuilder.Append(message);
if (foreground.HasValue)
{
_outputBuilder.Append("\x1B[39m\x1B[22m"); // reset to default foreground color
}
if (background.HasValue)
{
_outputBuilder.Append("\x1B[49m"); // reset to the background color
}
}
public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
{
Write(message, background, foreground);
_outputBuilder.AppendLine();
}
public void Flush()
{
_systemConsole.Write(_outputBuilder.ToString());
_outputBuilder.Clear();
}
private static string GetForegroundColorEscapeCode(ConsoleColor color)
{
switch (color)
{
case ConsoleColor.Black:
return "\x1B[30m";
case ConsoleColor.DarkRed:
return "\x1B[31m";
case ConsoleColor.DarkGreen:
return "\x1B[32m";
case ConsoleColor.DarkYellow:
return "\x1B[33m";
case ConsoleColor.DarkBlue:
return "\x1B[34m";
case ConsoleColor.DarkMagenta:
return "\x1B[35m";
case ConsoleColor.DarkCyan:
return "\x1B[36m";
case ConsoleColor.Gray:
return "\x1B[37m";
case ConsoleColor.Red:
return "\x1B[1m\x1B[31m";
case ConsoleColor.Green:
return "\x1B[1m\x1B[32m";
case ConsoleColor.Yellow:
return "\x1B[1m\x1B[33m";
case ConsoleColor.Blue:
return "\x1B[1m\x1B[34m";
case ConsoleColor.Magenta:
return "\x1B[1m\x1B[35m";
case ConsoleColor.Cyan:
return "\x1B[1m\x1B[36m";
case ConsoleColor.White:
return "\x1B[1m\x1B[37m";
default:
return "\x1B[39m\x1B[22m"; // default foreground color
}
}
private static string GetBackgroundColorEscapeCode(ConsoleColor color)
{
switch (color)
{
case ConsoleColor.Black:
return "\x1B[40m";
case ConsoleColor.Red:
return "\x1B[41m";
case ConsoleColor.Green:
return "\x1B[42m";
case ConsoleColor.Yellow:
return "\x1B[43m";
case ConsoleColor.Blue:
return "\x1B[44m";
case ConsoleColor.Magenta:
return "\x1B[45m";
case ConsoleColor.Cyan:
return "\x1B[46m";
case ConsoleColor.White:
return "\x1B[47m";
default:
return "\x1B[49m"; // Use default background color
}
}
}
internal class AnsiSystemConsole : IAnsiSystemConsole
{
private readonly TextWriter _textWriter;
/// <inheritdoc />
public AnsiSystemConsole(bool stdErr = false)
{
_textWriter = stdErr ? System.Console.Error : System.Console.Out;
}
public void Write(string message)
{
_textWriter.Write(message);
}
}
}
namespace Microsoft.Extensions.Logging.Console
{
internal interface IAnsiSystemConsole
{
void Write(string message);
}
public interface IConsole
{
void Write(string message, ConsoleColor? background, ConsoleColor? foreground);
void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground);
void Flush();
}
internal class WindowsLogConsole : IConsole
{
private readonly TextWriter _textWriter;
/// <inheritdoc />
public WindowsLogConsole(bool stdErr = false)
{
_textWriter = stdErr ? System.Console.Error : System.Console.Out;
}
private bool SetColor(ConsoleColor? background, ConsoleColor? foreground)
{
if (background.HasValue)
{
System.Console.BackgroundColor = background.Value;
}
if (foreground.HasValue)
{
System.Console.ForegroundColor = foreground.Value;
}
return background.HasValue || foreground.HasValue;
}
private void ResetColor()
{
System.Console.ResetColor();
}
public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
{
var colorChanged = SetColor(background, foreground);
_textWriter.Write(message);
if (colorChanged)
{
ResetColor();
}
}
public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
{
var colorChanged = SetColor(background, foreground);
_textWriter.WriteLine(message);
if (colorChanged)
{
ResetColor();
}
}
public void Flush()
{
// No action required as for every write, data is sent directly to the console
// output stream
}
}
}
namespace Microsoft.Extensions.Logging.Abstractions.Internal
{
/// <summary>
/// An empty scope without any logic
/// </summary>
internal class NullScope : IDisposable
{
public static NullScope Instance { get; } = new NullScope();
private NullScope()
{
}
/// <inheritdoc />
public void Dispose()
{
}
}
}
#else
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Provides programmatic configuration for JSON formatters using Newtonsoft.JSON.
/// </summary>
public class MvcNewtonsoftJsonOptions
{
IOptions<MvcJsonOptions> jsonOptions;
public MvcNewtonsoftJsonOptions(IOptions<MvcJsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions;
}
public JsonSerializerSettings SerializerSettings => this.jsonOptions.Value.SerializerSettings;
}
}
#endif

View File

@ -1,11 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<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" />
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.2" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.19515.63" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc1.final" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.19515.63" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
</ItemGroup>
</Project>

View File

@ -45,7 +45,11 @@ namespace BTCPayServer.Data
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
{
#if NETCOREAPP21
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
#else
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
#endif
{
}

View File

@ -73,7 +73,6 @@ namespace BTCPayServer.Data
}
[NotMapped]
[Obsolete]
public string Role
{
get; set;
@ -88,9 +87,6 @@ namespace BTCPayServer.Data
public string DefaultCrypto { get; set; }
public List<PairedSINData> PairedSINs { get; set; }
public IEnumerable<APIKeyData> APIKeys { get; set; }
[NotMapped]
public List<Claim> AdditionalClaims { get; set; } = new List<Claim>();
}
public enum NetworkFeeMode

View File

@ -1,17 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<Folder Include="Providers\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
</ItemGroup>

View File

@ -90,10 +90,10 @@ namespace BTCPayServer.Services.Rates
public async Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
{
var result = new ExchangeRates();
var symbols = await GetSymbolsAsync();
var symbols = await GetSymbolsAsync(cancellationToken);
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => _Helper.NormalizeSymbol(s)).ToList();
var csvPairsList = string.Join(",", normalizedPairsList);
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", csvPairsList } });
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", csvPairsList } }, cancellationToken: cancellationToken);
var tickers = new List<KeyValuePair<string, ExchangeTicker>>();
foreach (string symbol in symbols)
{
@ -151,7 +151,7 @@ namespace BTCPayServer.Services.Rates
};
}
private async Task<string[]> GetSymbolsAsync()
private async Task<string[]> GetSymbolsAsync(CancellationToken cancellationToken)
{
if (_LastSymbolUpdate != null && DateTimeOffset.UtcNow - _LastSymbolUpdate.Value < TimeSpan.FromDays(0.5))
{
@ -159,7 +159,7 @@ namespace BTCPayServer.Services.Rates
}
else
{
JToken json = await MakeJsonRequestAsync<JToken>("/0/public/AssetPairs");
JToken json = await MakeJsonRequestAsync<JToken>("/0/public/AssetPairs", cancellationToken: cancellationToken);
var symbols = (from prop in json.Children<JProperty>() where !prop.Name.Contains(".d", StringComparison.OrdinalIgnoreCase) select prop.Name).ToArray();
_Symbols = symbols;
_LastSymbolUpdate = DateTimeOffset.UtcNow;

View File

@ -113,9 +113,9 @@ namespace BTCPayServer.Services.Rates
Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_COINAVERAGE"), Authenticator = _CoinAverageSettings });
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
Providers.Add("ndax", new NdaxRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_NDAX")));
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
Providers.Add("ndax", new NdaxRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_NDAX")));
// Those exchanges make multiple requests when calling GetTickers so we remove them
//DirectProviders.Add("gemini", new ExchangeSharpRateProvider("gemini", new ExchangeGeminiAPI()));
@ -172,7 +172,7 @@ namespace BTCPayServer.Services.Rates
// Add other exchanges supported here
exchanges.Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average", $"https://apiv2.bitcoinaverage.com/indices/global/ticker/short"));
exchanges.Add(new CoinAverageExchange("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD"));
exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker"));
//exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker")); Buggy
exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices"));
return exchanges;

View File

@ -4,8 +4,9 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Primitives;
using System.Security.Claims;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
using Xunit.Abstractions;
@ -16,24 +17,26 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenQA.Selenium;
using Microsoft.AspNetCore.Identity;
namespace BTCPayServer.Tests
{
public class AuthenticationTests
{
public const int TestTimeout = TestUtils.TestTimeout;
public AuthenticationTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task GetRedirectedToLoginPathOnChallenge()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var client = tester.PayTester.HttpClient;
//Wallets endpoint is protected
var response = await client.GetAsync("wallets");
@ -49,13 +52,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanGetOpenIdConfiguration()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
using (var response =
await tester.PayTester.HttpClient.GetAsync("/.well-known/openid-configuration"))
{
@ -70,16 +73,17 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanUseNonInteractiveFlows()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.MakeAdmin();
var token = await RegisterPasswordClientAndGetAccessToken(user, null, tester);
await TestApiAgainstAccessToken(token, tester, user);
token = await RegisterPasswordClientAndGetAccessToken(user, "secret", tester);
@ -89,17 +93,18 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Selenium", "Selenium")]
[Fact]
public async Task CanUseImplicitFlow()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
var tester = s.Server;
var user = tester.NewAccount();
user.GrantAccess();
await user.MakeAdmin();
var id = Guid.NewGuid().ToString();
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
var openIdClient = await user.RegisterOpenIdClient(
@ -108,10 +113,11 @@ namespace BTCPayServer.Tests
ClientId = id,
DisplayName = id,
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Implicit},
RedirectUris = {redirecturi}
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()}");
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid server_management store_management&nonce={Guid.NewGuid().ToString()}");
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.FindElement(By.Id("consent-yes")).Click();
@ -126,15 +132,52 @@ namespace BTCPayServer.Tests
results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
await TestApiAgainstAccessToken(results["access_token"], tester, user);
var stores = await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
$"api/test/me/stores",
tester.PayTester.HttpClient);
Assert.NotEmpty(stores);
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/me/stores/{stores[0].Id}/can-edit",
tester.PayTester.HttpClient));
//we dont ask for consent after acquiring it the first time for the same scopes.
LogoutFlow(tester, id, s);
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
Assert.Throws<NoSuchElementException>(() => s.Driver.FindElement(By.Id("consent-yes")));
results = url.Split("#").Last().Split("&")
s.Driver.AssertElementNotFound(By.Id("consent-yes"));
// Let's asks without scopes
LogoutFlow(tester, id, s);
id = Guid.NewGuid().ToString();
openIdClient = await user.RegisterOpenIdClient(
new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions = { OpenIddictConstants.Permissions.GrantTypes.Implicit },
RedirectUris = { redirecturi },
});
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);
s.Driver.FindElement(By.Id("consent-yes")).Click();
results = s.Driver.Url.Split("#").Last().Split("&")
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
await TestApiAgainstAccessToken(results["access_token"], tester, user);
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{
await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
$"api/test/me/stores",
tester.PayTester.HttpClient);
});
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{
await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/me/stores/{stores[0].Id}/can-edit",
tester.PayTester.HttpClient);
});
}
}
@ -148,17 +191,18 @@ namespace BTCPayServer.Tests
}
[Fact(Timeout = TestTimeout)]
[Trait("Selenium", "Selenium")]
[Fact]
public async Task CanUseCodeFlow()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
var tester = s.Server;
var user = tester.NewAccount();
user.GrantAccess();
await user.MakeAdmin();
var id = Guid.NewGuid().ToString();
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
var secret = "secret";
@ -175,7 +219,7 @@ namespace BTCPayServer.Tests
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()}");
$"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access server_management store_management&state={Guid.NewGuid().ToString()}");
s.Driver.Navigate().GoToUrl(authorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.FindElement(By.Id("consent-yes")).Click();
@ -205,7 +249,7 @@ namespace BTCPayServer.Tests
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
await TestApiAgainstAccessToken(result.AccessToken, tester, user);
@ -245,7 +289,7 @@ namespace BTCPayServer.Tests
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
Assert.NotEmpty(result.AccessToken);
Assert.Null(result.Error);
return result.AccessToken;
@ -275,7 +319,8 @@ namespace BTCPayServer.Tests
new KeyValuePair<string, string>("grant_type",
OpenIddictConstants.GrantTypes.ClientCredentials),
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
new KeyValuePair<string, string>("client_secret", secret)
new KeyValuePair<string, string>("client_secret", secret),
new KeyValuePair<string, string>("scope", "server_management store_management")
})
};
@ -285,7 +330,7 @@ namespace BTCPayServer.Tests
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
Assert.NotEmpty(result.AccessToken);
Assert.Null(result.Error);
return result.AccessToken;
@ -315,7 +360,8 @@ namespace BTCPayServer.Tests
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)
new KeyValuePair<string, string>("client_secret", secret),
new KeyValuePair<string, string>("scope", "server_management store_management")
})
};
@ -325,7 +371,7 @@ namespace BTCPayServer.Tests
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = JObject.Parse(content).ToObject<OpenIdConnectResponse>();
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
Assert.NotEmpty(result.AccessToken);
Assert.Null(result.Error);
return result.AccessToken;
@ -353,8 +399,7 @@ namespace BTCPayServer.Tests
$"api/test/me/stores/{testAccount.StoreId}/can-edit",
tester.PayTester.HttpClient));
Assert.Equal(testAccount.RegisterDetails.IsAdmin, await TestApiAgainstAccessToken<bool>(accessToken,
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
$"api/test/me/is-admin",
tester.PayTester.HttpClient));

View File

@ -2,17 +2,25 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">$(TargetFrameworkOverride)</TargetFramework>
<IsPackable>false</IsPackable>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
<LangVersion>7.2</LangVersion>
<UserSecretsId>AB0AC1DD-9D26-485B-9416-56A33F268117</UserSecretsId>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);NETCOREAPP21</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="76.0.3809.6801" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="78.0.3904.7000" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>

View File

@ -1,4 +1,4 @@
using BTCPayServer.Configuration;
using BTCPayServer.Configuration;
using System.Linq;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
@ -13,7 +13,6 @@ using BTCPayServer.Tests.Logging;
using BTCPayServer.Tests.Mocks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Routing;
@ -33,12 +32,13 @@ using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading;
using AspNet.Security.OpenIdConnect.Primitives;
using OpenIddict.Abstractions;
using Xunit;
using BTCPayServer.Services;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Threading.Tasks;
using AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes;
namespace BTCPayServer.Tests
{
@ -93,7 +93,7 @@ namespace BTCPayServer.Tests
public bool MockRates { get; set; } = true;
public void Start()
public async Task StartAsync()
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
@ -118,6 +118,7 @@ namespace BTCPayServer.Tests
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"ltc.explorer.cookiefile=0");
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
config.AppendLine($"debuglog=debug.log");
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
config.AppendLine($"sshpassword={SSHPassword}");
if (!string.IsNullOrEmpty(SSHKeyFile))
@ -130,7 +131,7 @@ namespace BTCPayServer.Tests
else if (!String.IsNullOrEmpty(Postgres))
config.AppendLine($"postgres=" + Postgres);
var confPath = Path.Combine(chainDirectory, "settings.config");
File.WriteAllText(confPath, config.ToString());
await File.WriteAllTextAsync(confPath, config.ToString());
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
HttpClient = new HttpClient();
@ -140,6 +141,7 @@ namespace BTCPayServer.Tests
_Host = new WebHostBuilder()
.UseConfiguration(conf)
.UseContentRoot(FindBTCPayServerDirectory())
.UseWebRoot(Path.Combine(FindBTCPayServerDirectory(), "wwwroot"))
.ConfigureServices(s =>
{
s.AddLogging(l =>
@ -154,7 +156,7 @@ namespace BTCPayServer.Tests
.UseKestrel()
.UseStartup<Startup>()
.Build();
_Host.StartWithTasksAsync().GetAwaiter().GetResult();
await _Host.StartWithTasksAsync();
var urls = _Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
foreach (var url in urls)
@ -229,12 +231,12 @@ namespace BTCPayServer.Tests
WaitSiteIsOperational().GetAwaiter().GetResult();
await WaitSiteIsOperational();
}
private async Task WaitSiteIsOperational()
{
using (var cts = new CancellationTokenSource(10_000))
using (var cts = new CancellationTokenSource(20_000))
{
var synching = WaitIsFullySynched(cts.Token);
var accessingHomepage = WaitCanAccessHomepage(cts.Token);
@ -264,7 +266,7 @@ namespace BTCPayServer.Tests
private string FindBTCPayServerDirectory()
{
var solutionDirectory = LanguageService.TryGetSolutionDirectoryInfo(Directory.GetCurrentDirectory());
var solutionDirectory = TestUtils.TryGetSolutionDirectoryInfo(Directory.GetCurrentDirectory());
return Path.Combine(solutionDirectory.FullName, "BTCPayServer");
}
@ -291,7 +293,7 @@ namespace BTCPayServer.Tests
public string SSHPassword { get; internal set; }
public string SSHKeyFile { get; internal set; }
public string SSHConnection { get; set; }
public T GetController<T>(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller
public T GetController<T>(string userId = null, string storeId = null, bool isAdmin = false) where T : Controller
{
var context = new DefaultHttpContext();
context.Request.Host = new HostString("127.0.0.1", Port);
@ -300,10 +302,10 @@ namespace BTCPayServer.Tests
if (userId != null)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(OpenIdConnectConstants.Claims.Subject, userId));
if (additionalClaims != null)
claims.AddRange(additionalClaims);
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), Policies.CookieAuthentication));
claims.Add(new Claim(OpenIddictConstants.Claims.Subject, userId));
if (isAdmin)
claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin));
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie));
}
if (storeId != null)
{

View File

@ -21,25 +21,26 @@ namespace BTCPayServer.Tests
{
public class ChangellyTests
{
public const int TestTimeout = 60_000;
public ChangellyTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = 60000)]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanSetChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.StoreData.GetStoreBlob();
var storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.Null(storeBlob.ChangellySettings);
var updateModel = new UpdateChangellySettingsViewModel()
@ -54,7 +55,7 @@ namespace BTCPayServer.Tests
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.StoreData.GetStoreBlob();
storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.NotNull(storeBlob.ChangellySettings);
Assert.NotNull(storeBlob.ChangellySettings);
Assert.IsType<ChangellySettings>(storeBlob.ChangellySettings);
@ -68,13 +69,13 @@ namespace BTCPayServer.Tests
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanToggleChangellyPaymentMethod()
public async Task CanToggleChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
@ -106,13 +107,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CannotUseChangellyApiWithoutChangellyPaymentMethodSet()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var changellyController =
@ -161,13 +162,13 @@ namespace BTCPayServer.Tests
};
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanGetCurrencyListFromChangelly()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
@ -194,13 +195,13 @@ namespace BTCPayServer.Tests
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanCalculateToAmountForChangelly()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
@ -224,7 +225,7 @@ namespace BTCPayServer.Tests
}
[Fact]
[Trait("Integration", "Integration")]
[Trait("Fast", "Fast")]
public void CanComputeBaseAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeBaseAmount(1, 1));
@ -234,7 +235,7 @@ namespace BTCPayServer.Tests
}
[Fact]
[Trait("Integration", "Integration")]
[Trait("Fast", "Fast")]
public void CanComputeCorrectAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeCorrectAmount(0.5m, 1, 2));

View File

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using BTCPayServer.Tests.Logging;
@ -17,79 +18,70 @@ namespace BTCPayServer.Tests
[Trait("Selenium", "Selenium")]
public class CheckoutUITests
{
public const int TestTimeout = TestUtils.TestTimeout;
public CheckoutUITests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
public void CanCreateInvoice()
{
using (var s = SeleniumTester.Create())
{
s.Start();
s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
s.CreateInvoice(store);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
s.Driver.Quit();
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
public async Task CanHandleRefundEmailForm()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
s.GoToStore(store.storeId, StoreNavPages.Checkout);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).ForceClick();
var emailAlreadyThereInvoiceId =s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
var invoiceId = s.CreateInvoice(store.storeName);
s.GoToInvoiceCheckout(invoiceId);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
Assert.True(s.Driver.FindElement(By.Id("emailAddressFormInput")).Displayed);
s.Driver.FindElement(By.Id("emailAddressFormInput")).SendKeys("xxx");
s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"))
.Click();
var formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
Assert.True(formInput.Displayed);
Assert.Contains("form-input-invalid", formInput.GetAttribute("class"));
formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
formInput.SendKeys("@g.com");
s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"))
.Click();
await Task.Delay(1000);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
actionButton.Click();
try // Sometimes the click only take the focus, without actually really clicking on it...
{
actionButton.Click();
}
catch { }
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
public async Task CanUseLanguageDropdown()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
@ -113,12 +105,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanUsePaymentMethodDropdown()
[Fact(Timeout = TestTimeout)]
public async Task CanUsePaymentMethodDropdown()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
@ -138,30 +131,31 @@ namespace BTCPayServer.Tests
Assert.Contains("BTC", currencyDropdownButton.Text);
currencyDropdownButton.Click();
var elements = s.Driver.FindElement(By.ClassName("vex-content"))
.FindElements(By.ClassName("vexmenuitem"));
var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
Assert.Equal(3, elements.Count);
elements.Single(element => element.Text.Contains("LTC")).Click();
Thread.Sleep(1000);
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("LTC", currencyDropdownButton.Text);
elements = s.Driver.FindElement(By.ClassName("vex-content"))
.FindElements(By.ClassName("vexmenuitem"));
currencyDropdownButton.Click();
elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
elements.Single(element => element.Text.Contains("Lightning")).Click();
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("Lightning", currencyDropdownButton.Text);
s.Driver.Quit();
}
}
[Fact]
public void CanUseLightningSatsFeature()
[Fact(Timeout = TestTimeout)]
public async Task CanUseLightningSatsFeature()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddInternalLightningNode("BTC");

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
using BTCPayServer.Data;
using System.Threading.Tasks;
namespace BTCPayServer.Tests
{
@ -19,17 +20,17 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public async void CanSetCoinSwitchPaymentMethod()
public async Task CanSetCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.StoreData.GetStoreBlob();
var storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.Null(storeBlob.CoinSwitchSettings);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
@ -41,7 +42,7 @@ namespace BTCPayServer.Tests
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.StoreData.GetStoreBlob();
storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.IsType<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);
@ -53,11 +54,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public async void CanToggleCoinSwitchPaymentMethod()
public async Task CanToggleCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);

View File

@ -37,11 +37,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public void CanCreateAndDeleteCrowdfundApp()
public async Task CanCreateAndDeleteCrowdfundApp()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var user2 = tester.NewAccount();
@ -81,7 +81,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -169,11 +169,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public void CanComputeCrowdfundModel()
public async Task CanComputeCrowdfundModel()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");

View File

@ -26,11 +26,10 @@ namespace BTCPayServer.Tests
_Host = new WebHostBuilder()
.Configure(app =>
{
app.Run(req =>
app.Run(async req =>
{
_Requests.Writer.WriteAsync(JsonConvert.DeserializeObject<JObject>(new StreamReader(req.Request.Body).ReadToEnd()), _Closed.Token);
await _Requests.Writer.WriteAsync(JsonConvert.DeserializeObject<JObject>(await new StreamReader(req.Request.Body).ReadToEndAsync()), _Closed.Token);
req.Response.StatusCode = 200;
return Task.CompletedTask;
});
})
.UseKestrel()

View File

@ -5,10 +5,12 @@ ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
WORKDIR /source
COPY nuget.config nuget.config
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
COPY BTCPayServer.Data/BTCPayServer.Data.csproj BTCPayServer.Data/BTCPayServer.Data.csproj
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
RUN dotnet restore BTCPayServer.Tests/BTCPayServer.Tests.csproj
@ -23,6 +25,6 @@ ENV SCREEN_HEIGHT 600 \
SCREEN_WIDTH 1200
COPY . .
RUN cd BTCPayServer.Tests && dotnet build
RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true
WORKDIR /source/BTCPayServer.Tests
ENTRYPOINT ["./docker-entrypoint.sh"]

View File

@ -1,4 +1,6 @@
using System.Text;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
@ -26,6 +28,11 @@ namespace BTCPayServer.Tests
try
{
Assert.NotEmpty(driver.FindElements(By.ClassName("navbar-brand")));
if (driver.PageSource.Contains("alert-danger"))
{
foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger")))
Assert.False(dangerAlert.Displayed, "No alert should be displayed");
}
}
catch
{
@ -68,20 +75,26 @@ namespace BTCPayServer.Tests
return Assert.IsType<T>(vr.Model);
}
public static IWebElement AssertElementNotFound(this IWebDriver driver, By by)
public static void AssertElementNotFound(this IWebDriver driver, By by)
{
try
{
var webElement = driver.FindElement(by);
Assert.False(webElement.Displayed);
return webElement;
}
catch (NoSuchElementException)
{
}
DateTimeOffset now = DateTimeOffset.Now;
var wait = SeleniumTester.ImplicitWait;
return null;
while (DateTimeOffset.UtcNow - now < wait)
{
try
{
var webElement = driver.FindElement(by);
if (!webElement.Displayed)
return;
}
catch (NoSuchElementException)
{
return;
}
Thread.Sleep(50);
}
Assert.False(true, "Elements was found");
}
}
}

View File

@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;

View File

@ -27,7 +27,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -49,7 +49,7 @@ namespace BTCPayServer.Tests
Assert.Equal("paid", invoice.Status);
});
var walletController = tester.PayTester.GetController<WalletsController>(user.UserId);
var walletController = user.GetController<WalletsController>();
var walletId = new WalletId(user.StoreId, "BTC");
var sendDestination = new Key().PubKey.Hash.GetAddress(user.SupportedNetwork.NBitcoinNetwork).ToString();
var sendModel = new WalletSendModel()

View File

@ -37,11 +37,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public void CanCreateViewUpdateAndDeletePaymentRequest()
public async Task CanCreateViewUpdateAndDeletePaymentRequest()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -98,7 +98,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -160,7 +160,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");

View File

@ -40,9 +40,9 @@ namespace BTCPayServer.Tests
}
public void Start()
public async Task StartAsync()
{
Server.Start();
await Server.StartAsync();
ChromeOptions options = new ChromeOptions();
var isDebug = !Server.PayTester.InContainer;
@ -65,20 +65,24 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation("Selenium: Using chrome driver");
Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri);
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
GoToRegister();
Driver.AssertNoError();
}
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
public string Link(string relativeLink)
{
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
}
public void GoToRegister()
{
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
}
public string RegisterNewUser(bool isAdmin = false)
{
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
Driver.FindElement(By.Id("Register")).Click();
Driver.FindElement(By.Id("Email")).SendKeys(usr);
Driver.FindElement(By.Id("Password")).SendKeys("123456");
Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
@ -141,6 +145,7 @@ namespace BTCPayServer.Tests
Assert.NotEmpty(links);
foreach (var l in links)
{
Logs.Tester.LogInformation($"Checking no error on {l}");
Driver.Navigate().GoToUrl(l);
Driver.AssertNoError();
}

View File

@ -8,24 +8,26 @@ using OpenQA.Selenium.Interactions;
using System.Linq;
using NBitcoin;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace BTCPayServer.Tests
{
[Trait("Selenium", "Selenium")]
public class ChromeTests
{
public const int TestTimeout = TestUtils.TestTimeout;
public ChromeTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
public void CanNavigateServerSettings()
[Fact(Timeout = TestTimeout)]
public async Task CanNavigateServerSettings()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.RegisterNewUser(true);
s.Driver.FindElement(By.Id("ServerSettings")).Click();
s.Driver.AssertNoError();
@ -34,18 +36,17 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void NewUserLogin()
[Fact(Timeout = TestTimeout)]
public async Task NewUserLogin()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
//Register & Log Out
var email = s.RegisterNewUser();
s.Driver.FindElement(By.Id("Logout")).Click();
s.Driver.AssertNoError();
s.Driver.FindElement(By.Id("Login")).Click();
s.Driver.AssertNoError();
Assert.Contains("Account/Login", s.Driver.Url);
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
@ -74,7 +75,6 @@ namespace BTCPayServer.Tests
s.Driver.AssertNoError();
//Log In With New Password
s.Driver.FindElement(By.Id("Login")).Click();
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("abc???");
s.Driver.FindElement(By.Id("LoginButton")).Click();
@ -89,18 +89,17 @@ namespace BTCPayServer.Tests
static void LogIn(SeleniumTester s, string email)
{
s.Driver.FindElement(By.Id("Login")).Click();
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
s.Driver.AssertNoError();
}
[Fact]
[Fact(Timeout = TestTimeout)]
public async Task CanUseSSHService()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("server/services/ssh", s.Driver.PageSource);
@ -132,12 +131,12 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanUseDynamicDns()
[Fact(Timeout = TestTimeout)]
public async Task CanUseDynamicDns()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
@ -182,12 +181,12 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanCreateStores()
[Fact(Timeout = TestTimeout)]
public async Task CanCreateStores()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
@ -206,7 +205,7 @@ namespace BTCPayServer.Tests
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.GoToRegister();
// When logged we should not be able to access store and invoice details
var bob = s.RegisterNewUser();
s.Driver.Navigate().GoToUrl(storeUrl);
@ -229,17 +228,85 @@ namespace BTCPayServer.Tests
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
s.Driver.AssertNoError();
// Alice should be able to delete the store
s.Logout();
LogIn(s, alice);
s.Driver.FindElement(By.Id("Stores")).Click();
s.Driver.FindElement(By.LinkText("Remove")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.FindElement(By.Id("Stores")).Click();
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
}
}
[Fact]
public void CanCreateAppPoS()
[Fact(Timeout = TestTimeout)]
public async Task CanUsePairing()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.Driver.Navigate().GoToUrl(s.Link("/api-access-request"));
Assert.Contains("ReturnUrl", s.Driver.Url);
s.GoToRegister();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("Tokens")).Click();
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
s.Driver.FindElement(By.Id("RequestPairing")).Click();
string pairingCode = AssertUrlHasPairingCode(s);
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
Assert.Contains(pairingCode, s.Driver.PageSource);
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
{
Price = 0.000000012m,
Currency = "USD",
FullNotifications = true
}, NBitpayClient.Facade.Merchant);
client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
var code = await client.RequestClientAuthorizationAsync("hehe", NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(code.CreateLink(s.Server.PayTester.ServerUri));
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
{
Price = 0.000000012m,
Currency = "USD",
FullNotifications = true
}, NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
s.Driver.FindElement(By.Id("RequestPairing")).Click();
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
AssertUrlHasPairingCode(s);
}
}
private static string AssertUrlHasPairingCode(SeleniumTester s)
{
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
var pairingCode = regex.Groups[1].Value;
return pairingCode;
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreateAppPoS()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser();
var store = s.CreateNewStore();
@ -258,12 +325,12 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanCreateAppCF()
[Fact(Timeout = TestTimeout)]
public async Task CanCreateAppCF()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme();
@ -286,12 +353,12 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanCreatePayRequest()
[Fact(Timeout = TestTimeout)]
public async Task CanCreatePayRequest()
{
using (var s = SeleniumTester.Create())
{
s.Start();
await s.StartAsync();
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme();
@ -309,13 +376,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanManageWallet()
[Fact(Timeout = TestTimeout)]
public async Task CanManageWallet()
{
using (var s = SeleniumTester.Create())
{
s.Start();
s.RegisterNewUser();
await s.StartAsync();
s.RegisterNewUser(true);
s.CreateNewStore();
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
@ -331,6 +398,10 @@ namespace BTCPayServer.Tests
s.ClickOnAllSideMenus();
// Make sure we can rescan, because we are admin!
s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
Assert.Contains("The batch size make sure", s.Driver.PageSource);
// We setup the fingerprint and the account key path
s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");

View File

@ -82,9 +82,9 @@ namespace BTCPayServer.Tests
get; set;
}
public void Start()
public Task StartAsync()
{
PayTester.Start();
return PayTester.StartAsync();
}
/// <summary>
@ -166,12 +166,14 @@ namespace BTCPayServer.Tests
public void Dispose()
{
Logs.Tester.LogInformation("Disposing the BTCPayTester...");
foreach (var store in Stores)
{
Xunit.Assert.True(PayTester.StoreRepository.DeleteStore(store).GetAwaiter().GetResult());
}
if (PayTester != null)
PayTester.Dispose();
Logs.Tester.LogInformation("BTCPayTester disposed");
}
}
}

View File

@ -12,7 +12,6 @@ using BTCPayServer.Storage.ViewModels;
using BTCPayServer.Tests.Logging;
using DBriize.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources;
@ -31,11 +30,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public async void CanConfigureStorage()
public async Task CanConfigureStorage()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
@ -118,7 +117,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
@ -146,7 +145,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
@ -211,8 +210,8 @@ namespace BTCPayServer.Tests
TimeAmount = 1,
TimeType = ServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes
}));
Assert.True(tmpLinkGenerate.RouteValues.ContainsKey("StatusMessage"));
var statusMessageModel = new StatusMessageModel(tmpLinkGenerate.RouteValues["StatusMessage"].ToString());
var statusMessageModel = controller.TempData.GetStatusMessageModel();
Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
var url = statusMessageModel.Html.Substring(index).ReplaceMultiple(new Dictionary<string, string>()
@ -222,12 +221,14 @@ namespace BTCPayServer.Tests
//verify tmpfile is available and the same
data = await net.DownloadStringTaskAsync(new Uri(url));
Assert.Equal(fileContent, data);
//delete file
Assert.Equal(StatusMessageModel.StatusSeverity.Success, new StatusMessageModel(Assert
.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId))
.RouteValues["statusMessage"].ToString()).Severity);
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
controller.TempData.GetStatusMessageModel();
Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
//attempt to fetch deleted file
viewFilesViewModel =

View File

@ -20,6 +20,7 @@ using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Data;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using Microsoft.AspNetCore.Identity;
namespace BTCPayServer.Tests
{
@ -37,6 +38,13 @@ namespace BTCPayServer.Tests
GrantAccessAsync().GetAwaiter().GetResult();
}
public async Task MakeAdmin()
{
var userManager = parent.PayTester.GetService<UserManager<ApplicationUser>>();
var u = await userManager.FindByIdAsync(UserId);
await userManager.AddToRoleAsync(u, Roles.ServerAdmin);
}
public void Register()
{
RegisterAsync().GetAwaiter().GetResult();
@ -78,7 +86,8 @@ namespace BTCPayServer.Tests
public T GetController<T>(bool setImplicitStore = true) where T : Controller
{
return parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null);
var controller = parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null, IsAdmin);
return controller;
}
public async Task CreateStoreAsync()
@ -140,6 +149,7 @@ namespace BTCPayServer.Tests
{
get; set;
}
public bool IsAdmin { get; internal set; }
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType)
{
@ -172,7 +182,7 @@ namespace BTCPayServer.Tests
public async Task<BTCPayOpenIdClient> RegisterOpenIdClient(OpenIddictApplicationDescriptor descriptor, string secret = null)
{
var openIddictApplicationManager = parent.PayTester.GetService<OpenIddictApplicationManager<BTCPayOpenIdClient>>();
var client = new BTCPayOpenIdClient {ApplicationUserId = UserId};
var client = new BTCPayOpenIdClient { Id = Guid.NewGuid().ToString(), ApplicationUserId = UserId};
await openIddictApplicationManager.PopulateAsync(client, descriptor);
await openIddictApplicationManager.CreateAsync(client, secret);
return client;

View File

@ -5,13 +5,31 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
#if NETCOREAPP21
using Microsoft.AspNetCore.Http.Internal;
#endif
using Xunit.Sdk;
using System.Linq;
namespace BTCPayServer.Tests
{
public static class TestUtils
{
#if DEBUG && !SHORT_TIMEOUT
public const int TestTimeout = 600_000;
#else
public const int TestTimeout = 60_000;
#endif
public static DirectoryInfo TryGetSolutionDirectoryInfo(string currentPath = null)
{
var directory = new DirectoryInfo(
currentPath ?? Directory.GetCurrentDirectory());
while (directory != null && !directory.GetFiles("*.sln").Any())
{
directory = directory.Parent;
}
return directory;
}
public static FormFile GetFormFile(string filename, string content)
{
File.WriteAllText(filename, content);
@ -44,7 +62,7 @@ namespace BTCPayServer.Tests
formFile.ContentDisposition = $"form-data; name=\"file\"; filename=\"{fileInfo.Name}\"";
return formFile;
}
public static void Eventually(Action act, int ms = 200000)
public static void Eventually(Action act, int ms = 20_000)
{
CancellationTokenSource cts = new CancellationTokenSource(ms);
while (true)

View File

@ -15,9 +15,7 @@ using System.IO;
using Newtonsoft.Json.Linq;
using BTCPayServer.Controllers;
using Microsoft.AspNetCore.Mvc;
using BTCPayServer.Authentication;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Extensions;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using BTCPayServer.Services.Rates;
@ -57,16 +55,17 @@ using System.Security;
using System.Runtime.CompilerServices;
using System.Net;
using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Services.U2F.Models;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NBXplorer.DerivationStrategy;
using BTCPayServer.U2F.Models;
using BTCPayServer.Security.Bitpay;
namespace BTCPayServer.Tests
{
public class UnitTest1
{
public const int TestTimeout = 60_000;
public UnitTest1(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
@ -77,7 +76,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public async Task CheckNoDeadLink()
{
var views = Path.Combine(LanguageService.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "Views");
var views = Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "Views");
var viewFiles = Directory.EnumerateFiles(views, "*.cshtml", SearchOption.AllDirectories).ToArray();
Assert.NotEmpty(viewFiles);
Regex regex = new Regex("href=\"(http.*?)[\"#]");
@ -428,7 +427,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var response = await tester.PayTester.HttpClient.GetAsync("");
Assert.True(response.IsSuccessStatusCode);
}
@ -473,11 +472,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public void CanAcceptInvoiceWithTolerance2()
public async Task CanAcceptInvoiceWithTolerance2()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -539,7 +538,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess();
@ -552,7 +551,8 @@ namespace BTCPayServer.Tests
ConnectionString = "type=charge;server=" + tester.MerchantCharge.Client.Uri.AbsoluteUri,
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
}, "test", "BTC").GetAwaiter().GetResult();
Assert.DoesNotContain("Error", ((LightningNodeViewModel)Assert.IsType<ViewResult>(testResult).Model).StatusMessage, StringComparison.OrdinalIgnoreCase);
Assert.False(storeController.TempData.ContainsKey(WellKnownTempData.ErrorMessage));
storeController.TempData.Clear();
Assert.True(storeController.ModelState.IsValid);
Assert.IsType<RedirectToActionResult>(storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
@ -599,7 +599,7 @@ namespace BTCPayServer.Tests
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess();
@ -636,23 +636,24 @@ namespace BTCPayServer.Tests
});
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanUseServerInitiatedPairingCode()
public async Task CanUseServerInitiatedPairingCode()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.Register();
acc.CreateStore();
var controller = acc.GetController<StoresController>();
var token = (RedirectToActionResult)controller.CreateToken(new Models.StoreViewModels.CreateTokenViewModel()
var token = (RedirectToActionResult)await controller.CreateToken2(new Models.StoreViewModels.CreateTokenViewModel()
{
Label = "bla",
PublicKey = null
}).GetAwaiter().GetResult();
PublicKey = null,
StoreId = acc.StoreId
});
var pairingCode = (string)token.RouteValues["pairingCode"];
@ -661,7 +662,7 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanSendIPN()
{
@ -669,7 +670,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess();
acc.RegisterDerivationScheme("BTC");
@ -729,13 +730,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CantPairTwiceWithSamePubkey()
public async Task CantPairTwiceWithSamePubkey()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.Register();
acc.CreateStore();
@ -746,12 +747,12 @@ namespace BTCPayServer.Tests
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
acc.CreateStore();
var store2 = acc.GetController<StoresController>();
store2.Pair(pairingCode.ToString(), store2.StoreData.Id).GetAwaiter().GetResult();
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage, StringComparison.CurrentCultureIgnoreCase);
await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id);
Assert.Contains(nameof(PairingResult.ReusedKey), (string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase);
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanSolveTheDogesRatesOnKraken()
{
@ -768,20 +769,20 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanRescanWallet()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess();
acc.RegisterDerivationScheme("BTC", true);
var btcDerivationScheme = acc.DerivationScheme;
acc.RegisterDerivationScheme("LTC", true);
var walletController = tester.PayTester.GetController<WalletsController>(acc.UserId);
var walletController = acc.GetController<WalletsController>();
WalletId walletId = new WalletId(acc.StoreId, "LTC");
var rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(walletController.WalletRescan(walletId).Result).Model);
Assert.False(rescan.Ok);
@ -790,8 +791,9 @@ namespace BTCPayServer.Tests
Assert.False(rescan.IsServerAdmin);
walletId = new WalletId(acc.StoreId, "BTC");
var serverAdminClaim = new[] { new Claim(Policies.CanModifyServerSettings.Key, "true") };
walletController = tester.PayTester.GetController<WalletsController>(acc.UserId, additionalClaims: serverAdminClaim);
acc.IsAdmin = true;
walletController = acc.GetController<WalletsController>();
rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(walletController.WalletRescan(walletId).Result).Model);
Assert.True(rescan.Ok);
Assert.True(rescan.IsFullySync);
@ -861,13 +863,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanListInvoices()
public async Task CanListInvoices()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess();
acc.RegisterDerivationScheme("BTC");
@ -911,43 +913,43 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public void CanGetRates()
public async Task CanGetRates()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess();
acc.RegisterDerivationScheme("BTC");
acc.RegisterDerivationScheme("LTC");
var rateController = acc.GetController<RateController>();
var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", acc.StoreId, default)
var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", default)
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
Assert.NotNull(GetBaseCurrencyRatesResult);
Assert.NotNull(GetBaseCurrencyRatesResult.Data);
Assert.Equal(2, GetBaseCurrencyRatesResult.Data.Length);
Assert.Single(GetBaseCurrencyRatesResult.Data.Where(o => o.Code == "LTC"));
var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default)
var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
// We don't have any default currencies, so this should be failing
Assert.Null(GetRatesResult?.Data);
var store = acc.GetController<StoresController>();
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates(acc.StoreId)).Model);
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates()).Model);
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
store.Rates(ratesVM).Wait();
store = acc.GetController<StoresController>();
rateController = acc.GetController<RateController>();
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default)
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
// Now we should have a result
Assert.NotNull(GetRatesResult);
Assert.NotNull(GetRatesResult.Data);
Assert.Equal(2, GetRatesResult.Data.Length);
var GetCurrencyPairRateResult = JObject.Parse(((JsonResult)rateController.GetCurrencyPairRate("BTC", "LTC", acc.StoreId, default)
var GetCurrencyPairRateResult = JObject.Parse(((JsonResult)rateController.GetCurrencyPairRate("BTC", "LTC", default)
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate>>();
Assert.NotNull(GetCurrencyPairRateResult);
@ -958,7 +960,9 @@ namespace BTCPayServer.Tests
var rates = acc.BitPay.GetRates();
HttpClient client = new HttpClient();
// Unauthentified requests should also be ok
var response = client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}").GetAwaiter().GetResult();
var response = await client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}");
response.EnsureSuccessStatusCode();
response = await client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/rates?storeId={acc.StoreId}");
response.EnsureSuccessStatusCode();
}
}
@ -969,13 +973,13 @@ namespace BTCPayServer.Tests
Assert.Equal(expected, result.Invoices.Any(i => i.InvoiceId == invoiceId));
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanRBFPayment()
public async Task CanRBFPayment()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -1039,7 +1043,7 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
public void CanParseFilter()
{
@ -1067,7 +1071,7 @@ namespace BTCPayServer.Tests
Assert.Equal("hekki", search.TextSearch);
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
public void CanParseFingerprint()
{
@ -1084,13 +1088,13 @@ namespace BTCPayServer.Tests
Assert.Equal(f1.ToString(), f2.ToString());
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CheckCORSSetOnBitpayAPI()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
foreach (var req in new[]
{
"invoices/",
@ -1122,13 +1126,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void TestAccessBitpayAPI()
public async Task TestAccessBitpayAPI()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
user.GrantAccess();
@ -1138,7 +1142,7 @@ namespace BTCPayServer.Tests
// Test request pairing code client side
var storeController = user.GetController<StoresController>();
storeController.CreateToken(new CreateTokenViewModel()
storeController.CreateToken(user.StoreId, new CreateTokenViewModel()
{
Label = "test2",
StoreId = user.StoreId
@ -1199,14 +1203,14 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanUseExchangeSpecificRate()
public async Task CanUseExchangeSpecificRate()
{
using (var tester = ServerTester.Create())
{
tester.PayTester.MockRates = false;
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -1227,7 +1231,7 @@ namespace BTCPayServer.Tests
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
{
var storeController = user.GetController<StoresController>();
var vm = (RatesViewModel)((ViewResult)storeController.Rates(user.StoreId)).Model;
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
vm.PreferredExchange = exchange;
storeController.Rates(vm).Wait();
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
@ -1242,23 +1246,23 @@ namespace BTCPayServer.Tests
return invoice2.CryptoInfo[0].Rate;
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanUseAnyoneCanCreateInvoice()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
Logs.Tester.LogInformation("StoreId without anyone can create invoice = 401");
Logs.Tester.LogInformation("StoreId without anyone can create invoice = 403");
var response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices?storeId={user.StoreId}")
{
Content = new StringContent("{\"Price\": 5000, \"currency\": \"USD\"}", Encoding.UTF8, "application/json"),
});
Assert.Equal(401, (int)response.StatusCode);
Assert.Equal(403, (int)response.StatusCode);
Logs.Tester.LogInformation("No store without anyone can create invoice = 404 because the bitpay API can't know the storeid");
response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices")
@ -1269,12 +1273,12 @@ namespace BTCPayServer.Tests
user.ModifyStore(s => s.AnyoneCanCreateInvoice = true);
Logs.Tester.LogInformation("Bad store with anyone can create invoice = 401");
Logs.Tester.LogInformation("Bad store with anyone can create invoice = 403");
response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices?storeId=badid")
{
Content = new StringContent("{\"Price\": 5000, \"currency\": \"USD\"}", Encoding.UTF8, "application/json"),
});
Assert.Equal(401, (int)response.StatusCode);
Assert.Equal(403, (int)response.StatusCode);
Logs.Tester.LogInformation("Good store with anyone can create invoice = 200");
response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices?storeId={user.StoreId}")
@ -1285,13 +1289,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanTweakRate()
public async Task CanTweakRate()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -1308,7 +1312,7 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice);
var storeController = user.GetController<StoresController>();
var vm = (RatesViewModel)((ViewResult)storeController.Rates(user.StoreId)).Model;
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
Assert.Equal(0.0, vm.Spread);
vm.Spread = 40;
storeController.Rates(vm).Wait();
@ -1330,13 +1334,13 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanHaveLTCOnlyStore()
public async Task CanHaveLTCOnlyStore()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("LTC");
@ -1395,19 +1399,19 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanModifyRates()
public async Task CanModifyRates()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var store = user.GetController<StoresController>();
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
Assert.False(rateVm.ShowScripting);
Assert.Equal("coinaverage", rateVm.PreferredExchange);
Assert.Equal(0.0, rateVm.Spread);
@ -1415,7 +1419,7 @@ namespace BTCPayServer.Tests
rateVm.PreferredExchange = "bitflyer";
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
Assert.Equal("bitflyer", rateVm.PreferredExchange);
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
@ -1432,7 +1436,7 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(store.ShowRateRulesPost(true).Result);
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
store = user.GetController<StoresController>();
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
Assert.Equal(rateVm.StoreId, user.StoreId);
Assert.Equal(rateVm.DefaultScript, rateVm.Script);
Assert.True(rateVm.ShowScripting);
@ -1450,20 +1454,20 @@ namespace BTCPayServer.Tests
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
store = user.GetController<StoresController>();
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
Assert.Equal(50, rateVm.Spread);
Assert.True(rateVm.ShowScripting);
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanPayWithTwoCurrencies()
public async Task CanPayWithTwoCurrencies()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -1676,11 +1680,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public void CanAddDerivationSchemes()
public async Task CanAddDerivationSchemes()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -1820,7 +1824,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess();
@ -1865,7 +1869,7 @@ namespace BTCPayServer.Tests
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2092,7 +2096,7 @@ noninventoryitem:
Assert.True(client.WaitAllRunning(default).Wait(100));
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
public void PosDataParser_ParsesCorrectly()
{
@ -2115,13 +2119,13 @@ noninventoryitem:
});
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task PosDataParser_ParsesCorrectly_Slower()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2161,9 +2165,9 @@ noninventoryitem:
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanExportInvoicesJson()
public async Task CanExportInvoicesJson()
{
decimal GetFieldValue(string input, string fieldName)
{
@ -2173,7 +2177,7 @@ noninventoryitem:
}
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2236,13 +2240,13 @@ noninventoryitem:
});
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanChangeNetworkFeeMode()
public async Task CanChangeNetworkFeeMode()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2317,13 +2321,13 @@ noninventoryitem:
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanExportInvoicesCsv()
public async Task CanExportInvoicesCsv()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2357,13 +2361,13 @@ noninventoryitem:
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanCreateAndDeleteApps()
public async Task CanCreateAndDeleteApps()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var user2 = tester.NewAccount();
@ -2394,13 +2398,13 @@ noninventoryitem:
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanCreateStrangeInvoice()
public async Task CanCreateStrangeInvoice()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2455,13 +2459,13 @@ noninventoryitem:
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void InvoiceFlowThroughDifferentStatesCorrectly()
public async Task InvoiceFlowThroughDifferentStatesCorrectly()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2622,22 +2626,7 @@ noninventoryitem:
}
}
//[Fact]
//[Trait("Integration", "Integration")]
// 29 january, the exchange is down
//public void CheckQuadrigacxRateProvider()
//{
// var quadri = new QuadrigacxRateProvider();
// var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
// Assert.NotEmpty(rates);
// Assert.NotEqual(0.0m, rates.First().BidAsk.Bid);
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid);
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid);
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid);
// Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
//}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanQueryDirectProviders()
{
@ -2679,7 +2668,7 @@ noninventoryitem:
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public void CanGetRateCryptoCurrenciesByDefault()
{
@ -2734,13 +2723,13 @@ noninventoryitem:
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CheckLogsRoute()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -2807,7 +2796,7 @@ noninventoryitem:
return name;
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
public void CheckRatesProvider()
{
@ -2893,50 +2882,14 @@ noninventoryitem:
Assert.True(DerivationSchemeSettings.TryParseFromColdcard("{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}", testnet, out settings));
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit);
}
[Fact]
[Trait("Fast", "Fast")]
public void CheckParseStatusMessageModel()
{
var legacyStatus = "Error: some bad shit happened";
var parsed = new StatusMessageModel(legacyStatus);
Assert.Equal(legacyStatus, parsed.Message);
Assert.Equal(StatusMessageModel.StatusSeverity.Error, parsed.Severity);
var legacyStatus2 = "Some normal shit happened";
parsed = new StatusMessageModel(legacyStatus2);
Assert.Equal(legacyStatus2, parsed.Message);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, parsed.Severity);
var newStatus = new StatusMessageModel()
{
Html = "<a href='xxx'>something new</a>",
Severity = StatusMessageModel.StatusSeverity.Info
};
parsed = new StatusMessageModel(newStatus.ToString());
Assert.Null(parsed.Message);
Assert.Equal(newStatus.Html, parsed.Html);
Assert.Equal(StatusMessageModel.StatusSeverity.Info, parsed.Severity);
var newStatus2 = new StatusMessageModel()
{
Message = "something new",
Severity = StatusMessageModel.StatusSeverity.Success
};
parsed = new StatusMessageModel(newStatus2.ToString());
Assert.Null(parsed.Html);
Assert.Equal(newStatus2.Message, parsed.Message);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, parsed.Severity);
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateInvoiceWithSpecificPaymentMethods()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess();
@ -2965,13 +2918,13 @@ noninventoryitem:
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded()
{
using (var tester = ServerTester.Create())
{
tester.Start();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
@ -2991,13 +2944,11 @@ noninventoryitem:
var addRequest = Assert.IsType<AddU2FDeviceViewModel>(Assert.IsType<ViewResult>(manageController.AddU2FDevice("label")).Model);
//name should match the one provided in beginning
Assert.Equal("label",addRequest.Name);
//sending an invalid response model back to server, should error out
var statusMessage = Assert
.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addRequest))
.RouteValues["StatusMessage"].ToString();
Assert.NotNull(statusMessage);
Assert.Equal(StatusMessageModel.StatusSeverity.Error, new StatusMessageModel(statusMessage).Severity);
//sending an invalid response model back to server, should error out
Assert.IsType<RedirectToActionResult>(await manageController.AddU2FDevice(addRequest));
var statusModel = manageController.TempData.GetStatusMessageModel();
Assert.Equal(StatusMessageModel.StatusSeverity.Error, statusModel.Severity);
var contextFactory = tester.PayTester.GetService<ApplicationDbContextFactory>();
@ -3006,6 +2957,7 @@ noninventoryitem:
{
var newDevice = new U2FDevice()
{
Id = Guid.NewGuid().ToString(),
Name = "fake",
Counter = 0,
KeyHandle = UTF8Encoding.UTF8.GetBytes("fake"),
@ -3039,7 +2991,8 @@ noninventoryitem:
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
{
var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString();
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetAddress() == h) != null;
return (ctx.AddressInvoices.Where(i => i.InvoiceDataId == invoice.Id).ToArrayAsync().GetAwaiter().GetResult())
.Where(i => i.GetAddress() == h).Any();
}
}
}

View File

@ -31,7 +31,7 @@ namespace BTCPayServer.Tests
var json = await client.GetTransifexAsync("https://api.transifex.com/organizations/btcpayserver/projects/btcpayserver/resources/enjson/");
var langs = new[] { "en" }.Concat(((JObject)json["stats"]).Properties().Select(n => n.Name)).ToArray();
var langsDir = Path.Combine(Services.LanguageService.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "wwwroot", "locales");
var langsDir = Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "wwwroot", "locales");
JObject sourceLang = null;
Task.WaitAll(langs.Select(async l =>

View File

@ -0,0 +1,28 @@
version: "3"
services:
monerod:
image: kukks/docker-monero:test
restart: unless-stopped
container_name: xmr_monerod
entrypoint: monerod --fixed-difficulty 100 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --non-interactive --block-notify="/scripts/notifier.sh https://127.0.0.1:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --testnet --no-igd --hide-my-port --no-sync --offline
volumes:
- "monero_data:/home/monero/.bitmonero"
ports:
- "18081:18081"
monero_wallet:
image: kukks/docker-monero:test
restart: unless-stopped
container_name: xmr_wallet_rpc
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=127.0.0.1:18081 --wallet-file=/wallet/wallet.keys --tx-notify="/scripts/notifier.sh https://127.0.0.1:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
ports:
- "18082:18082"
volumes:
- "monero_wallet:/wallet"
depends_on:
- monerod
volumes:
monero_data:
monero_wallet:

View File

@ -16,7 +16,6 @@ services:
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_DB: "Postgres"
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
TESTS_PORT: 80
TESTS_HOSTNAME: tests
TESTS_RUN_EXTERNAL_INTEGRATION: ${TESTS_RUN_EXTERNAL_INTEGRATION:-false}
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
@ -77,7 +76,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.0.0.57
image: nicolasdorier/nbxplorer:2.0.0.62
restart: unless-stopped
ports:
- "32838:32838"
@ -128,7 +127,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.7.1-dev
image: btcpayserver/lightning:v0.7.3-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -141,7 +140,7 @@ services:
announce-addr=customer_lightningd
log-level=debug
funding-confirms=1
dev-broadcast-interval=1000
dev-fast-gossip
dev-bitcoind-poll=1
ports:
- "30992:9835" # api port
@ -155,7 +154,7 @@ services:
- bitcoind
lightning-charged:
image: shesek/lightning-charge:0.4.6-standalone
image: shesek/lightning-charge:0.4.11-standalone
restart: unless-stopped
environment:
NETWORK: regtest
@ -175,7 +174,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.7.1-dev
image: btcpayserver/lightning:v0.7.3-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@ -187,7 +186,8 @@ services:
announce-addr=merchant_lightningd
funding-confirms=1
log-level=debug
dev-broadcast-interval=1000
dev-fast-gossip
dev-bitcoind-poll=1
ports:
- "30993:9835" # api port
expose:
@ -224,7 +224,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.7.0-beta
image: btcpayserver/lnd:v0.7.1-beta-withseed
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -242,7 +242,6 @@ services:
bitcoin.defaultchanconfs=1
no-macaroons=1
debuglevel=debug
noseedbackup=1
trickledelay=1000
ports:
- "53280:8080"
@ -255,7 +254,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.7.0-beta
image: btcpayserver/lnd:v0.7.1-beta-withseed
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -273,7 +272,6 @@ services:
bitcoin.defaultchanconfs=1
no-macaroons=1
debuglevel=debug
noseedbackup=1
trickledelay=1000
ports:
- "53281:8080"

View File

@ -1,27 +0,0 @@
using AspNet.Security.OpenIdConnect.Primitives;
using BTCPayServer.Data;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace BTCPayServer.Authentication.OpenId
{
public class AuthorizationCodeGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn
{
public AuthorizationCodeGrantTypeEventHandler(
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityOptions> identityOptions,
UserManager<ApplicationUser> userManager) : base(applicationManager, authorizationManager, signInManager,
identityOptions, userManager)
{
}
protected override bool IsValid(OpenIdConnectRequest request)
{
return request.IsAuthorizationCodeGrantType();
}
}
}

View File

@ -1,39 +0,0 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.Server;
namespace BTCPayServer.Authentication.OpenId
{
public class LogoutEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleLogoutRequest>
{
public LogoutEventHandler(
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
SignInManager<ApplicationUser> signInManager, IOptions<IdentityOptions> identityOptions) : base(
applicationManager, authorizationManager,
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;
}
}
}

View File

@ -1,139 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using BTCPayServer.Data;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Server;
namespace BTCPayServer.Authentication.OpenId
{
public static class OpenIdExtensions
{
public static async Task<AuthenticationTicket> CreateAuthenticationTicket(
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
IdentityOptions identityOptions,
SignInManager<ApplicationUser> signInManager,
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())
{
ticket.SetScopes(request.GetScopes());
}
else if (request.IsAuthorizationCodeGrantType() &&
string.IsNullOrEmpty(ticket.GetInternalAuthorizationId()))
{
var app = await applicationManager.FindByClientIdAsync(request.ClientId);
var authorizationId = await IsUserAuthorized(authorizationManager, request, user.Id, app.Id);
if (!string.IsNullOrEmpty(authorizationId))
{
ticket.SetInternalAuthorizationId(authorizationId);
}
}
foreach (var claim in ticket.Principal.Claims)
{
claim.SetDestinations(GetDestinations(identityOptions, claim, ticket));
}
return ticket;
}
private static IEnumerable<string> GetDestinations(IdentityOptions identityOptions, 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.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 static async Task<string> IsUserAuthorized(
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
OpenIdConnectRequest request, string userId, string applicationId)
{
var authorizations =
await authorizationManager.ListAsync(queryable =>
queryable.Where(authorization =>
authorization.Subject.Equals(userId, StringComparison.OrdinalIgnoreCase) &&
applicationId.Equals(authorization.Application.Id, StringComparison.OrdinalIgnoreCase) &&
authorization.Status.Equals(OpenIddictConstants.Statuses.Valid,
StringComparison.OrdinalIgnoreCase)));
if (authorizations.Length > 0)
{
var scopeTasks = authorizations.Select(authorization =>
(authorizationManager.GetScopesAsync(authorization).AsTask(), authorization.Id));
await Task.WhenAll(scopeTasks.Select((tuple) => tuple.Item1));
var authorizationsWithSufficientScopes = scopeTasks
.Select((tuple) => (tuple.Id, Scopes: tuple.Item1.Result))
.Where((tuple) => !request.GetScopes().Except(tuple.Scopes).Any());
if (authorizationsWithSufficientScopes.Any())
{
return authorizationsWithSufficientScopes.First().Id;
}
}
return null;
}
}
}

View File

@ -1,27 +0,0 @@
using AspNet.Security.OpenIdConnect.Primitives;
using BTCPayServer.Data;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace BTCPayServer.Authentication.OpenId
{
public class RefreshTokenGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn
{
public RefreshTokenGrantTypeEventHandler(
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(
applicationManager, authorizationManager, signInManager,
identityOptions, userManager)
{
}
protected override bool IsValid(OpenIdConnectRequest request)
{
return request.IsRefreshTokenGrantType();
}
}
}

View File

@ -5,19 +5,20 @@
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Build\dockerfiles\**" />
<Compile Remove="Build\**" />
<Compile Remove="Storage\Services\Providers\GoogleCloudStorage\**" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
<Compile Remove="wwwroot\css\**" />
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
<Content Remove="Build\dockerfiles\**" />
<Content Remove="Build\**" />
<Content Remove="wwwroot\bundles\jqueryvalidate\**" />
<Content Remove="wwwroot\css\**" />
<Content Remove="wwwroot\vendor\jquery-nice-select\**" />
<EmbeddedResource Remove="Build\dockerfiles\**" />
<EmbeddedResource Remove="Build\**" />
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**" />
<EmbeddedResource Remove="wwwroot\css\**" />
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**" />
<None Remove="Build\dockerfiles\**" />
<None Remove="Build\**" />
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
<None Remove="wwwroot\css\**" />
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
@ -30,10 +31,10 @@
<EmbeddedResource Include="Currencies.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.1" />
<PackageReference Include="BuildBundlerMinifier" Version="3.0.415" />
<PackageReference Include="BundlerMinifier.Core" Version="3.0.415" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.0.415" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.5" />
<PackageReference Include="BuildBundlerMinifier" Version="3.1.430" />
<PackageReference Include="BundlerMinifier.Core" Version="3.1.430" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.1.430" />
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
<PackageReference Include="LedgerWallet" Version="2.0.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
@ -42,13 +43,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="NBitpayClient" Version="1.0.0.34" />
<PackageReference Include="DBriize" Version="1.0.0.4" />
<PackageReference Include="DBriize" Version="1.0.1.3" />
<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.9" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
<PackageReference Include="OpenIddict" Version="2.0.0" />
<PackageReference Include="Serilog" Version="2.7.1" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
@ -60,16 +60,25 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
<PackageReference Include="TwentyTwenty.Storage" Version="2.11.2" />
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.11.2" />
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.11.2" />
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.11.2" />
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.11.2" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.11.2" />
<PackageReference Include="U2F.Core" Version="1.0.4" />
<PackageReference Include="YamlDotNet" Version="5.2.1" />
<PackageReference Include="OpenIddict" Version="3.0.0-alpha1.19515.63" />
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="3.0.0-alpha1.19515.63"></PackageReference>
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="3.0.0-alpha1.19515.63"></PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0"></PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
@ -120,9 +129,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Authentication\OpenId\Models\" />
<Folder Include="Build\" />
<Folder Include="U2F\Services" />
<Folder Include="wwwroot\vendor\clipboard.js\" />
<Folder Include="wwwroot\vendor\highlightjs\" />
<Folder Include="wwwroot\vendor\summernote" />
@ -152,6 +158,9 @@
<Content Update="Views\Server\LightningWalletServices.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Server\RPCService.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Server\P2PService.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
@ -203,6 +212,8 @@
<Content Update="Views\Wallets\WalletTransactions.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Remove="Views\Server\EditGoogleCloudStorageStorageProvider.cshtml" Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
</Content>
<Content Update="Views\Wallets\_Nav.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
@ -213,4 +224,4 @@
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
</ItemGroup>
</Project>
</Project>

View File

@ -207,6 +207,11 @@ namespace BTCPayServer.Configuration
LogFile = GetDebugLog(conf);
if (!string.IsNullOrEmpty(LogFile))
{
if (!Path.IsPathRooted(LogFile))
LogFile = Path.Combine(DataDir, LogFile);
}
if (!string.IsNullOrEmpty(LogFile))
{
Logs.Configuration.LogInformation("LogFile: " + LogFile);
Logs.Configuration.LogInformation("Log Level: " + GetDebugLogLevel(conf));

View File

@ -47,6 +47,7 @@ namespace BTCPayServer.Configuration
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
{
var crypto = network.CryptoCode.ToLowerInvariant();

View File

@ -142,6 +142,12 @@ namespace BTCPayServer.Configuration
Macaroons = Macaroons?.Clone()
};
}
public bool? IsOnion()
{
if (!this.Server.IsAbsoluteUri)
return null;
return this.Server.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);
}
public static bool TryParse(string str, out ExternalConnectionString result, out string error)
{
if (str == null)

View File

@ -76,6 +76,7 @@ namespace BTCPayServer.Configuration
Spark,
RTL,
Charge,
P2P
P2P,
RPC
}
}

View File

@ -1,6 +1,7 @@
using BTCPayServer.Authentication;
using BTCPayServer.Filters;
using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Security;
using BTCPayServer.Security.Bitpay;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
@ -13,7 +14,7 @@ using System.Threading.Tasks;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
[Authorize(AuthenticationSchemes = Security.AuthenticationSchemes.Bitpay)]
[BitpayAPIConstraint()]
public class AccessTokenController : Controller
{

View File

@ -18,15 +18,15 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Logging;
using BTCPayServer.Security;
using System.Globalization;
using BTCPayServer.Services.U2F;
using BTCPayServer.Services.U2F.Models;
using BTCPayServer.U2F;
using BTCPayServer.U2F.Models;
using Newtonsoft.Json;
using NicolasDorier.RateLimits;
using BTCPayServer.Data;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("[controller]/[action]")]
public class AccountController : Controller
{
@ -398,7 +398,9 @@ namespace BTCPayServer.Controllers
{
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
var settings = await _SettingsRepository.GetSettingAsync<ThemeSettings>();
settings.FirstRun = false;
await _SettingsRepository.UpdateSetting<ThemeSettings>(settings);
if(_Options.DisableRegistration)
{
// Once the admin user has been created lock subsequent user registrations (needs to be disabled for unit tests that require multiple users).
@ -420,7 +422,7 @@ namespace BTCPayServer.Controllers
}
else
{
TempData["StatusMessage"] = "Account created, please confirm your email";
TempData[WellKnownTempData.SuccessMessage] = "Account created, please confirm your email";
return View();
}
}
@ -447,88 +449,6 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(HomeController.Index), "Home");
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new
{
returnUrl
});
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
throw new ApplicationException("Error loading external login information during confirmation.");
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewData["ReturnUrl"] = returnUrl;
return View(nameof(ExternalLogin), model);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ConfirmEmail(string userId, string code)

View File

@ -164,7 +164,7 @@ namespace BTCPayServer.Controllers
StoreId = app.StoreDataId,
Settings = newSettings
});
StatusMessage = "App updated";
TempData[WellKnownTempData.SuccessMessage] = "App updated";
return RedirectToAction(nameof(UpdateCrowdfund), new {appId});
}
}

View File

@ -201,7 +201,7 @@ namespace BTCPayServer.Controllers
});
await _AppService.UpdateOrCreateApp(app);
StatusMessage = "App updated";
TempData[WellKnownTempData.SuccessMessage] = "App updated";
return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
}

View File

@ -19,7 +19,7 @@ using NBitcoin.DataEncoders;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
[Route("apps")]
public partial class AppsController : Controller
@ -50,8 +50,6 @@ namespace BTCPayServer.Controllers
private readonly EmailSenderFactory _emailSenderFactory;
private AppService _AppService;
[TempData]
public string StatusMessage { get; set; }
public string CreatedAppId { get; set; }
public async Task<IActionResult> ListApps()
@ -71,7 +69,7 @@ namespace BTCPayServer.Controllers
if (appData == null)
return NotFound();
if (await _AppService.DeleteApp(appData))
StatusMessage = "App removed successfully";
TempData[WellKnownTempData.SuccessMessage] = "App removed successfully";
return RedirectToAction(nameof(ListApps));
}
@ -82,12 +80,12 @@ namespace BTCPayServer.Controllers
var stores = await _AppService.GetOwnedStores(GetUserId());
if (stores.Length == 0)
{
StatusMessage = new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Html =
$"Error: You need to create at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>",
Severity = StatusMessageModel.StatusSeverity.Error
}.ToString();
});
return RedirectToAction(nameof(ListApps));
}
var vm = new CreateAppViewModel();
@ -102,12 +100,12 @@ namespace BTCPayServer.Controllers
var stores = await _AppService.GetOwnedStores(GetUserId());
if (stores.Length == 0)
{
StatusMessage = new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Html =
$"Error: You need to create at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>",
Severity = StatusMessageModel.StatusSeverity.Error
}.ToString();
});
return RedirectToAction(nameof(ListApps));
}
var selectedStore = vm.SelectedStore;
@ -124,7 +122,7 @@ namespace BTCPayServer.Controllers
if (!stores.Any(s => s.Id == selectedStore))
{
StatusMessage = "Error: You are not owner of this store";
TempData[WellKnownTempData.ErrorMessage] = "You are not owner of this store";
return RedirectToAction(nameof(ListApps));
}
var appData = new AppData
@ -134,7 +132,7 @@ namespace BTCPayServer.Controllers
AppType = appType.ToString()
};
await _AppService.UpdateOrCreateApp(appData);
StatusMessage = "App successfully created";
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
CreatedAppId = appData.Id;
switch (appType)

View File

@ -165,7 +165,6 @@ namespace BTCPayServer.Controllers
}
}
var store = await _AppService.GetStore(app);
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
{
ItemCode = choice?.Id,
@ -283,7 +282,6 @@ namespace BTCPayServer.Controllers
return NotFound("Contribution Amount is more than is currently allowed.");
}
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
try
{
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()

View File

@ -8,9 +8,7 @@ using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using BTCPayServer.Authentication.OpenId;
using BTCPayServer.Security.OpenId;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.Authorization;
@ -19,9 +17,13 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Server;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using OpenIddict.Server.AspNetCore;
namespace BTCPayServer.Controllers
{
@ -47,10 +49,11 @@ namespace BTCPayServer.Controllers
_IdentityOptions = identityOptions;
}
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest();
// Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
@ -69,7 +72,7 @@ namespace BTCPayServer.Controllers
if (!string.IsNullOrEmpty(
await OpenIdExtensions.IsUserAuthorized(_authorizationManager, request, userId, application.Id)))
{
return await Authorize(request, "YES", false);
return await Authorize("YES", false);
}
// Flow the request_id to allow OpenIddict to restore
@ -78,15 +81,15 @@ namespace BTCPayServer.Controllers
{
ApplicationName = await _applicationManager.GetDisplayNameAsync(application),
RequestId = request.RequestId,
Scope = request.Scope
Scope = request.GetScopes()
});
}
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request,
string consent, bool createAuthorization = true)
public async Task<IActionResult> Authorize(string consent, bool createAuthorization = true)
{
var request = HttpContext.GetOpenIddictServerRequest();
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
@ -111,26 +114,24 @@ namespace BTCPayServer.Controllers
default:
// Notify OpenIddict that the authorization grant has been denied by the resource owner
// to redirect the user agent to the client application using the appropriate response_mode.
return Forbid(OpenIddictServerDefaults.AuthenticationScheme);
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// Create a new authentication ticket.
var ticket =
await OpenIdExtensions.CreateAuthenticationTicket(_applicationManager, _authorizationManager,
_IdentityOptions.Value, _signInManager,
request, user);
var principal = await _signInManager.CreateUserPrincipalAsync(user);
principal = await _signInManager.CreateUserPrincipalAsync(user);
principal.SetScopes(request.GetScopes().Restrict(principal));
principal.SetDestinations(_IdentityOptions.Value);
if (createAuthorization)
{
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
var authorization = await _authorizationManager.CreateAsync(User, user.Id, application.Id,
type, ticket.GetScopes().ToImmutableArray(),
ticket.Properties.Items.ToImmutableDictionary());
ticket.SetInternalAuthorizationId(authorization.Id);
type, principal.GetScopes().ToImmutableArray());
principal.SetInternalAuthorizationId(authorization.Id);
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
}
}

View File

@ -12,6 +12,8 @@ using Newtonsoft.Json;
using BTCPayServer.Services;
using BTCPayServer.HostedServices;
using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Identity;
using BTCPayServer.Data;
namespace BTCPayServer.Controllers
{
@ -20,11 +22,15 @@ namespace BTCPayServer.Controllers
private readonly CssThemeManager _cachedServerSettings;
public IHttpClientFactory HttpClientFactory { get; }
SignInManager<ApplicationUser> SignInManager { get; }
public HomeController(IHttpClientFactory httpClientFactory, CssThemeManager cachedServerSettings)
public HomeController(IHttpClientFactory httpClientFactory,
CssThemeManager cachedServerSettings,
SignInManager<ApplicationUser> signInManager)
{
HttpClientFactory = httpClientFactory;
_cachedServerSettings = cachedServerSettings;
SignInManager = signInManager;
}
private async Task<ViewResult> GoToApp(string appId, AppType? appType)
@ -71,14 +77,26 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Index()
{
if (_cachedServerSettings.FirstRun)
{
return RedirectToAction(nameof(AccountController.Register), "Account");
}
var matchedDomainMapping = _cachedServerSettings.DomainToAppMapping.FirstOrDefault(item =>
item.Domain.Equals(Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));
if (matchedDomainMapping != null)
{
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? View("Home");
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? GoToHome();
}
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? View("Home");
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? GoToHome();
}
private IActionResult GoToHome()
{
if (SignInManager.IsSignedIn(User))
return View("Home");
else
return RedirectToAction(nameof(AccountController.Login), "Account");
}
[Route("translate")]

View File

@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
[BitpayAPIConstraint]
[Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = Policies.BitpayAuthentication)]
[Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Bitpay)]
public class InvoiceControllerAPI : Controller
{
private InvoiceController _InvoiceController;

View File

@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers
{
[HttpGet]
[Route("invoices/{invoiceId}")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Invoice(string invoiceId)
{
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
@ -71,9 +71,7 @@ namespace BTCPayServer.Controllers
ProductInformation = invoice.ProductInformation,
StatusException = invoice.ExceptionStatus,
Events = invoice.Events,
PosData = PosDataParser.ParsePosData(invoice.PosData),
StatusMessage = StatusMessage,
PosData = PosDataParser.ParsePosData(invoice.PosData)
};
model.Addresses = invoice.HistoricalAddresses.Select(h =>
@ -179,7 +177,7 @@ namespace BTCPayServer.Controllers
paymentMethodId = store.GetDefaultPaymentId(_NetworkProvider);
isDefaultPaymentId = true;
}
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
if (network == null && isDefaultPaymentId)
{
//TODO: need to look into a better way for this as it does not scale
@ -225,7 +223,6 @@ namespace BTCPayServer.Controllers
(1m + (changelly.AmountMarkupPercentage / 100m)))
: (decimal?)null;
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
var model = new PaymentModel()
{
@ -235,8 +232,8 @@ namespace BTCPayServer.Controllers
InvoiceId = invoice.Id,
DefaultLang = storeBlob.DefaultLang ?? "en",
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
CustomCSSLink = storeBlob.CustomCSS?.AbsoluteUri,
CustomLogoLink = storeBlob.CustomLogo?.AbsoluteUri,
CustomCSSLink = storeBlob.CustomCSS,
CustomLogoLink = storeBlob.CustomLogo,
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
BtcDue = accounting.Due.ToString(),
@ -244,6 +241,8 @@ namespace BTCPayServer.Controllers
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation),
CustomerEmail = invoice.RefundMail,
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
FeeRate = paymentMethodDetails.GetFeeRate(),
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
@ -357,6 +356,7 @@ namespace BTCPayServer.Controllers
break;
}
}
catch(WebSocketException) { }
finally
{
leases.Dispose();
@ -394,7 +394,7 @@ namespace BTCPayServer.Controllers
[HttpGet]
[Route("invoices")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int timezoneOffset = 0)
{
@ -403,7 +403,6 @@ namespace BTCPayServer.Controllers
SearchTerm = searchTerm,
Skip = skip,
Count = count,
StatusMessage = StatusMessage,
TimezoneOffset = timezoneOffset
};
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
@ -454,7 +453,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> Export(string format, string searchTerm = null, int timezoneOffset = 0)
{
@ -488,14 +487,14 @@ namespace BTCPayServer.Controllers
[HttpGet]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice()
{
var stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), nameof(StoreData.StoreName), null);
if (stores.Count() == 0)
{
StatusMessage = "Error: You need to create at least one store before creating a transaction";
TempData[WellKnownTempData.ErrorMessage] = "You need to create at least one store before creating a transaction";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
@ -504,30 +503,18 @@ namespace BTCPayServer.Controllers
[HttpPost]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(Policy = Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
{
var stores = await _StoreRepository.GetStoresByUserId(GetUserId());
model.Stores = new SelectList(stores, nameof(StoreData.Id), nameof(StoreData.StoreName), model.StoreId);
model.AvailablePaymentMethods = GetPaymentMethodsSelectList();
var store = stores.FirstOrDefault(s => s.Id == model.StoreId);
if (store == null)
{
ModelState.AddModelError(nameof(model.StoreId), "Store not found");
}
var store = HttpContext.GetStoreData();
if (!ModelState.IsValid)
{
return View(model);
}
StatusMessage = null;
if (!store.HasClaim(Policies.CanCreateInvoice.Key))
{
ModelState.AddModelError(nameof(model.StoreId), "You need to be owner of this store to create an invoice");
return View(model);
}
if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0)
{
@ -535,15 +522,6 @@ namespace BTCPayServer.Controllers
return View(model);
}
if (StatusMessage != null)
{
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id
});
}
try
{
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
@ -564,7 +542,7 @@ namespace BTCPayServer.Controllers
})
}, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
StatusMessage = $"Invoice {result.Data.Id} just created!";
TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Data.Id} just created!";
return RedirectToAction(nameof(ListInvoices));
}
catch (BitpayHttpException ex)
@ -576,7 +554,7 @@ namespace BTCPayServer.Controllers
[HttpPost]
[Route("invoices/{invoiceId}/changestate/{newState}")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ChangeInvoiceState(string invoiceId, string newState)
{
@ -585,15 +563,12 @@ namespace BTCPayServer.Controllers
InvoiceId = new[] {invoiceId},
UserId = GetUserId()
})).FirstOrDefault();
var model = new InvoiceStateChangeModel();
if (invoice == null)
{
model.NotFound = true;
return NotFound(model);
}
if (newState == "invalid")
{
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
@ -616,13 +591,6 @@ namespace BTCPayServer.Controllers
public string StatusString { get; set; }
}
[TempData]
public string StatusMessage
{
get;
set;
}
private string GetUserId()
{
return _UserManager.GetUserId(User);

View File

@ -26,6 +26,7 @@ using Newtonsoft.Json;
namespace BTCPayServer.Controllers
{
[Filters.BitpayAPIConstraint(false)]
public partial class InvoiceController : Controller
{
InvoiceRepository _InvoiceRepository;
@ -65,8 +66,6 @@ namespace BTCPayServer.Controllers
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null, CancellationToken cancellationToken = default)
{
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");
@ -141,6 +140,7 @@ namespace BTCPayServer.Controllers
.Where(c => c != null))
{
currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, invoice.Currency));
//TODO: abstract
if (storeBlob.LightningMaxValue != null)
currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.LightningMaxValue.Currency));
if (storeBlob.OnChainMinValue != null)
@ -175,7 +175,7 @@ namespace BTCPayServer.Controllers
if (supported.Count == 0)
{
StringBuilder errors = new StringBuilder();
errors.AppendLine("Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/btcpay-basics/gettingstarted#connecting-btcpay-store-to-your-wallet)");
errors.AppendLine("Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/getting-started/connectwallet)");
foreach (var error in logs.ToList())
{
errors.AppendLine(error.ToString());

View File

@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Models;
using BTCPayServer.Services.U2F.Models;
using BTCPayServer.U2F.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
@ -9,11 +9,10 @@ namespace BTCPayServer.Controllers
public partial class ManageController
{
[HttpGet]
public async Task<IActionResult> U2FAuthentication(string statusMessage = null)
public async Task<IActionResult> U2FAuthentication()
{
return View(new U2FAuthenticationViewModel()
{
StatusMessage = statusMessage,
Devices = await _u2FService.GetDevices(_userManager.GetUserId(User))
});
}
@ -33,14 +32,12 @@ namespace BTCPayServer.Controllers
{
if (!_btcPayServerEnvironment.IsSecure)
{
return RedirectToAction("U2FAuthentication", new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Cannot register U2F device while not on https or tor"
}
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Cannot register U2F device while not on https or tor"
});
return RedirectToAction("U2FAuthentication");
}
var serverRegisterResponse = _u2FService.StartDeviceRegistration(_userManager.GetUserId(User),
@ -64,11 +61,8 @@ namespace BTCPayServer.Controllers
if (await _u2FService.CompleteRegistration(_userManager.GetUserId(User), viewModel.DeviceResponse,
string.IsNullOrEmpty(viewModel.Name) ? "Unlabelled U2F Device" : viewModel.Name))
{
return RedirectToAction("U2FAuthentication", new
{
StatusMessage = "Device added!"
});
TempData[WellKnownTempData.SuccessMessage] = "Device added!";
return RedirectToAction("U2FAuthentication");
}
}
catch (Exception e)
@ -76,14 +70,12 @@ namespace BTCPayServer.Controllers
errorMessage = e.Message;
}
return RedirectToAction("U2FAuthentication", new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = string.IsNullOrEmpty(errorMessage) ? "Could not add device." : errorMessage
}
Severity = StatusMessageModel.StatusSeverity.Error,
Message = string.IsNullOrEmpty(errorMessage) ? "Could not add device." : errorMessage
});
return RedirectToAction("U2FAuthentication");
}
}
}

View File

@ -11,19 +11,21 @@ using Microsoft.Extensions.Logging;
using BTCPayServer.Models;
using BTCPayServer.Models.ManageViewModels;
using BTCPayServer.Services;
using BTCPayServer.Authentication;
using Microsoft.AspNetCore.Hosting;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Services.Mails;
using System.Globalization;
using BTCPayServer.Security;
using BTCPayServer.Services.U2F;
using BTCPayServer.U2F;
using BTCPayServer.Data;
#if NETCOREAPP21
using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
#endif
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("[controller]/[action]")]
public partial class ManageController : Controller
{
@ -32,8 +34,7 @@ namespace BTCPayServer.Controllers
private readonly EmailSenderFactory _EmailSenderFactory;
private readonly ILogger _logger;
private readonly UrlEncoder _urlEncoder;
TokenRepository _TokenRepository;
IHostingEnvironment _Env;
IWebHostEnvironment _Env;
private readonly U2FService _u2FService;
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
StoreRepository _StoreRepository;
@ -46,10 +47,9 @@ namespace BTCPayServer.Controllers
EmailSenderFactory emailSenderFactory,
ILogger<ManageController> logger,
UrlEncoder urlEncoder,
TokenRepository tokenRepository,
BTCPayWalletProvider walletProvider,
StoreRepository storeRepository,
IHostingEnvironment env,
IWebHostEnvironment env,
U2FService u2FService,
BTCPayServerEnvironment btcPayServerEnvironment)
{
@ -58,19 +58,12 @@ namespace BTCPayServer.Controllers
_EmailSenderFactory = emailSenderFactory;
_logger = logger;
_urlEncoder = urlEncoder;
_TokenRepository = tokenRepository;
_Env = env;
_u2FService = u2FService;
_btcPayServerEnvironment = btcPayServerEnvironment;
_StoreRepository = storeRepository;
}
[TempData]
public string StatusMessage
{
get; set;
}
[HttpGet]
public async Task<IActionResult> Index()
{
@ -85,8 +78,7 @@ namespace BTCPayServer.Controllers
Username = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
IsEmailConfirmed = user.EmailConfirmed,
StatusMessage = StatusMessage
IsEmailConfirmed = user.EmailConfirmed
};
return View(model);
}
@ -138,7 +130,7 @@ namespace BTCPayServer.Controllers
}
}
StatusMessage = "Your profile has been updated";
TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated";
return RedirectToAction(nameof(Index));
}
@ -161,7 +153,7 @@ namespace BTCPayServer.Controllers
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
var email = user.Email;
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(email, callbackUrl);
StatusMessage = "Verification email sent. Please check your email.";
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent. Please check your email.";
return RedirectToAction(nameof(Index));
}
@ -180,7 +172,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(SetPassword));
}
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
var model = new ChangePasswordViewModel();
return View(model);
}
@ -208,7 +200,7 @@ namespace BTCPayServer.Controllers
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
TempData[WellKnownTempData.SuccessMessage] = "Your password has been changed.";
return RedirectToAction(nameof(ChangePassword));
}
@ -229,7 +221,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(ChangePassword));
}
var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
var model = new SetPasswordViewModel();
return View(model);
}
@ -256,92 +248,11 @@ namespace BTCPayServer.Controllers
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "Your password has been set.";
TempData[WellKnownTempData.SuccessMessage] = "Your password has been set.";
return RedirectToAction(nameof(SetPassword));
}
[HttpGet]
public async Task<IActionResult> ExternalLogins()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.ToList();
model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
model.StatusMessage = StatusMessage;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LinkLogin(string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action(nameof(LinkLoginCallback));
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return new ChallengeResult(provider, properties);
}
[HttpGet]
public async Task<IActionResult> LinkLoginCallback()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
if (info == null)
{
throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
}
var result = await _userManager.AddLoginAsync(user, info);
if (!result.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
}
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
StatusMessage = "The external login was added.";
return RedirectToAction(nameof(ExternalLogins));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
if (!result.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "The external login was removed.";
return RedirectToAction(nameof(ExternalLogins));
}
#region Helpers

View File

@ -31,7 +31,7 @@ using NBitpayClient;
namespace BTCPayServer.Controllers
{
[Route("payment-requests")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public class PaymentRequestController : Controller
{
private readonly InvoiceController _InvoiceController;
@ -66,7 +66,7 @@ namespace BTCPayServer.Controllers
[HttpGet]
[Route("")]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50, string statusMessage = null)
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50)
{
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
{
@ -75,7 +75,6 @@ namespace BTCPayServer.Controllers
return View(new ListPaymentRequestsViewModel()
{
Skip = skip,
StatusMessage = statusMessage,
Count = count,
Total = result.Total,
Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList()
@ -84,7 +83,7 @@ namespace BTCPayServer.Controllers
[HttpGet]
[Route("edit/{id?}")]
public async Task<IActionResult> EditPaymentRequest(string id, string statusMessage = null)
public async Task<IActionResult> EditPaymentRequest(string id)
{
SelectList stores = null;
var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId());
@ -97,22 +96,17 @@ namespace BTCPayServer.Controllers
nameof(StoreData.StoreName), data?.StoreDataId);
if (!stores.Any())
{
return RedirectToAction("GetPaymentRequests",
new
{
StatusMessage = new StatusMessageModel()
{
Html =
$"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}'>Create store</a>",
Severity = StatusMessageModel.StatusSeverity.Error
}
});
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Html = $"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}'>Create store</a>",
Severity = StatusMessageModel.StatusSeverity.Error
});
return RedirectToAction("GetPaymentRequests");
}
return View(new UpdatePaymentRequestViewModel(data)
{
Stores = stores,
StatusMessage = statusMessage
Stores = stores
});
}
@ -169,7 +163,8 @@ namespace BTCPayServer.Controllers
PaymentRequestId = data.Id,
});
return RedirectToAction("EditPaymentRequest", new {id = data.Id, StatusMessage = "Saved"});
TempData[WellKnownTempData.SuccessMessage] = "Saved";
return RedirectToAction("EditPaymentRequest", new {id = data.Id});
}
[HttpGet]
@ -200,24 +195,20 @@ namespace BTCPayServer.Controllers
var result = await _PaymentRequestRepository.RemovePaymentRequest(id, GetUserId());
if (result)
{
return RedirectToAction("GetPaymentRequests",
new {StatusMessage = "Payment request successfully removed"});
TempData[WellKnownTempData.SuccessMessage] = "Payment request successfully removed";
return RedirectToAction("GetPaymentRequests");
}
else
{
return RedirectToAction("GetPaymentRequests",
new
{
StatusMessage =
"Error: Payment request could not be removed. Any request that has generated invoices cannot be removed."
});
TempData[WellKnownTempData.ErrorMessage] = "Payment request could not be removed. Any request that has generated invoices cannot be removed.";
return RedirectToAction("GetPaymentRequests");
}
}
[HttpGet]
[Route("{id}")]
[AllowAnonymous]
public async Task<IActionResult> ViewPaymentRequest(string id, string statusMessage = null)
public async Task<IActionResult> ViewPaymentRequest(string id)
{
var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId());
if (result == null)
@ -225,8 +216,6 @@ namespace BTCPayServer.Controllers
return NotFound();
}
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
result.StatusMessage = statusMessage;
return View(result);
}
@ -289,7 +278,6 @@ namespace BTCPayServer.Controllers
var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null);
var blob = pr.GetBlob();
var store = pr.StoreData;
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
try
{
var redirectUrl = Request.GetDisplayUrl().TrimEnd("/pay", StringComparison.InvariantCulture)
@ -343,10 +331,10 @@ namespace BTCPayServer.Controllers
if (redirect)
{
TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled";
return RedirectToAction(nameof(ViewPaymentRequest), new
{
Id = id,
StatusMessage = "Payment cancelled"
Id = id
});
}

View File

@ -11,18 +11,25 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Rating;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Authorization;
using BTCPayServer.Authentication;
using Microsoft.AspNetCore.Cors;
using System.Threading;
using BTCPayServer.Data;
using BTCPayServer.Security;
using BTCPayServer.Security.Bitpay;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
[AllowAnonymous]
[EnableCors(CorsPolicies.All)]
[Authorize(Policy = Policies.CanGetRates.Key, AuthenticationSchemes = Security.AuthenticationSchemes.Bitpay)]
public class RateController : Controller
{
public StoreData CurrentStore
{
get
{
return HttpContext.GetStoreData();
}
}
RateFetcher _RateProviderFactory;
BTCPayNetworkProvider _NetworkProvider;
CurrencyNameTable _CurrencyNameTable;
@ -47,40 +54,28 @@ namespace BTCPayServer.Controllers
[Route("rates/{baseCurrency}")]
[HttpGet]
[BitpayAPIConstraint]
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string storeId, CancellationToken cancellationToken)
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, CancellationToken cancellationToken)
{
storeId = await GetStoreId(storeId);
var store = this.HttpContext.GetStoreData();
if (store == null || store.Id != storeId)
store = await _StoreRepo.FindStore(storeId);
if (store == null)
{
var err = Json(new BitpayErrorsModel() { Error = "Store not found" });
err.StatusCode = 404;
return err;
}
var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider);
var supportedMethods = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider);
var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode))
.Select(method => method.PaymentId.CryptoCode).Distinct();
var currencypairs = BuildCurrencyPairs(currencyCodes, baseCurrency);
var result = await GetRates2(currencypairs, store.Id, cancellationToken);
var result = await GetRates2(currencypairs, null, cancellationToken);
var rates = (result as JsonResult)?.Value as Rate[];
if (rates == null)
return result;
return Json(new DataWrapper<Rate[]>(rates));
}
[Route("rates/{baseCurrency}/{currency}")]
[HttpGet]
[BitpayAPIConstraint]
public async Task<IActionResult> GetCurrencyPairRate(string baseCurrency, string currency, string storeId, CancellationToken cancellationToken)
public async Task<IActionResult> GetCurrencyPairRate(string baseCurrency, string currency, CancellationToken cancellationToken)
{
storeId = await GetStoreId(storeId);
var result = await GetRates2($"{baseCurrency}_{currency}", storeId, cancellationToken);
var result = await GetRates2($"{baseCurrency}_{currency}", null, cancellationToken);
var rates = (result as JsonResult)?.Value as Rate[];
if (rates == null)
return result;
@ -90,54 +85,27 @@ namespace BTCPayServer.Controllers
[Route("rates")]
[HttpGet]
[BitpayAPIConstraint]
public async Task<IActionResult> GetRates(string currencyPairs, string storeId, CancellationToken cancellationToken)
public async Task<IActionResult> GetRates(string currencyPairs, CancellationToken cancellationToken)
{
var result = await GetRates2(currencyPairs, storeId, cancellationToken);
var result = await GetRates2(currencyPairs, null, cancellationToken);
var rates = (result as JsonResult)?.Value as Rate[];
if (rates == null)
return result;
return Json(new DataWrapper<Rate[]>(rates));
}
private async Task<string> GetStoreId(string storeId)
{
if (storeId != null && this.HttpContext.GetStoreData()?.Id == storeId)
return storeId;
if(storeId == null)
{
var tokens = await this.TokenRepository.GetTokens(this.User.GetSIN());
storeId = tokens.Select(s => s.StoreId).Where(s => s != null).FirstOrDefault();
}
if (storeId == null)
return null;
var store = await _StoreRepo.FindStore(storeId);
if (store == null)
return null;
this.HttpContext.SetStoreData(store);
return storeId;
}
[Route("api/rates")]
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> GetRates2(string currencyPairs, string storeId, CancellationToken cancellationToken)
{
storeId = await GetStoreId(storeId);
if (storeId == null)
{
var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings)" });
result.StatusCode = 400;
return result;
}
var store = this.HttpContext.GetStoreData();
if (store == null || store.Id != storeId)
store = await _StoreRepo.FindStore(storeId);
var store = this.CurrentStore ?? await this._StoreRepo.FindStore(storeId);
if (store == null)
{
var result = Json(new BitpayErrorsModel() { Error = "Store not found" });
result.StatusCode = 404;
return result;
var err = Json(new BitpayErrorsModel() { Error = "Store not found" });
err.StatusCode = 404;
return err;
}
if (currencyPairs == null)
{
currencyPairs = store.GetStoreBlob().GetDefaultCurrencyPairString();
@ -186,7 +154,7 @@ namespace BTCPayServer.Controllers
bool first = true;
foreach (var currencyCode in currencyCodes)
{
if(!first)
if (!first)
currencyPairsBuilder.Append(",");
first = false;
currencyPairsBuilder.Append($"{baseCrypto}_{currencyCode}");

View File

@ -6,7 +6,7 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Validation;
using OpenIddict.Validation.AspNetCore;
namespace BTCPayServer.Controllers.RestApi
{
@ -15,7 +15,7 @@ namespace BTCPayServer.Controllers.RestApi
/// </summary>
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.OpenId)]
public class TestController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
@ -40,11 +40,15 @@ namespace BTCPayServer.Controllers.RestApi
}
[HttpGet("me/is-admin")]
[Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.OpenId)]
public bool AmIAnAdmin()
{
return User.IsInRole(Roles.ServerAdmin);
{
return true;
}
[HttpGet("me/stores")]
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
public async Task<StoreData[]> GetCurrentUserStores()
{
return await _storeRepository.GetStoresByUserId(_userManager.GetUserId(User));
@ -52,7 +56,8 @@ namespace BTCPayServer.Controllers.RestApi
[HttpGet("me/stores/{storeId}/can-edit")]
[Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)]
[Authorize(Policy = Policies.CanModifyStoreSettings.Key,
AuthenticationSchemes = AuthenticationSchemes.OpenId)]
public bool CanEdit(string storeId)
{
return true;

View File

@ -10,8 +10,10 @@ using BTCPayServer.Storage.Services.Providers.AzureBlobStorage;
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
#if NETCOREAPP21
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage;
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration;
#endif
using BTCPayServer.Storage.Services.Providers.Models;
using BTCPayServer.Storage.ViewModels;
using BTCPayServer.Views;
@ -24,9 +26,8 @@ namespace BTCPayServer.Controllers
public partial class ServerController
{
[HttpGet("server/files/{fileId?}")]
public async Task<IActionResult> Files(string fileId = null, string statusMessage = null)
public async Task<IActionResult> Files(string fileId = null)
{
TempData["StatusMessage"] = statusMessage;
var fileUrl = string.IsNullOrEmpty(fileId) ? null : await _FileService.GetFileUrl(Request.GetAbsoluteRootUri(), fileId);
return View(new ViewFilesViewModel()
@ -52,14 +53,12 @@ namespace BTCPayServer.Controllers
}
catch (Exception e)
{
return RedirectToAction(nameof(Files), new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
statusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = e.Message
}
Severity = StatusMessageModel.StatusSeverity.Error,
Message = e.Message
});
return RedirectToAction(nameof(Files));
}
}
@ -117,16 +116,14 @@ namespace BTCPayServer.Controllers
}
var url = await _FileService.GetTemporaryFileUrl(Request.GetAbsoluteRootUri(), fileId, expiry, viewModel.IsDownload);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html = $"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
});
return RedirectToAction(nameof(Files), new
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html =
$"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
}.ToString(),
fileId,
fileId
});
}
@ -163,9 +160,8 @@ namespace BTCPayServer.Controllers
}
[HttpGet("server/storage")]
public async Task<IActionResult> Storage(bool forceChoice = false, string statusMessage = null)
public async Task<IActionResult> Storage(bool forceChoice = false)
{
TempData["StatusMessage"] = statusMessage;
var savedSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
if (forceChoice || savedSettings == null)
{
@ -196,14 +192,12 @@ namespace BTCPayServer.Controllers
{
if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider))
{
return RedirectToAction(nameof(Storage), new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{provider} provider is not supported"
}.ToString()
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{provider} provider is not supported"
});
return RedirectToAction(nameof(Storage));
}
var data = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) ?? new StorageSettings();
@ -214,14 +208,12 @@ namespace BTCPayServer.Controllers
switch (storageProviderService)
{
case null:
return RedirectToAction(nameof(Storage), new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{storageProvider} is not supported"
}.ToString()
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{storageProvider} is not supported"
});
return RedirectToAction(nameof(Storage));
case AzureBlobStorageFileProviderService fileProviderService:
return View(nameof(EditAzureBlobStorageStorageProvider),
fileProviderService.GetProviderConfiguration(data));
@ -229,11 +221,11 @@ namespace BTCPayServer.Controllers
case AmazonS3FileProviderService fileProviderService:
return View(nameof(EditAmazonS3StorageProvider),
fileProviderService.GetProviderConfiguration(data));
#if NETCOREAPP21
case GoogleCloudStorageFileProviderService fileProviderService:
return View(nameof(EditGoogleCloudStorageStorageProvider),
fileProviderService.GetProviderConfiguration(data));
#endif
case FileSystemFileProviderService fileProviderService:
if (data.Provider != BTCPayServer.Storage.Models.StorageProvider.FileSystem)
{
@ -260,14 +252,14 @@ namespace BTCPayServer.Controllers
{
return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.AmazonS3);
}
#if NETCOREAPP21
[HttpPost("server/storage/GoogleCloudStorage")]
public async Task<IActionResult> EditGoogleCloudStorageStorageProvider(
GoogleCloudStorageConfiguration viewModel)
{
return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.GoogleCloudStorage);
}
#endif
[HttpPost("server/storage/FileSystem")]
public async Task<IActionResult> EditFileSystemStorageProvider(FileSystemStorageConfiguration viewModel)
{
@ -286,11 +278,11 @@ namespace BTCPayServer.Controllers
data.Provider = storageProvider;
data.Configuration = JObject.FromObject(viewModel);
await _SettingsRepository.UpdateSetting(data);
TempData["StatusMessage"] = new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Storage settings updated successfully"
}.ToString();
});
return View(viewModel);
}
}

View File

@ -38,7 +38,8 @@ using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Controllers
{
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key)]
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key,
AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes.Cookie)]
public partial class ServerController : Controller
{
private UserManager<ApplicationUser> _UserManager;
@ -135,7 +136,7 @@ namespace BTCPayServer.Controllers
return View(vm);
}
await _SettingsRepository.UpdateSetting(rates);
StatusMessage = "Rate settings successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "Rate settings successfully updated";
return RedirectToAction(nameof(Rates));
}
@ -157,7 +158,6 @@ namespace BTCPayServer.Controllers
public IActionResult ListUsers(int skip = 0, int count = 50)
{
var users = new UsersViewModel();
users.StatusMessage = StatusMessage;
users.Users = _UserManager.Users.Skip(skip).Take(count)
.Select(u => new UsersViewModel.UserViewModel
{
@ -190,6 +190,8 @@ namespace BTCPayServer.Controllers
{
MaintenanceViewModel vm = new MaintenanceViewModel();
vm.CanUseSSH = _sshState.CanUseSSH;
if (!vm.CanUseSSH)
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPayServer configuration";
vm.DNSDomain = this.Request.Host.Host;
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
vm.DNSDomain = null;
@ -262,21 +264,21 @@ namespace BTCPayServer.Controllers
builder.Path = null;
builder.Query = null;
StatusMessage = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\"";
TempData[WellKnownTempData.SuccessMessage] = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\" (this page won't reload automatically)";
}
else if (command == "update")
{
var error = await RunSSH(vm, $"btcpay-update.sh");
if (error != null)
return error;
StatusMessage = $"The server might restart soon if an update is available...";
TempData[WellKnownTempData.SuccessMessage] = $"The server might restart soon if an update is available... (this page won't reload automatically)";
}
else if (command == "clean")
{
var error = await RunSSH(vm, $"btcpay-clean.sh");
if (error != null)
return error;
StatusMessage = $"The old docker images will be cleaned soon...";
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
}
else
{
@ -356,12 +358,10 @@ namespace BTCPayServer.Controllers
if (user == null)
return NotFound();
viewModel.StatusMessage = "";
var admins = await _UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
if (!viewModel.IsAdmin && admins.Count == 1)
{
viewModel.StatusMessage = "This is the only Admin, so their role can't be removed until another Admin is added.";
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added.";
return View(viewModel); // return
}
@ -373,10 +373,10 @@ namespace BTCPayServer.Controllers
else
await _UserManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
viewModel.StatusMessage = "User successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "User successfully updated";
}
return View(viewModel);
return RedirectToAction(nameof(User), new { userId = userId });
}
@ -419,15 +419,9 @@ namespace BTCPayServer.Controllers
return NotFound();
await _UserManager.DeleteAsync(user);
await _StoreRepository.CleanUnreachableStores();
StatusMessage = "User deleted";
TempData[WellKnownTempData.SuccessMessage] = "User deleted";
return RedirectToAction(nameof(ListUsers));
}
[TempData]
public string StatusMessage
{
get; set;
}
public IHttpClientFactory HttpClientFactory { get; }
[Route("server/policies")]
@ -488,7 +482,7 @@ namespace BTCPayServer.Controllers
}
await _SettingsRepository.UpdateSetting(settings);
TempData["StatusMessage"] = "Policies updated successfully";
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully";
return RedirectToAction(nameof(Policies));
}
@ -497,6 +491,8 @@ namespace BTCPayServer.Controllers
{
var result = new ServicesViewModel();
result.ExternalServices = _Options.ExternalServices.ToList();
// other services
foreach (var externalService in _Options.OtherExternalServices)
{
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
@ -542,6 +538,7 @@ namespace BTCPayServer.Controllers
}
}
// external storage services
var storageSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
result.ExternalStorageServices.Add(new ServicesViewModel.OtherExternalService()
{
@ -573,6 +570,17 @@ namespace BTCPayServer.Controllers
ServiceName = torService.Name,
};
}
if (torService.ServiceType == TorServiceType.RPC)
{
externalService = new ExternalService()
{
CryptoCode = torService.Network.CryptoCode,
DisplayName = "Full node RPC",
Type = ExternalServiceTypes.RPC,
ConnectionString = new ExternalConnectionString(new Uri($"btcrpc://btcrpc:btcpayserver4ever@{torService.OnionHost}:{torService.VirtualPort}?label=BTCPayNode", UriKind.Absolute)),
ServiceName = torService.Name
};
}
return externalService != null;
}
@ -581,16 +589,21 @@ namespace BTCPayServer.Controllers
var result = _Options.ExternalServices.GetService(serviceName, cryptoCode);
if (result != null)
return result;
_torServices.Services.FirstOrDefault(s => TryParseAsExternalService(s, out result));
return result;
foreach (var torService in _torServices.Services)
{
if (TryParseAsExternalService(torService, out var torExternalService) &&
torExternalService.ServiceName == serviceName)
return torExternalService;
}
return null;
}
[Route("server/services/{serviceName}/{cryptoCode}")]
public async Task<IActionResult> Service(string serviceName, string cryptoCode, bool showQR = false, uint? nonce = null)
{
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
if (!_dashBoard.IsFullySynched(cryptoCode, out _))
{
StatusMessage = $"Error: {cryptoCode} is not fully synched";
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
return RedirectToAction(nameof(Services));
}
var service = GetService(serviceName, cryptoCode);
@ -608,6 +621,15 @@ namespace BTCPayServer.Controllers
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
});
}
if (service.Type == ExternalServiceTypes.RPC)
{
return View("RPCService", new LightningWalletServices()
{
ShowQR = showQR,
WalletName = service.ServiceName,
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
});
}
var connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type, _Options.NetworkType);
switch (service.Type)
{
@ -617,7 +639,7 @@ namespace BTCPayServer.Controllers
case ExternalServiceTypes.Spark:
if (connectionString.AccessKey == null)
{
StatusMessage = $"Error: The access key of the service is not set";
TempData[WellKnownTempData.ErrorMessage] = $"The access key of the service is not set";
return RedirectToAction(nameof(Services));
}
LightningWalletServices vm = new LightningWalletServices();
@ -634,7 +656,7 @@ namespace BTCPayServer.Controllers
}
catch (Exception ex)
{
StatusMessage = $"Error: {ex.Message}";
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return RedirectToAction(nameof(Services));
}
}
@ -653,7 +675,7 @@ namespace BTCPayServer.Controllers
private IActionResult LndServices(ExternalService service, ExternalConnectionString connectionString, uint? nonce)
{
var model = new LndGrpcServicesViewModel();
var model = new LndServicesViewModel();
if (service.Type == ExternalServiceTypes.LNDGRPC)
{
model.Host = $"{connectionString.Server.DnsSafeHost}:{connectionString.Server.Port}";
@ -714,7 +736,7 @@ namespace BTCPayServer.Controllers
{
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
{
StatusMessage = $"Error: {cryptoCode} is not fully synched";
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
return RedirectToAction(nameof(Services));
}
var service = GetService(serviceName, cryptoCode);
@ -728,7 +750,7 @@ namespace BTCPayServer.Controllers
}
catch (Exception ex)
{
StatusMessage = $"Error: {ex.Message}";
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return RedirectToAction(nameof(Services));
}
@ -810,7 +832,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
if (errorMessage == null)
{
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
TempData[WellKnownTempData.SuccessMessage] = $"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);
@ -846,7 +868,7 @@ namespace BTCPayServer.Controllers
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
if (!viewModel.Settings.Enabled)
{
StatusMessage = $"The Dynamic DNS service has been disabled";
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS service has been disabled";
viewModel.Settings.LastUpdated = null;
}
else
@ -854,7 +876,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
if (errorMessage == null)
{
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved";
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
}
else
@ -893,7 +915,7 @@ namespace BTCPayServer.Controllers
return NotFound();
settings.Services.RemoveAt(i);
await _SettingsRepository.UpdateSetting(settings);
StatusMessage = "Dynamic DNS service successfully removed";
TempData[WellKnownTempData.SuccessMessage] = "Dynamic DNS service successfully removed";
this.RouteData.Values.Remove(nameof(hostname));
return RedirectToAction(nameof(DynamicDnsServices));
}
@ -964,7 +986,7 @@ namespace BTCPayServer.Controllers
try
{
await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent);
StatusMessage = "authorized_keys has been updated";
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
updated = true;
}
catch (Exception ex)
@ -993,11 +1015,11 @@ namespace BTCPayServer.Controllers
if (exception is null)
{
StatusMessage = "authorized_keys has been updated";
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
}
else
{
StatusMessage = $"Error: {exception.Message}";
TempData[WellKnownTempData.ErrorMessage] = exception.Message;
}
return RedirectToAction(nameof(SSHService));
}
@ -1013,7 +1035,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Theme(ThemeSettings settings)
{
await _SettingsRepository.UpdateSetting(settings);
TempData["StatusMessage"] = "Theme settings updated successfully";
TempData[WellKnownTempData.SuccessMessage] = "Theme settings updated successfully";
return View(settings);
}
@ -1031,7 +1053,7 @@ namespace BTCPayServer.Controllers
{
if (!model.Settings.IsComplete())
{
model.StatusMessage = "Error: Required fields missing";
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
return View(model);
}
@ -1042,18 +1064,18 @@ namespace BTCPayServer.Controllers
var client = model.Settings.CreateSmtpClient();
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";
TempData[WellKnownTempData.SuccessMessage] = "Email sent to " + model.TestEmail + ", please, verify you received it";
}
catch (Exception ex)
{
model.StatusMessage = "Error: " + ex.Message;
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
}
return View(model);
}
else // if(command == "Save")
{
await _SettingsRepository.UpdateSetting(model.Settings);
model.StatusMessage = "Email settings saved";
TempData[WellKnownTempData.SuccessMessage] = "Email settings saved";
return View(model);
}
}
@ -1070,7 +1092,7 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(_Options.LogFile))
{
vm.StatusMessage = "Error: File Logging Option not specified. " +
TempData[WellKnownTempData.ErrorMessage] = "File Logging Option not specified. " +
"You need to set debuglog and optionally " +
"debugloglevel in the configuration or through runtime arguments";
}
@ -1079,7 +1101,7 @@ namespace BTCPayServer.Controllers
var di = Directory.GetParent(_Options.LogFile);
if (di == null)
{
vm.StatusMessage = "Error: Could not load log files";
TempData[WellKnownTempData.ErrorMessage] = "Could not load log files";
}
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_Options.LogFile);

Some files were not shown because too many files have changed in this diff Show More