Compare commits
224 Commits
v1.0.3.129
...
v1.0.3.137
Author | SHA1 | Date | |
---|---|---|---|
aab7c08500 | |||
ff8de47e69 | |||
3ff9cc85ef | |||
0a2440e14c | |||
d37abb53f0 | |||
656a3956b1 | |||
c31e7593b6 | |||
7be9083220 | |||
b85667a191 | |||
892d906ac7 | |||
0277708b2a | |||
d757aa2978 | |||
cf7bba9600 | |||
a0d498812d | |||
29e8783beb | |||
57b87a55bc | |||
9ff9377bc7 | |||
163ef031b9 | |||
1cfdd1489b | |||
0a9290a980 | |||
f0c2fb62df | |||
24d6532dac | |||
fee1fe22a7 | |||
d8103f7293 | |||
3d3446e46a | |||
22af4d2836 | |||
06b2a3383d | |||
06c3f01e41 | |||
d392880102 | |||
55dd8da284 | |||
e76f5b19b1 | |||
5650b8560d | |||
2d80dbfa8f | |||
c62aeb670a | |||
1a407a2da3 | |||
72d7e2dc5b | |||
a126ec1718 | |||
bf59114a00 | |||
6b587bfc31 | |||
28a2017b65 | |||
c3a6e9a799 | |||
45c66a167b | |||
380629a961 | |||
74fdd1358c | |||
e9d63650b9 | |||
9ecb27a9bd | |||
b0ae878ef6 | |||
28cc4facd4 | |||
749146ad3c | |||
a4ba93d249 | |||
7eae1f3b3c | |||
d66e8f2d13 | |||
058b768b0a | |||
3425f84507 | |||
aad586232c | |||
b514533814 | |||
3b1f851f8a | |||
99095f25d9 | |||
d79a28ac6f | |||
120fe1ba85 | |||
ad8d8c31ab | |||
3e5ab70583 | |||
cd5b334a00 | |||
9a99b3fdc2 | |||
2249ee195a | |||
93cdbf7a1a | |||
22fa43f8d6 | |||
39a6bd15d1 | |||
1d8fe9fb93 | |||
565cac34b0 | |||
1cb02b2913 | |||
1174178771 | |||
f1a2cc2b65 | |||
36d97d1e74 | |||
12264d8e74 | |||
4d68b12080 | |||
3c7137830e | |||
4f1b4131cb | |||
ccb45e3a99 | |||
f3461bfbe6 | |||
22b05500f1 | |||
66a2383ad1 | |||
7d6fb21a8c | |||
5054e76d64 | |||
9adb825c30 | |||
9a28dc5121 | |||
c82c67359c | |||
59afc05661 | |||
c05820224e | |||
fee106abef | |||
48fa11759f | |||
eac4c91820 | |||
037fcf93f5 | |||
dabe805602 | |||
dacf5c1e16 | |||
da2e8665a1 | |||
8643c04a39 | |||
c5ba063edf | |||
9648836e2d | |||
3c9b58916b | |||
d56a5ad86e | |||
cd5cc6435c | |||
c908301b84 | |||
e68d76b56d | |||
8339ec59b7 | |||
c5cfd7921a | |||
e584004b23 | |||
281a2461ad | |||
bd94b5f84e | |||
2b040b3465 | |||
0e452c7cdf | |||
2acdc77289 | |||
fda6a1a77b | |||
7e5c593e09 | |||
40b191ef49 | |||
a92f0fe289 | |||
ca17efbc29 | |||
5025e0dd4d | |||
1325c5d441 | |||
30585d2cc1 | |||
98468f4eb0 | |||
39876dea07 | |||
03917ec806 | |||
1c9a91140b | |||
3417556f5c | |||
0cc2fa962d | |||
8ba1303968 | |||
ae2b055fb5 | |||
a919d3ddec | |||
102b38b5a8 | |||
56a363adf9 | |||
5c8dcb0292 | |||
0fd5c722f6 | |||
b86befbdaf | |||
53310dee8a | |||
78b86ce0ea | |||
3bdc7c102a | |||
f9714f0be0 | |||
536f98b566 | |||
f4977e7f9f | |||
68807bae37 | |||
ccc2d0e13c | |||
c2032ee15b | |||
724a5b5460 | |||
c9ec0f9d3c | |||
411fe90b8c | |||
a56004fbef | |||
e75edac3c1 | |||
aaa05eb5ec | |||
8d0d80e086 | |||
4d84343a80 | |||
275fbc81e7 | |||
d23adfbd78 | |||
f7b85babfe | |||
56e85b68d9 | |||
755a6bf8e6 | |||
7282199c31 | |||
639f5d2fc4 | |||
8c8ef9d3ca | |||
2c5c6d28e3 | |||
fd78d02576 | |||
3a0328d0be | |||
d66b111121 | |||
8cbc58ea2f | |||
3366c86b16 | |||
c7e3241a85 | |||
1c2c3ede80 | |||
0f46da2e6b | |||
514386ecdd | |||
2257b95732 | |||
7a5cfcf50f | |||
e8d5190569 | |||
d6d13ec001 | |||
f3aa67e0f1 | |||
59839a3332 | |||
fa18bd9a69 | |||
bf9dd57177 | |||
a230e21737 | |||
e763e9e41a | |||
2e0abdbd06 | |||
f3890cd029 | |||
2f918b1195 | |||
5b73f22eb4 | |||
edfdae744f | |||
438fc34ad2 | |||
9e107b1eb1 | |||
a8e2a99faa | |||
0823a3e0dc | |||
7ac72c6c2a | |||
6d703d590b | |||
3dada3c464 | |||
a2cb6178b8 | |||
42dda56eea | |||
6407e15187 | |||
af97e296ba | |||
200c9709fe | |||
18b8bec8d0 | |||
41d714e2ce | |||
60a8b23c03 | |||
0096250384 | |||
2ce0749bb6 | |||
7ab97311be | |||
e6cfb6e851 | |||
b3298589c3 | |||
127ad3e573 | |||
2a262c4e1e | |||
ee804c9922 | |||
3a8e136f39 | |||
8eec6db825 | |||
1c90b6227c | |||
8b7ea6c71f | |||
3fc9d0c010 | |||
345ce6ba5a | |||
d9cd00f49a | |||
2266c0dc96 | |||
882430cf58 | |||
11730cbae6 | |||
dc3abc76c3 | |||
0013703cef | |||
91b1a5e3e5 | |||
911faeb510 | |||
8fa9834bf6 | |||
cf930fc46a | |||
9ca9b5a5d2 |
@ -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:
|
||||
|
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
custom: https://foundation.btcpayserver.org
|
@ -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)"
|
@ -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>())
|
||||
{
|
@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class MoneroLikeSpecificBtcPayNetwork : BTCPayNetworkBase
|
||||
{
|
||||
public int MaxTrackedConfirmation = 10;
|
||||
}
|
||||
}
|
121
BTCPayServer.Common/Altcoins/Monero/RPC/JsonRpcClient.cs
Normal file
121
BTCPayServer.Common/Altcoins/Monero/RPC/JsonRpcClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
33
BTCPayServer.Common/Altcoins/Monero/RPC/Models/Info.cs
Normal file
33
BTCPayServer.Common/Altcoins/Monero/RPC/Models/Info.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
9
BTCPayServer.Common/Altcoins/Monero/RPC/Models/Peer.cs
Normal file
9
BTCPayServer.Common/Altcoins/Monero/RPC/Models/Peer.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
20
BTCPayServer.Common/Altcoins/Monero/Utils/MoneroMoney.cs
Normal file
20
BTCPayServer.Common/Altcoins/Monero/Utils/MoneroMoney.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -52,9 +52,11 @@ 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;
|
||||
public string UriScheme { get; internal set; }
|
||||
public KeyPath GetRootKeyPath(DerivationType type)
|
||||
{
|
||||
KeyPath baseKey;
|
||||
@ -102,10 +104,8 @@ namespace BTCPayServer
|
||||
|
||||
public abstract class BTCPayNetworkBase
|
||||
{
|
||||
|
||||
public string CryptoCode { get; internal set; }
|
||||
public string BlockExplorerLink { get; internal set; }
|
||||
public string UriScheme { get; internal set; }
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[Obsolete("Should not be needed")]
|
||||
@ -118,8 +118,6 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
public string CryptoImagePath { get; set; }
|
||||
|
||||
public int MaxTrackedConfirmation { get; internal set; } = 6;
|
||||
public string[] DefaultRateRules { get; internal set; } = Array.Empty<string>();
|
||||
public override string ToString()
|
||||
{
|
||||
@ -133,7 +131,7 @@ namespace BTCPayServer
|
||||
|
||||
public virtual string ToString<T>(T obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
return NBitcoin.JsonConverters.Serializer.ToString(obj, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
259
BTCPayServer.Common/Shims.cs
Normal file
259
BTCPayServer.Common/Shims.cs
Normal 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
|
@ -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>
|
||||
|
@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@ -91,10 +95,10 @@ namespace BTCPayServer.Data
|
||||
builder.UseSqlite(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data"));
|
||||
else if (_Type == DatabaseType.Postgres)
|
||||
builder
|
||||
.UseNpgsql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data"))
|
||||
.UseNpgsql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10))
|
||||
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
|
||||
else if (_Type == DatabaseType.MySQL)
|
||||
builder.UseMySql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data"));
|
||||
builder.UseMySql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -84,15 +84,16 @@ namespace BTCPayServer.Services.Rates
|
||||
{ "ZEUR", "EUR" },
|
||||
{ "ZJPY", "JPY" },
|
||||
{ "ZCAD", "CAD" },
|
||||
{ "ZGBP", "GBP" }
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
@ -150,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))
|
||||
{
|
||||
@ -158,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;
|
||||
|
36
BTCPayServer.Rating/Providers/NdaxRateProvider.cs
Normal file
36
BTCPayServer.Rating/Providers/NdaxRateProvider.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class NdaxRateProvider : IRateProvider, IHasExchangeName
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public NdaxRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public string ExchangeName => "ndax";
|
||||
|
||||
public async Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://ndax.io/api/returnTicker", cancellationToken);
|
||||
var jobj = await response.Content.ReadAsAsync<Dictionary<string, JObject>>(cancellationToken);
|
||||
return new ExchangeRates(jobj.Select(pair => new ExchangeRate(ExchangeName, CurrencyPair.Parse(pair.Key),
|
||||
new BidAsk(GetValue(pair.Value["highestBid"]), GetValue(pair.Value["lowestAsk"])))));
|
||||
}
|
||||
|
||||
private static decimal GetValue(JToken jobj)
|
||||
{
|
||||
return string.IsNullOrEmpty(jobj.ToString()) ? 0 : jobj.Value<decimal>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -115,6 +115,7 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
|
||||
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()));
|
||||
@ -171,6 +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")); Buggy
|
||||
exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices"));
|
||||
|
||||
return exchanges;
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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,13 +118,20 @@ 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))
|
||||
config.AppendLine($"sshkeyfile={SSHKeyFile}");
|
||||
if (!string.IsNullOrEmpty(SSHConnection))
|
||||
config.AppendLine($"sshconnection={SSHConnection}");
|
||||
|
||||
if (TestDatabase == TestDatabases.MySQL && !String.IsNullOrEmpty(MySQL))
|
||||
config.AppendLine($"mysql=" + MySQL);
|
||||
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();
|
||||
@ -134,6 +141,7 @@ namespace BTCPayServer.Tests
|
||||
_Host = new WebHostBuilder()
|
||||
.UseConfiguration(conf)
|
||||
.UseContentRoot(FindBTCPayServerDirectory())
|
||||
.UseWebRoot(Path.Combine(FindBTCPayServerDirectory(), "wwwroot"))
|
||||
.ConfigureServices(s =>
|
||||
{
|
||||
s.AddLogging(l =>
|
||||
@ -148,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)
|
||||
@ -223,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);
|
||||
@ -258,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");
|
||||
}
|
||||
|
||||
@ -280,9 +288,12 @@ namespace BTCPayServer.Tests
|
||||
return _Host.Services.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider => _Host.Services;
|
||||
public IServiceProvider ServiceProvider => _Host.Services;
|
||||
|
||||
public T GetController<T>(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller
|
||||
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, bool isAdmin = false) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Host = new HostString("127.0.0.1", Port);
|
||||
@ -291,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)
|
||||
{
|
||||
|
@ -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));
|
||||
|
174
BTCPayServer.Tests/CheckoutUITests.cs
Normal file
174
BTCPayServer.Tests/CheckoutUITests.cs
Normal file
@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitpayClient;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
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.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanHandleRefundEmailForm()
|
||||
{
|
||||
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
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");
|
||||
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
|
||||
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
|
||||
s.GoToHome();
|
||||
var invoiceId = s.CreateInvoice(store.storeName);
|
||||
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");
|
||||
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(Timeout = TestTimeout)]
|
||||
public async Task CanUseLanguageDropdown()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
|
||||
var invoiceId = s.CreateInvoice(store.storeName);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.True(s.Driver.FindElement(By.Id("DefaultLang")).FindElements(By.TagName("option")).Count > 1);
|
||||
var payWithTextEnglish = s.Driver.FindElement(By.Id("pay-with-text")).Text;
|
||||
|
||||
var prettyDropdown = s.Driver.FindElement(By.Id("prettydropdown-DefaultLang"));
|
||||
prettyDropdown.Click();
|
||||
await Task.Delay(200);
|
||||
prettyDropdown.FindElement(By.CssSelector("[data-value=\"da-DK\"]")).Click();
|
||||
await Task.Delay(1000);
|
||||
Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text);
|
||||
s.Driver.Navigate().GoToUrl(s.Driver.Url + "?lang=da-DK");
|
||||
|
||||
Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text);
|
||||
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUsePaymentMethodDropdown()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddDerivationScheme("BTC");
|
||||
|
||||
//check that there is no dropdown since only one payment method is set
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||
s.GoToHome();
|
||||
s.GoToStore(store.storeId);
|
||||
s.AddDerivationScheme("LTC");
|
||||
s.AddLightningNode("BTC",LightningConnectionType.CLightning);
|
||||
//there should be three now
|
||||
invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
|
||||
Assert.Contains("BTC", currencyDropdownButton.Text);
|
||||
currencyDropdownButton.Click();
|
||||
|
||||
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);
|
||||
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(Timeout = TestTimeout)]
|
||||
public async Task CanUseLightningSatsFeature()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
s.AddInternalLightningNode("BTC");
|
||||
s.GoToStore(store.storeId, StoreNavPages.Checkout);
|
||||
s.SetCheckbox(s, "LightningAmountInSatoshi", true);
|
||||
var command = s.Driver.FindElement(By.Name("command"));
|
||||
|
||||
command.ForceClick();
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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()
|
||||
|
@ -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"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -14,7 +14,6 @@ namespace BTCPayServer.Tests
|
||||
public static void ScrollTo(this IWebDriver driver, By by)
|
||||
{
|
||||
var element = driver.FindElement(by);
|
||||
((IJavaScriptExecutor)driver).ExecuteScript($"window.scrollBy({element.Location.X},{element.Location.Y});");
|
||||
}
|
||||
/// <summary>
|
||||
/// Sometimes the chrome driver is fucked up and we need some magic to click on the element.
|
||||
@ -29,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
|
||||
{
|
||||
@ -70,5 +74,27 @@ namespace BTCPayServer.Tests
|
||||
var vr = Assert.IsType<ViewResult>(result);
|
||||
return Assert.IsType<T>(vr.Model);
|
||||
}
|
||||
|
||||
public static void AssertElementNotFound(this IWebDriver driver, By by)
|
||||
{
|
||||
DateTimeOffset now = DateTimeOffset.Now;
|
||||
var wait = SeleniumTester.ImplicitWait;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
@ -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()
|
||||
|
@ -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");
|
||||
|
@ -2,6 +2,8 @@ using System;
|
||||
using BTCPayServer;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
@ -9,8 +11,17 @@ using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using Xunit;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -28,35 +39,50 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
}
|
||||
|
||||
public void Start()
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
Server.Start();
|
||||
await Server.StartAsync();
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
options.AddArguments("headless"); // Comment to view browser
|
||||
options.AddArguments("window-size=1200x600"); // Comment to view browser
|
||||
var isDebug = !Server.PayTester.InContainer;
|
||||
|
||||
if (!isDebug)
|
||||
{
|
||||
options.AddArguments("headless"); // Comment to view browser
|
||||
options.AddArguments("window-size=1200x1000"); // Comment to view browser
|
||||
}
|
||||
options.AddArgument("shm-size=2g");
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
options.AddArgument("no-sandbox");
|
||||
}
|
||||
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
|
||||
if (isDebug)
|
||||
{
|
||||
//when running locally, depending on your resolution, the website may go into mobile responsive mode and screw with navigation of tests
|
||||
Driver.Manage().Window.Maximize();
|
||||
}
|
||||
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() + "@a.com";
|
||||
Driver.FindElement(By.Id("Register")).Click();
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
|
||||
@ -78,15 +104,39 @@ namespace BTCPayServer.Tests
|
||||
return (usr, Driver.FindElement(By.Id("Id")).GetAttribute("value"));
|
||||
}
|
||||
|
||||
public void AddDerivationScheme(string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
|
||||
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
|
||||
{
|
||||
Driver.FindElement(By.Id("ModifyBTC")).ForceClick();
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme);
|
||||
Driver.FindElement(By.Id("Continue")).ForceClick();
|
||||
Driver.FindElement(By.Id("Confirm")).ForceClick();
|
||||
Driver.FindElement(By.Id("Save")).ForceClick();
|
||||
return;
|
||||
}
|
||||
|
||||
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
{
|
||||
string connectionString = null;
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
connectionString = "type=charge;server=" + Server.MerchantCharge.Client.Uri.AbsoluteUri;
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
connectionString = "type=clightning;server=" + ((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri;
|
||||
else if (connectionType == LightningConnectionType.LndREST)
|
||||
connectionString = $"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
|
||||
Driver.FindElement(By.Id($"save")).ForceClick();
|
||||
}
|
||||
|
||||
public void AddInternalLightningNode(string cryptoCode)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Id($"internal-ln-node-setter")).ForceClick();
|
||||
Driver.FindElement(By.Id($"save")).ForceClick();
|
||||
}
|
||||
|
||||
public void ClickOnAllSideMenus()
|
||||
{
|
||||
@ -95,31 +145,13 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
public string CreateInvoice(string random, string refundEmail = "")
|
||||
{
|
||||
Driver.FindElement(By.Id("Invoices")).Click();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
Driver.FindElement(By.CssSelector("input#Amount.form-control")).SendKeys("100");
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys("Deriv" + random + Keys.Enter);
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
|
||||
var id = statusElement.Text.Split(" ")[1];
|
||||
if (!string.IsNullOrEmpty(refundEmail))
|
||||
{
|
||||
GoToInvoiceCheckout(id);
|
||||
Driver.FindElement(By.Id("emailAddressFormInput")).SendKeys(refundEmail);
|
||||
Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"))
|
||||
.Click();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@ -159,16 +191,21 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
|
||||
public void GoToStore(string storeId)
|
||||
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
Driver.FindElement(By.Id($"update-store-{storeId}")).Click();
|
||||
if (storeNavPage != StoreNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id(storeNavPage.ToString())).Click();
|
||||
}
|
||||
}
|
||||
|
||||
public void GoToInvoiceCheckout(string invoiceId)
|
||||
{
|
||||
Driver.FindElement(By.Id("Invoices")).Click();
|
||||
Driver.FindElement(By.Id($"invoice-checkout-{invoiceId}")).Click();
|
||||
CheckForJSErrors();
|
||||
}
|
||||
|
||||
|
||||
@ -184,5 +221,68 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
SetCheckbox(s.Driver.FindElement(By.Name(inputName)), value);
|
||||
}
|
||||
|
||||
public void ScrollToElement(IWebElement element)
|
||||
{
|
||||
Actions actions = new Actions(Driver);
|
||||
actions.MoveToElement(element);
|
||||
actions.Perform();
|
||||
}
|
||||
|
||||
public void GoToInvoices()
|
||||
{
|
||||
Driver.FindElement(By.Id("Invoices")).Click();
|
||||
}
|
||||
|
||||
public void GoToCreateInvoicePage()
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
}
|
||||
|
||||
public string CreateInvoice(string store, decimal amount = 100, string currency = "USD", string refundEmail = "")
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
|
||||
var currencyEl = Driver.FindElement(By.Id("Currency"));
|
||||
currencyEl.Clear();
|
||||
currencyEl.SendKeys(currency);
|
||||
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys(store + Keys.Enter);
|
||||
Driver.FindElement(By.Id("Create")).ForceClick();
|
||||
Assert.True(Driver.PageSource.Contains("just created!"), "Unable to create Invoice");
|
||||
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
|
||||
var id = statusElement.Text.Split(" ")[1];
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void CheckForJSErrors()
|
||||
{
|
||||
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
|
||||
// var errorStrings = new List<string>
|
||||
// {
|
||||
// "SyntaxError",
|
||||
// "EvalError",
|
||||
// "ReferenceError",
|
||||
// "RangeError",
|
||||
// "TypeError",
|
||||
// "URIError"
|
||||
// };
|
||||
//
|
||||
// var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
|
||||
//
|
||||
// if (jsErrors.Any())
|
||||
// {
|
||||
// Logs.Tester.LogInformation("JavaScript error(s):" + Environment.NewLine + jsErrors.Aggregate("", (s, entry) => s + entry.Message + Environment.NewLine));
|
||||
// }
|
||||
// Assert.Empty(jsErrors);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,24 +7,27 @@ using Xunit.Abstractions;
|
||||
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();
|
||||
@ -33,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);
|
||||
@ -73,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();
|
||||
@ -88,18 +89,54 @@ 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]
|
||||
public void CanUseDynamicDns()
|
||||
[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);
|
||||
using (var client = await s.Server.PayTester.GetService<BTCPayServer.Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
|
||||
{
|
||||
var result = await client.RunBash("echo hello");
|
||||
Assert.Equal(string.Empty, result.Error);
|
||||
Assert.Equal("hello\n", result.Output);
|
||||
Assert.Equal(0, result.ExitStatus);
|
||||
}
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).SendKeys("tes't\r\ntest2");
|
||||
s.Driver.FindElement(By.Id("submit")).ForceClick();
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
|
||||
// Browser replace \n to \r\n, so it is hard to compare exactly what we want
|
||||
Assert.Contains("tes't", text);
|
||||
Assert.Contains("test2", text);
|
||||
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
s.Driver.FindElement(By.Id("submit")).ForceClick();
|
||||
|
||||
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
|
||||
Assert.DoesNotContain("test2", text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseDynamicDns()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
|
||||
@ -144,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();
|
||||
@ -157,8 +194,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(store, s.Driver.PageSource);
|
||||
var storeUrl = s.Driver.Url;
|
||||
s.ClickOnAllSideMenus();
|
||||
|
||||
CreateInvoice(s, store);
|
||||
s.GoToInvoices();
|
||||
s.CreateInvoice(store);
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
var invoiceUrl = s.Driver.Url;
|
||||
|
||||
@ -168,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);
|
||||
@ -191,46 +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 CanCreateInvoice()
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUsePairing()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Start();
|
||||
s.RegisterNewUser();
|
||||
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();
|
||||
|
||||
CreateInvoice(s, store);
|
||||
s.Driver.FindElement(By.Id("Tokens")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
|
||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void CreateInvoice(SeleniumTester s, string store)
|
||||
private static string AssertUrlHasPairingCode(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("Invoices")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("input#Amount.form-control")).SendKeys("100");
|
||||
s.Driver.FindElement(By.Name("StoreId")).SendKeys(store + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
Assert.True(s.Driver.PageSource.Contains("just created!"), "Unable to create Invoice");
|
||||
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]
|
||||
public void CanCreateAppPoS()
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanCreateAppPoS()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Start();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
|
||||
@ -249,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();
|
||||
@ -269,7 +345,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC");
|
||||
s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY");
|
||||
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Submit();
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.True(s.Driver.PageSource.Contains("Currently Active!"), "Unable to create CF");
|
||||
@ -277,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();
|
||||
@ -292,7 +368,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Submit();
|
||||
s.Driver.FindElement(By.Id("SaveButton")).ForceClick();
|
||||
s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return);
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request");
|
||||
@ -300,20 +376,20 @@ 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
|
||||
// to sign the transaction
|
||||
var mnemonic = "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage";
|
||||
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
||||
s.AddDerivationScheme("ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD");
|
||||
s.AddDerivationScheme("BTC", "ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD");
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create("bcrt1qmxg8fgnmkp354vhe78j6sr4ut64tyz2xyejel4", Network.RegTest), Money.Coins(3.0m));
|
||||
s.Server.ExplorerNode.Generate(1);
|
||||
|
||||
@ -322,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");
|
||||
|
@ -71,6 +71,10 @@ namespace BTCPayServer.Tests
|
||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||
PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false"));
|
||||
|
||||
PayTester.SSHPassword = GetEnvironment("TESTS_SSHPASSWORD", "opD3i2282D");
|
||||
PayTester.SSHKeyFile = GetEnvironment("TESTS_SSHKEYFILE", "");
|
||||
PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622");
|
||||
}
|
||||
|
||||
public bool Dockerized
|
||||
@ -78,9 +82,9 @@ namespace BTCPayServer.Tests
|
||||
get; set;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
public Task StartAsync()
|
||||
{
|
||||
PayTester.Start();
|
||||
return PayTester.StartAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -162,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 =>
|
||||
|
28
BTCPayServer.Tests/docker-compose.monero.yml
Normal file
28
BTCPayServer.Tests/docker-compose.monero.yml
Normal 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:
|
@ -16,8 +16,6 @@ services:
|
||||
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
|
||||
TESTS_DB: "Postgres"
|
||||
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
|
||||
TESTS_MYSQL: User ID=root;Host=mysql;Port=3306;Database=btcpayserver
|
||||
TESTS_PORT: 80
|
||||
TESTS_HOSTNAME: tests
|
||||
TESTS_RUN_EXTERNAL_INTEGRATION: ${TESTS_RUN_EXTERNAL_INTEGRATION:-false}
|
||||
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
|
||||
@ -26,6 +24,9 @@ services:
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify"
|
||||
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
|
||||
TESTS_INCONTAINER: "true"
|
||||
TESTS_SSHCONNECTION: "root@sshd:22"
|
||||
TESTS_SSHPASSWORD: ""
|
||||
TESTS_SSHKEYFILE: ""
|
||||
expose:
|
||||
- "80"
|
||||
links:
|
||||
@ -33,26 +34,34 @@ services:
|
||||
extra_hosts:
|
||||
- "tests:127.0.0.1"
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
|
||||
- "merchant_lightningd_datadir:/etc/merchant_lightningd_datadir"
|
||||
|
||||
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||
dev:
|
||||
image: btcpayserver/bitcoin:0.18.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
image: alpine:3.7
|
||||
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- mysql
|
||||
- customer_lightningd
|
||||
- merchant_lightningd
|
||||
- lightning-charged
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
|
||||
sshd:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: sshd.Dockerfile
|
||||
ports:
|
||||
- "21622:22"
|
||||
expose:
|
||||
- 22
|
||||
volumes:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:0.18.0
|
||||
@ -64,11 +73,10 @@ services:
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- mysql
|
||||
- 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"
|
||||
@ -119,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:
|
||||
@ -132,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
|
||||
@ -146,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
|
||||
@ -166,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"
|
||||
@ -178,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:
|
||||
@ -213,18 +222,9 @@ services:
|
||||
- "39372:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.12
|
||||
expose:
|
||||
- "3306"
|
||||
ports:
|
||||
- "33036:3306"
|
||||
environment:
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
|
||||
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"
|
||||
@ -287,6 +285,7 @@ services:
|
||||
- bitcoind
|
||||
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
customer_lightningd_datadir:
|
||||
merchant_lightningd_datadir:
|
||||
|
12
BTCPayServer.Tests/sshd.Dockerfile
Normal file
12
BTCPayServer.Tests/sshd.Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM alpine:3.8
|
||||
|
||||
RUN apk add --no-cache openssh sudo bash
|
||||
RUN ssh-keygen -f /root/.ssh/id_rsa -t rsa -q -P "" -m PEM
|
||||
RUN echo 'root:opD3i2282D' | chpasswd
|
||||
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
|
||||
RUN ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa && \
|
||||
ssh-keygen -f /etc/ssh/ssh_host_dsa_key -N '' -t dsa && \
|
||||
ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa && \
|
||||
ssh-keygen -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519
|
||||
|
||||
CMD ["/usr/sbin/sshd", "-D"]
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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="2.9.406" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="2.9.406" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="2.9.406" />
|
||||
<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>
|
@ -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));
|
||||
@ -245,6 +250,7 @@ namespace BTCPayServer.Configuration
|
||||
}
|
||||
settings.Password = conf.GetOrDefault<string>("sshpassword", "");
|
||||
settings.KeyFile = conf.GetOrDefault<string>("sshkeyfile", "");
|
||||
settings.AuthorizedKeysFile = conf.GetOrDefault<string>("sshauthorizedkeys", "");
|
||||
settings.KeyFilePassword = conf.GetOrDefault<string>("sshkeyfilepassword", "");
|
||||
return settings;
|
||||
}
|
||||
|
@ -40,12 +40,14 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--sshpassword", "SSH password to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshkeyfile", "SSH private key file to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshauthorizedkeys", "Path to a authorized_keys file that BTCPayServer can modify from the website (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
|
||||
app.Option("--torrcfile", "Path to torrc file containing hidden services directories (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--socksendpoint", "Socks endpoint to connect to onion urls (default: empty)", CommandOptionType.SingleValue);
|
||||
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();
|
||||
|
@ -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)
|
||||
|
@ -76,6 +76,7 @@ namespace BTCPayServer.Configuration
|
||||
Spark,
|
||||
RTL,
|
||||
Charge,
|
||||
P2P
|
||||
P2P,
|
||||
RPC
|
||||
}
|
||||
}
|
||||
|
7
BTCPayServer/Contracts/IStoreNavExtension.cs
Normal file
7
BTCPayServer/Contracts/IStoreNavExtension.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public interface IStoreNavExtension
|
||||
{
|
||||
string Partial { get; }
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ namespace BTCPayServer.Controllers
|
||||
Description = settings.Description,
|
||||
NotificationEmail = settings.NotificationEmail,
|
||||
NotificationUrl = settings.NotificationUrl,
|
||||
SearchTerm = $"storeid:{app.StoreDataId}",
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue? settings.RedirectAutomatically.Value? "true": "false" : ""
|
||||
};
|
||||
if (HttpContext?.Request != null)
|
||||
@ -200,7 +201,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
});
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
StatusMessage = "App updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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;
|
||||
@ -41,7 +41,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = id,
|
||||
InvoiceId = new[] {id},
|
||||
StoreId = new[] { HttpContext.GetStoreData().Id }
|
||||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
|
@ -32,12 +32,12 @@ 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()
|
||||
{
|
||||
InvoiceId = invoiceId,
|
||||
InvoiceId = new[] {invoiceId},
|
||||
UserId = GetUserId(),
|
||||
IncludeAddresses = true,
|
||||
IncludeEvents = true
|
||||
@ -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,16 +232,17 @@ 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)),
|
||||
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
|
||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||
BtcDue = accounting.Due.ToString(),
|
||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(),
|
||||
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,
|
||||
@ -295,7 +293,7 @@ namespace BTCPayServer.Controllers
|
||||
.ToList()
|
||||
};
|
||||
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto);
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob);
|
||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||
model.PaymentMethodId = paymentMethodId.ToString();
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
@ -358,6 +356,7 @@ namespace BTCPayServer.Controllers
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(WebSocketException) { }
|
||||
finally
|
||||
{
|
||||
leases.Dispose();
|
||||
@ -395,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)
|
||||
{
|
||||
@ -404,7 +403,6 @@ namespace BTCPayServer.Controllers
|
||||
SearchTerm = searchTerm,
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
StatusMessage = StatusMessage,
|
||||
TimezoneOffset = timezoneOffset
|
||||
};
|
||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
|
||||
@ -455,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)
|
||||
{
|
||||
@ -489,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");
|
||||
}
|
||||
|
||||
@ -505,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)
|
||||
{
|
||||
@ -536,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()
|
||||
@ -565,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)
|
||||
@ -577,24 +554,21 @@ 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)
|
||||
{
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
InvoiceId = invoiceId,
|
||||
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);
|
||||
@ -617,13 +591,6 @@ namespace BTCPayServer.Controllers
|
||||
public string StatusString { get; set; }
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
|
@ -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());
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user