Compare commits

..

184 Commits

Author SHA1 Message Date
4c349803b6 Use Channel instead of blocking collection for the Invoice Watcher 2019-11-16 19:12:26 +09:00
35a4278ae8 Merge pull request #1161 from britttttk/app-button
Hide View button in crowdfund
2019-11-16 18:58:11 +09:00
06365b7b73 Merge pull request #1158 from Kukks/register-login-secure-lock
Do not allow login or register over an insecure connection
2019-11-16 18:15:16 +09:00
f31e013986 bump 2019-11-16 18:03:16 +09:00
fe1df743ce bump nbx 2019-11-16 17:22:51 +09:00
22ffd48cd4 refactor 2019-11-16 08:57:16 +01:00
c83bcd259d Testing happy messages, fix approval button 2019-11-16 15:20:54 +09:00
8ebac28ccf Fix tests 2019-11-16 15:07:02 +09:00
4e5001d7ef Delete password from the lnd seed backup 2019-11-16 14:07:43 +09:00
84fe14a1ed Test LND Seed Backup service and Logs. Use the Confirm View instead of poppup 2019-11-16 14:06:37 +09:00
6828730313 Merge pull request #1162 from dennisreimann/theme-fixes
Theme fixes
2019-11-16 12:36:50 +09:00
e540079220 Merge pull request #1163 from dennisreimann/main-bundle-script-order
Fix script order in main bundle
2019-11-16 12:36:03 +09:00
e99f806b66 Merge pull request #1164 from dennisreimann/page-titles
Page title fixes
2019-11-16 12:35:45 +09:00
b452f1d125 Limit custom table styles to small tables
Otherwise it overrides to many styles so that i.e. font-sizes with th .h*-classes do not work anymore.
2019-11-15 15:15:22 +01:00
f0a9d209a5 Fix bootstrap reference in cart bundle 2019-11-15 15:15:21 +01:00
984aa2912a Fix script order in main bundle
creative.js relies on ScrollReveal being present on initialization, so it has to be included after scrollreveal.min.js
2019-11-15 15:15:20 +01:00
c8937f6d46 POS app theme fixes 2019-11-15 15:15:19 +01:00
cb849ab23e Light/dark color fixes
fix
2019-11-15 15:15:18 +01:00
3bca3427eb Consistent use of page title on Pay Button page 2019-11-15 14:44:08 +01:00
a127b13db1 Add missing payment request page titles 2019-11-15 14:43:36 +01:00
a2f608266b Fix bootstrap reference in cart bundle 2019-11-15 14:01:18 +01:00
05f8b8d8af Fix script order in main bundle
creative.js relies on ScrollReveal being present on initialization, so it has to be included after scrollreveal.min.js
2019-11-15 13:56:03 +01:00
f1cef81d76 Do not allow login or register over an insecure connection 2019-11-15 12:39:17 +01:00
e1bf376f10 Mark up alert-links for better display 2019-11-15 11:24:27 +01:00
9298eab30d Improve QR code display on dark background 2019-11-15 11:23:52 +01:00
584528f903 Improve pay button display on dark background 2019-11-15 11:08:10 +01:00
3501920956 Set apart theme select from custom css file references 2019-11-15 11:06:09 +01:00
65a867e82d Fix css references on bootstrap kitchensink page 2019-11-15 11:05:12 +01:00
2bfea50014 Update NBXplorer 2019-11-15 18:53:20 +09:00
89539fca01 Fix build 2019-11-15 18:20:10 +09:00
240f6fa45a Allow /rates to pass storeId 2019-11-15 18:10:14 +09:00
c004065eea Apply some lipstick on the lnd seed backup 2019-11-15 17:55:55 +09:00
1cfba82d77 Hide button when missing required fields 2019-11-14 23:58:38 -07:00
3bdcbd8907 Small adjustment to make tables look better 2019-11-15 15:22:01 +09:00
fd53811fdd Adjust base font-size to match old style 2019-11-15 14:45:53 +09:00
5eeda40098 Addressing CSS inconsistencies compared to old design
Fixes: #1159
2019-11-14 22:48:15 -06:00
7bce316e78 Merge branch 'master' into feature/lndseedbackup 2019-11-14 21:02:56 -06:00
68f172b9ef Switching back to v as prefix, other scripts assume it's presence
z wouldn't work because some scripts in btcpayserver-docker re-append v as prefix to find tag
2019-11-14 21:00:39 -06:00
9b3e7338ba Fixing broken CrowdfundTests.CanContributeOnlyWhenAllowed test 2019-11-14 20:56:28 -06:00
c6ef6fc17d Removing bootstrap and creative start vendor dirs, moving to main 2019-11-14 20:41:53 -06:00
e4c797eaa7 Merge pull request #1137 from dennisreimann/ui-css-refactoring
UI: Support themes via CSS variables
2019-11-14 20:28:01 -06:00
bdca51a178 Fine tuning the classic theme 2019-11-14 21:10:17 +01:00
e147ce8606 Minor layout improvements 2019-11-14 18:45:35 +01:00
830286ecc1 Center content on forgot password page 2019-11-14 18:44:14 +01:00
bc56cab488 Fix colors in wallet send view 2019-11-14 18:44:13 +01:00
dd683db6d5 Bump up font size and spacings in classic theme 2019-11-14 18:44:12 +01:00
5436999b13 Fix welcome layout background 2019-11-14 18:44:11 +01:00
29946763ba Move theme switch to top 2019-11-14 18:42:58 +01:00
7160f7dc22 Add bootstrap kitchensink
Can be used to control the look of all elements:
/_bootstrap_kitchensink.html?theme=casa
2019-11-14 18:42:57 +01:00
e44db30ddb Finetune color settings
fix
2019-11-14 18:42:56 +01:00
458b2fd72e Register page fixes 2019-11-14 18:42:55 +01:00
66ffa5f987 Rename alpha theme variables to backdrop 2019-11-14 18:42:54 +01:00
dd8caa46c4 Remove modern theme option
For now as it is not really shaped yet.
2019-11-14 18:42:53 +01:00
55de85d829 Casa theme color fixes 2019-11-14 18:42:52 +01:00
10f782aa11 Integrate and adapt new register page 2019-11-14 18:42:51 +01:00
5bb6918465 Add Casa theme draft 2019-11-14 18:42:50 +01:00
205fd3c9ce Use grid consistently 2019-11-14 18:42:49 +01:00
50bf55cbdf Unify section headings 2019-11-14 18:42:48 +01:00
3a9ecd8b33 Dissect css rules and simplify color variables 2019-11-14 18:42:48 +01:00
5942a840b2 Move main bundle files also 2019-11-14 18:42:47 +01:00
6dd3c79c23 Move themes and bootstrap 2019-11-14 18:42:46 +01:00
a25e0ef592 add-theme-picker 2019-11-14 18:42:45 +01:00
29465cbadc Use "modern" theme as example 2019-11-14 18:42:44 +01:00
fdc1aa25e4 Use variable based bootstrap and creativestart
fix
2019-11-14 18:42:40 +01:00
04fbe42bda Merge pull request #1139 from bolatovumar/issue-1138
Vary page layout for registering new accounts
2019-11-14 23:58:37 +09:00
57a6949565 Fix View app on Crowdfunding (#1141) 2019-11-14 21:13:41 +09:00
bcb85e2084 Errors not working in PointOfSale (#1141) 2019-11-14 20:55:18 +09:00
6900964c03 Fix debug log not showing (Fix #1157) 2019-11-14 18:49:33 +09:00
3d2b8d2969 Merge pull request #1156 from bolatovumar/fix-1155
Improve mobile view on new welcome layout
2019-11-14 17:26:58 +09:00
c7da4e3eff Add TestTimeouts 2019-11-14 17:14:08 +09:00
9e31154808 Improve mobile view on new welcome layout
fix #1155
2019-11-13 18:46:58 -08:00
89353b8e7c Remove svg trash 2019-11-13 18:50:23 +09:00
b8eea53e01 Merge pull request #1154 from pavlenex/supporters-dglabs
Add supporter to readme
2019-11-13 18:43:43 +09:00
9bafce8069 Add DG Labs logo to readme 2019-11-13 10:38:31 +01:00
a9de1c2c64 Add link to foundation website 2019-11-13 17:37:57 +09:00
e6505040e0 Missing svg 2019-11-13 16:06:41 +09:00
92bdb20e1c Add new sponsor 2019-11-13 16:04:47 +09:00
c6bba9188b Improve error message for CanUsePosApp 2019-11-13 15:56:58 +09:00
c3a84f6577 Merge pull request #1142 from Kukks/bugfix/u2f
Fix U2F bug when using multiple devices
2019-11-13 15:37:14 +09:00
ba54b5aacf Merge pull request #1144 from Kukks/ndax
Bump ExchangeSharp, fixes breaking changes and switch to exchangeshar…
2019-11-13 15:36:52 +09:00
e92a15e072 Add logs to find out if tests hang on website getting operational 2019-11-13 15:24:56 +09:00
ad853c6985 Merge pull request #1153 from NicolasDorier/nico/remove-trezor
Remove Trezor in AddDerivationScheme
2019-11-13 15:20:12 +09:00
e7ff5c0a4b Remove Trezor in AddDerivationScheme 2019-11-13 15:10:33 +09:00
fef1bde9b6 Merge pull request #1151 from NicolasDorier/update/nbxplorerclient
Bump NBXplorer.Client
2019-11-13 14:45:10 +09:00
eb87d7cadc Bump NBXplorer.Client 2019-11-13 13:51:06 +09:00
5394e0a4a0 Merge pull request #1143 from Kukks/bugfix/ln-sats
Display proper rate for ln sats feature
2019-11-12 14:01:13 +09:00
0f0b846e04 Merge pull request #1032 from bolatovumar/fix-1029
Retain store ids when filtering invoices
2019-11-11 21:00:42 +09:00
ae869489b5 Merge pull request #1133 from pavlenex/readme-2
Add CTA at the bottom + fix broken table tag
2019-11-11 20:58:53 +09:00
cdc973cbea fix tests 2019-11-11 11:25:23 +01:00
bae9ca2a7a fix ambigous ref 2019-11-11 10:08:10 +01:00
0df7506ad5 Bump ExchangeSharp, fixes breaking changes and switch to exchangesharp provider for ndax 2019-11-11 07:14:29 +01:00
974101624b fix text 2019-11-10 18:51:51 +01:00
621a7d998d Display proper rate for ln sats feature
fixes #1128
2019-11-10 18:49:24 +01:00
539c7d6e17 fix for multiple U2F devices 2019-11-10 18:12:20 +01:00
2743641122 hidden challenges rendered 2019-11-10 17:32:28 +01:00
1d08a811e7 Enabling circleci builds on feature tags 2019-11-10 07:00:30 -06:00
c5ca02e48e Js cleanup based on Kukks feedback 2019-11-10 06:22:17 -06:00
bce201038f Including walletunlock.json file in TestData for easier debugging 2019-11-10 06:19:59 -06:00
c24bb443d3 Fixing check for external services that don't specify server 2019-11-10 06:12:32 -06:00
9c165d84ef Removing LND seed information from walletunlock.json 2019-11-10 06:12:32 -06:00
58b834fe9d Displaying information from walletunlock.json if file is present 2019-11-10 06:12:32 -06:00
2b1aac9aa9 Service page for LND Seed Backup 2019-11-10 06:12:32 -06:00
dfdb99165b Vary page layout for registering new accounts
address #1138
2019-11-09 21:24:19 -08:00
b75eaee6dd Merge pull request #1115 from escapedcat/feat/ledger-xpub-help
feat(stores): add ledger specific xpub help
2019-11-10 00:28:05 +09:00
888b06376d Merge pull request #1132 from Kukks/xmr-fix
fix moneromoney util
2019-11-10 00:07:27 +09:00
7f6b1e7e6e Merge pull request #1135 from bolatovumar/fix-1129
Make sure documentation tooltip link stays inline with label
2019-11-10 00:06:36 +09:00
8dd1efc126 Adding test timeout 2019-11-09 16:57:40 +09:00
9cd64bf81a Inline "allow custom payment amount" checkbox with label 2019-11-08 19:12:52 -08:00
2c14cece6b Make sure documentation tooltip link stays inline with label
close #1129
2019-11-08 19:12:51 -08:00
11c269693e feat(stores): add ledger specific xpub help
problem: maximum of 20 ledger accounts are being listed in the derivation-scheme select
solution: help users with i.e. multiple ledger accounts by providing link to docs for detailed info on where to find the xpub key manually
2019-11-08 15:24:12 +01:00
ae9b93ca8f Removing useless tool 2019-11-08 22:41:14 +09:00
563e95df7c Fix tests 2019-11-08 21:42:34 +09:00
5b37a9df0b Make sure we don't pass the psbt in GET url 2019-11-08 21:21:23 +09:00
28396206de Show the torrc config when debugging via F5 2019-11-08 17:26:32 +09:00
9d3026f676 Add torrc files in tests 2019-11-08 16:10:49 +09:00
0f7458254e Retain store ids when filtering invoices 2019-11-07 18:14:38 -08:00
2305a0a5c8 Add CTA at the bottom + fix broken table tag 2019-11-07 19:43:25 +01:00
7ce2ddb400 fix moneromoney util 2019-11-07 18:41:10 +01:00
13c8372329 Merge pull request #1131 from pavlenex/supporters-final
Add supporters, remove listed alts
2019-11-07 23:17:29 +09:00
8fd7ee0763 Add supporters, remove listed alts 2019-11-07 11:33:11 +01:00
aab7c08500 Unify StatusMessage handling 2019-11-07 18:59:51 +09:00
ff8de47e69 Fix tests 2019-11-07 18:36:45 +09:00
3ff9cc85ef Fix tests 2019-11-07 18:35:47 +09:00
0a2440e14c Fix test 2019-11-07 18:29:52 +09:00
d37abb53f0 Fix status message when a model is passed 2019-11-07 18:20:17 +09:00
656a3956b1 Fix strange message in PaymentRequests (Fix #1130) 2019-11-07 17:59:35 +09:00
c31e7593b6 Add label to FullyNoded 2019-11-07 17:08:24 +09:00
7be9083220 Update sponsor 2019-11-07 16:46:10 +09:00
b85667a191 Fix status message of PaymentRequest 2019-11-07 16:19:36 +09:00
892d906ac7 Fix tx link in payment request 2019-11-07 16:12:05 +09:00
0277708b2a Fix tor service parsing 2019-11-07 15:58:03 +09:00
d757aa2978 Fix typo 2019-11-07 15:48:56 +09:00
cf7bba9600 Fix services if there is more than one tor external services 2019-11-07 15:41:39 +09:00
a0d498812d bump 2019-11-07 14:35:18 +09:00
29e8783beb Update translations 2019-11-07 14:34:34 +09:00
57b87a55bc Show Bitcoin RPC service 2019-11-07 14:33:10 +09:00
9ff9377bc7 Make sure the recommended fees are different from the network fee rate 2019-11-07 13:45:45 +09:00
163ef031b9 Make sure the recommended fees are different from the network fee rate 2019-11-07 13:35:47 +09:00
1cfdd1489b Merge pull request #1127 from bolatovumar/master
Make recommended fee confirmation target configurable
2019-11-07 13:27:03 +09:00
0a9290a980 Make recommended fee confirmation target configurable
Address #1110
2019-11-06 16:21:33 -08:00
f0c2fb62df Select all link when clicked 2019-11-07 00:13:00 +09:00
24d6532dac Add onion link to login/register 2019-11-06 23:58:04 +09:00
fee1fe22a7 Use better svg version of the logo 2019-11-06 23:25:56 +09:00
d8103f7293 Fix webfont 2019-11-06 21:57:43 +09:00
3d3446e46a Improve design of register/login on mobile 2019-11-06 21:51:10 +09:00
22af4d2836 Because links are not reusable in LightningWalletService, it should not open in a new tab 2019-11-06 21:17:50 +09:00
06b2a3383d Merge pull request #1125 from NicolasDorier/fix/maintainence
Improve message in Maintainance page
2019-11-06 16:41:11 +09:00
06c3f01e41 Improve message in Maintainance page 2019-11-06 16:17:03 +09:00
d392880102 Merge pull request #1121 from NicolasDorier/ux/login-register
Add sponsor and new design to the login and registration page
2019-11-06 15:29:36 +09:00
55dd8da284 Redirect first run to register, redirect unlogged to login 2019-11-06 15:19:14 +09:00
e76f5b19b1 bump lightning package 2019-11-06 13:20:57 +09:00
5650b8560d Refactor the Header in its own partial view 2019-11-06 13:15:26 +09:00
2d80dbfa8f Custom logo and custom url for checkout page should accept any string (relative path) (Fix #1124) 2019-11-06 12:02:13 +09:00
c62aeb670a Merge pull request #1120 from rockstardev/cifixrefactor
Fixing CircleCi to run external test only in repo context, small refactor of LndServiceViewModel
2019-11-05 09:41:43 -06:00
1a407a2da3 Add sponsor and new design to the login and registration page 2019-11-05 18:01:43 +09:00
72d7e2dc5b Merge pull request #1116 from bolatovumar/issue-1111
Improve pay button display options section
2019-11-04 12:04:14 +09:00
a126ec1718 Renaming to LndServiceViewModel, used both by grpc and rest 2019-11-03 13:18:09 -06:00
bf59114a00 Run CircleCi external tests only in context of btcpayserver user & repo 2019-11-03 13:16:21 -06:00
6b587bfc31 Improve pay button display options section
address #1111
2019-11-03 09:53:09 -08:00
28a2017b65 Merge pull request #1108 from dennisreimann/paybutton
More pay button improvements
2019-11-03 20:26:55 +09:00
c3a6e9a799 Merge pull request #1112 from Kukks/fix-monero-qr
fix qr code amounts for monero
2019-11-03 20:26:35 +09:00
45c66a167b Merge pull request #1113 from Kukks/payment-request-fixes2
fix Payment request "Cancel invoice" button always showing
2019-11-03 20:25:46 +09:00
380629a961 Update BTCPayServer.Lightning 2019-11-03 16:34:14 +09:00
74fdd1358c Update translations 2019-11-03 16:31:01 +09:00
e9d63650b9 bump 2019-11-03 16:18:33 +09:00
9ecb27a9bd Fix and test /api-tokens (fix https://github.com/btcpayserver/woocommerce-plugin/issues/34) 2019-11-03 16:17:28 +09:00
b0ae878ef6 Merge pull request #1107 from NicolasDorier/ui/new-confirm
Redesign confirm forms
2019-11-03 15:37:33 +09:00
28cc4facd4 Redesign confirm form 2019-11-03 15:20:25 +09:00
749146ad3c fix Payment request "Cancel invoice" button always showing
The Cancel current invoice button always showed even when there was active invoice
2019-11-02 08:49:19 +01:00
a4ba93d249 fix qr code amounts for monero 2019-11-02 08:41:48 +01:00
7eae1f3b3c More pay button improvements 2019-10-31 11:31:30 +01:00
d66e8f2d13 Removing obsolete StatusMessage 2019-10-31 15:19:38 +09:00
058b768b0a update lightning charge in tests 2019-10-31 14:31:44 +09:00
3425f84507 Merge pull request #1106 from NicolasDorier/refactor/statusmessage
[WIP] Refactor StatusMessage and remove ExternalLogin
2019-10-31 14:12:05 +09:00
aad586232c Refactor StatusMessage and remove ExternalLogin 2019-10-31 14:11:33 +09:00
b514533814 Merge pull request #1104 from dennisreimann/paybutton
Improve pay button display
2019-10-31 14:01:38 +09:00
3b1f851f8a Improve pay button display
These changes should provide a more solid CSS approach to display the pay button in different scenarios. Also it fixes minor functionality issues like preselecting the merchant-chosen currency in the custom amount and slider dropdown.
2019-10-29 21:08:08 +01:00
99095f25d9 bump clightning 2019-10-29 16:46:22 +09:00
d79a28ac6f Merge pull request #1103 from rockstardev/lndseedless
Updating reference to new LND docker image and walletunlock.json path
2019-10-29 15:38:44 +09:00
120fe1ba85 Updating reference to new LND docker image and walletunlock.json path 2019-10-28 23:47:55 -05:00
ad8d8c31ab Merge pull request #1099 from Kukks/modal-close-fix
Fix: Close button for modal is broken
2019-10-28 14:26:20 +09:00
3e5ab70583 Fix: Close button for modal is broken
closes #1093
2019-10-27 08:13:30 +01:00
cd5b334a00 Make sure renci ssh does not prevent btcpay from closing 2019-10-26 23:40:35 +09:00
9a99b3fdc2 Fix bug: Could not delete store 2019-10-26 23:35:55 +09:00
267 changed files with 12557 additions and 19459 deletions

View File

@ -31,7 +31,11 @@ jobs:
- 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
@ -100,15 +104,16 @@ workflows:
# ignore any commit on any branch by default
branches:
ignore: /.*/
# only act on version tags
# only act on version tags v1.0.0.88
# OR feature tags like vlndseedbackup
tags:
only: /v[1-9]+(\.[0-9]+)*/
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- arm32v7:
filters:
branches:
ignore: /.*/
tags:
only: /v[1-9]+(\.[0-9]+)*/
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- multiarch:
requires:
- amd64
@ -117,4 +122,4 @@ workflows:
branches:
ignore: /.*/
tags:
only: /v[1-9]+(\.[0-9]+)*/
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/

View File

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

View File

@ -4,7 +4,7 @@
<ItemGroup>
<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" />
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="NBXplorer.Client" Version="2.0.0.26" />
</ItemGroup>
</Project>

View File

@ -8,11 +8,11 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
<FrameworkReference Include="Microsoft.AspNetCore.App" 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" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>
<ItemGroup>

View File

@ -36,27 +36,27 @@ namespace BTCPayServer.Services.Rates
{
await new SynchronizationContextRemover();
var rates = await _ExchangeAPI.GetTickersAsync();
lock (notFoundSymbols)
{
var exchangeRates =
rates
var exchangeRateTasks = rates
.Where(t => t.Value.Ask != 0m && t.Value.Bid != 0m)
.Select(t => CreateExchangeRate(t))
.Where(t => t != null)
.ToArray();
return new ExchangeRates(exchangeRates);
}
.Select(t => CreateExchangeRate(t));
var exchangeRates = await Task.WhenAll(exchangeRateTasks);
return new ExchangeRates(exchangeRates
.Where(t => t != null)
.ToArray());
}
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>();
private ExchangeRate CreateExchangeRate(KeyValuePair<string, ExchangeTicker> ticker)
private async Task<ExchangeRate> CreateExchangeRate(KeyValuePair<string, ExchangeTicker> ticker)
{
if (notFoundSymbols.ContainsKey(ticker.Key))
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
return null;
try
{
var tickerName = _ExchangeAPI.ExchangeSymbolToGlobalSymbol(ticker.Key);
var tickerName = await _ExchangeAPI.ExchangeMarketSymbolToGlobalMarketSymbolAsync(ticker.Key);
if (!CurrencyPair.TryParse(tickerName, out var pair))
{
notFoundSymbols.TryAdd(ticker.Key, ticker.Key);

View File

@ -91,7 +91,7 @@ namespace BTCPayServer.Services.Rates
{
var result = new ExchangeRates();
var symbols = await GetSymbolsAsync(cancellationToken);
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => _Helper.NormalizeSymbol(s)).ToList();
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => _Helper.NormalizeMarketSymbol(s)).ToList();
var csvPairsList = string.Join(",", normalizedPairsList);
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", csvPairsList } }, cancellationToken: cancellationToken);
var tickers = new List<KeyValuePair<string, ExchangeTicker>>();
@ -114,7 +114,7 @@ namespace BTCPayServer.Services.Rates
}
else
{
global = _Helper.ExchangeSymbolToGlobalSymbol(symbol);
global = await _Helper.ExchangeMarketSymbolToGlobalMarketSymbolAsync(symbol);
}
if (CurrencyPair.TryParse(global, out var pair))
result.Add(new ExchangeRate("kraken", pair.Inverse(), new BidAsk(ticker.Bid, ticker.Ask)));
@ -142,10 +142,10 @@ namespace BTCPayServer.Services.Rates
Last = last,
Volume = new ExchangeVolume
{
BaseVolume = ticker["v"][1].ConvertInvariant<decimal>(),
BaseSymbol = symbol,
ConvertedVolume = ticker["v"][1].ConvertInvariant<decimal>() * last,
ConvertedSymbol = symbol,
BaseCurrencyVolume = ticker["v"][1].ConvertInvariant<decimal>(),
BaseCurrency = symbol,
QuoteCurrencyVolume = ticker["v"][1].ConvertInvariant<decimal>() * last,
QuoteCurrency = symbol,
Timestamp = DateTime.UtcNow
}
};

View File

@ -1,36 +0,0 @@
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>();
}
}
}

View File

@ -9,6 +9,7 @@ using ExchangeSharp;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
namespace BTCPayServer.Services.Rates
{
@ -103,7 +104,8 @@ namespace BTCPayServer.Services.Rates
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
Providers.Add("bittrex", new ExchangeSharpRateProvider("bittrex", new ExchangeBittrexAPI(), true));
Providers.Add("poloniex", new ExchangeSharpRateProvider("poloniex", new ExchangePoloniexAPI(), true));
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitbtcAPI(), false));
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitBTCAPI(), true));
Providers.Add("ndax", new ExchangeSharpRateProvider("ndax", new ExchangeNDAXAPI(), true));
// Cryptopia is often not available
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
@ -115,7 +117,6 @@ 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()));
@ -172,7 +173,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("ndax", "NDAX", "https://ndax.io/api/returnTicker"));
exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices"));
return exchanges;

View File

@ -12,11 +12,15 @@
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);NETCOREAPP21</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="76.0.3809.6801" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="78.0.3904.7000" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>

View File

@ -102,7 +102,6 @@ namespace BTCPayServer.Tests
if (!Directory.Exists(chainDirectory))
Directory.CreateDirectory(chainDirectory);
StringBuilder config = new StringBuilder();
config.AppendLine($"{chain.ToLowerInvariant()}=1");
if (InContainer)
@ -118,6 +117,12 @@ namespace BTCPayServer.Tests
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"ltc.explorer.cookiefile=0");
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
config.AppendLine($"debuglog=debug.log");
var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json");
File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true);
config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}");
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
config.AppendLine($"sshpassword={SSHPassword}");
if (!string.IsNullOrEmpty(SSHKeyFile))
@ -228,9 +233,10 @@ namespace BTCPayServer.Tests
rateProvider.Providers.Add("bittrex", bittrex);
}
Logs.Tester.LogInformation("Waiting site is operational...");
await WaitSiteIsOperational();
Logs.Tester.LogInformation("Site is now operational");
}
private async Task WaitSiteIsOperational()

View File

@ -18,7 +18,7 @@ namespace BTCPayServer.Tests
[Trait("Selenium", "Selenium")]
public class CheckoutUITests
{
public const int TestTimeout = 60_000;
public const int TestTimeout = TestUtils.TestTimeout;
public CheckoutUITests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
@ -32,6 +32,7 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
@ -80,6 +81,7 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
@ -109,6 +111,7 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
@ -152,6 +155,7 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddInternalLightningNode("BTC");

View File

@ -35,7 +35,7 @@ namespace BTCPayServer.Tests
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateAndDeleteCrowdfundApp()
{
@ -75,7 +75,7 @@ namespace BTCPayServer.Tests
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanContributeOnlyWhenAllowed()
{
@ -100,7 +100,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
var publicApps = user.GetController<AppsPublicController>();
@ -126,7 +126,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.StartDate= DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
@ -138,7 +138,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.EndDate= DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
@ -152,7 +152,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(1.01)
@ -167,7 +167,7 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanComputeCrowdfundModel()
{
@ -195,7 +195,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
var publicApps = user.GetController<AppsPublicController>();
@ -253,7 +253,7 @@ namespace BTCPayServer.Tests
Assert.Contains(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Logs.Tester.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = user.BitPay.CreateInvoice(new Invoice()
@ -272,7 +272,7 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
invoice = user.BitPay.CreateInvoice(new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },

View File

@ -25,6 +25,6 @@ ENV SCREEN_HEIGHT 600 \
SCREEN_WIDTH 1200
COPY . .
RUN cd BTCPayServer.Tests && dotnet build
RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true
WORKDIR /source/BTCPayServer.Tests
ENTRYPOINT ["./docker-entrypoint.sh"]

View File

@ -28,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
{

View File

@ -11,6 +11,7 @@ using NBitcoin;
using NBitpayClient;
using Xunit;
using Xunit.Abstractions;
using BTCPayServer.Models;
namespace BTCPayServer.Tests
{
@ -68,10 +69,9 @@ namespace BTCPayServer.Tests
var vmLedger = await walletController.WalletSend(walletId, sendModel, command: "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
PSBT.Parse(vmLedger.PSBT, user.SupportedNetwork.NBitcoinNetwork);
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmLedger.SuccessPath);
Assert.NotNull(vmLedger.WebsocketPath);
var redirectedPSBT = (string)Assert.IsType<RedirectToActionResult>(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt")).RouteValues["psbt"];
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"));
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmPSBT.Decoded);
@ -98,9 +98,9 @@ namespace BTCPayServer.Tests
var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
combineVM.PSBT = signedPSBT.ToBase64();
vmPSBT = await walletController.WalletPSBTCombine(walletId, combineVM).AssertViewModelAsync<WalletPSBTViewModel>();
var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
var signedPSBT2 = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2);
@ -108,19 +108,27 @@ namespace BTCPayServer.Tests
// Can use uploaded file?
combineVM.PSBT = null;
combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes());
vmPSBT = await walletController.WalletPSBTCombine(walletId, combineVM).AssertViewModelAsync<WalletPSBTViewModel>();
signedPSBT2 = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2);
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT);
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"));
Assert.Equal(signedPSBT.ToBase64(), (string)redirect.RouteValues["psbt"]);
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"));
Assert.Equal(signedPSBT.ToBase64(), psbt);
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
}
}
private static string AssertRedirectedPSBT(IActionResult view)
{
var postRedirectView = Assert.IsType<ViewResult>(view);
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
return redirectedPSBT;
}
}
}

View File

@ -66,20 +66,28 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri);
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
GoToRegister();
Driver.AssertNoError();
}
internal void AssertHappyMessage()
{
Assert.Single(Driver.FindElements(By.ClassName("alert-success")).Where(el => el.Displayed));
}
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
public string Link(string relativeLink)
{
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
}
public void GoToRegister()
{
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
}
public string RegisterNewUser(bool isAdmin = false)
{
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
Driver.FindElement(By.Id("Register")).Click();
Driver.FindElement(By.Id("Email")).SendKeys(usr);
Driver.FindElement(By.Id("Password")).SendKeys("123456");
Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
@ -107,8 +115,7 @@ namespace BTCPayServer.Tests
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;
AssertHappyMessage();
}
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
@ -142,6 +149,7 @@ namespace BTCPayServer.Tests
Assert.NotEmpty(links);
foreach (var l in links)
{
Logs.Tester.LogInformation($"Checking no error on {l}");
Driver.Navigate().GoToUrl(l);
Driver.AssertNoError();
}

View File

@ -32,6 +32,28 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("ServerSettings")).Click();
s.Driver.AssertNoError();
s.ClickOnAllSideMenus();
s.Driver.FindElement(By.LinkText("Services")).Click();
Logs.Tester.LogInformation("Let's if we can access LND's seed");
Assert.Contains("server/services/lndseedbackup/BTC", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/server/services/lndseedbackup/BTC"));
s.Driver.FindElement(By.Id("details")).Click();
var seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
Assert.True(seedEl.Displayed);
Assert.Contains("about over million", seedEl.Text, StringComparison.OrdinalIgnoreCase);
var passEl = s.Driver.FindElement(By.Id("PasswordInput"));
Assert.True(passEl.Displayed);
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
s.Driver.FindElement(By.Id("delete")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.AssertHappyMessage();
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
Logs.Tester.LogInformation("Let's check if we can access the logs");
s.Driver.FindElement(By.LinkText("Logs")).Click();
s.Driver.FindElement(By.PartialLinkText(".log")).Click();
Assert.Contains("Starting listening NBXplorer", s.Driver.PageSource);
s.Driver.Quit();
}
}
@ -46,8 +68,9 @@ namespace BTCPayServer.Tests
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);
// Should show the Tor address
Assert.Contains("wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
@ -76,7 +99,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();
@ -91,7 +113,6 @@ 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();
@ -199,6 +220,7 @@ namespace BTCPayServer.Tests
s.ClickOnAllSideMenus();
s.GoToInvoices();
s.CreateInvoice(store);
s.AssertHappyMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
@ -208,7 +230,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);
@ -231,6 +253,16 @@ 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);
}
}
@ -242,7 +274,7 @@ namespace BTCPayServer.Tests
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();
@ -250,12 +282,10 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Tokens")).Click();
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
s.Driver.FindElement(By.Id("RequestPairing")).Click();
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;
string pairingCode = AssertUrlHasPairingCode(s);
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
s.AssertHappyMessage();
Assert.Contains(pairingCode, s.Driver.PageSource);
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
@ -279,9 +309,21 @@ namespace BTCPayServer.Tests
Currency = "USD",
FullNotifications = true
}, NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
s.Driver.FindElement(By.Id("RequestPairing")).Click();
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
AssertUrlHasPairingCode(s);
}
}
private static string AssertUrlHasPairingCode(SeleniumTester s)
{
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
var pairingCode = regex.Groups[1].Value;
return pairingCode;
}
[Fact(Timeout = TestTimeout)]

View File

@ -28,7 +28,7 @@ namespace BTCPayServer.Tests
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanConfigureStorage()
{
@ -139,7 +139,7 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("ExternalIntegration", "ExternalIntegration")]
public async Task CanUseAzureBlobStorage()
{
@ -210,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>()
@ -221,12 +221,14 @@ namespace BTCPayServer.Tests
//verify tmpfile is available and the same
data = await net.DownloadStringTaskAsync(new Uri(url));
Assert.Equal(fileContent, data);
//delete file
Assert.Equal(StatusMessageModel.StatusSeverity.Success, new StatusMessageModel(Assert
.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId))
.RouteValues["statusMessage"].ToString()).Severity);
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
controller.TempData.GetStatusMessageModel();
Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
//attempt to fetch deleted file
viewFilesViewModel =

View File

@ -0,0 +1 @@
{"wallet_password":"hellorockstar", "cipher_seed_mnemonic":["about","over","million","coast","nature","gold","fabric","aware","adult","salute","sock","envelope","oblige","shed","basic","need","analyst","spot","worth","garbage","planet","genre","leave","cereal"]}

View File

@ -0,0 +1 @@
rhrk7ncuqfcbkt7deaysn7st32hrf2tzztoumkj2vp3cqzy2d2arvdad.onion

View File

@ -0,0 +1 @@
r32tqfpchpiptrfltyd4ku7q3ir4yidyfe4em63iz2g5wjcauyvyj4id.onion

View File

@ -0,0 +1 @@
wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion

View File

@ -0,0 +1 @@
wzaq2ek6a5g6jyhr7giqsz2gbr6qdtcuwywcyvfplvrk7ugup3virvad.onion

View File

@ -0,0 +1 @@
jzebv74oaugje6aeykzcypzxs6pvt27wdmrcpe4pkjfprxgem2vyi7qd.onion

View File

@ -0,0 +1 @@
aeyn6mou5himgmog4tlnbjlgx5bwtwsqttd7sn7hay6lksvlfwmtkqyd.onion

View File

@ -0,0 +1 @@
uewlcjmuuwcgs43lpcc7d64k2pf4xs7yyer7ztsq6cblyjxue3bc35qd.onion

View File

@ -0,0 +1 @@
4iz3q7dp5xn5isqxsslrvbjrltqoqqocq25zmjgzvvxm5yp57jj4buid.onion

View File

@ -0,0 +1 @@
ii3kqeayarsxr2jqkmr2l5np2f4vn6lznk55upd3udfbfskbjdzoq4yd.onion

View File

@ -0,0 +1,24 @@
# For the hidden service BTC-P2P
HiddenServiceDir hidden_services/BTC-P2P
# Redirecting to btcpayserver_bitcoind
HiddenServicePort 8333 172.19.0.2:39388
# For the hidden service BTC-RPC
HiddenServiceDir hidden_services/BTC-RPC
# Redirecting to btcpayserver_bitcoind
HiddenServicePort 8332 172.19.0.2:43782
# For the hidden service BTCPayServer
HiddenServiceDir hidden_services/BTCPayServer
# Redirecting to nginx
HiddenServicePort 80 172.19.0.13:80
# For the hidden service BTCTransmuter
HiddenServiceDir hidden_services/BTCTransmuter
# Redirecting to nginx
HiddenServicePort 80 172.19.0.13:80
# For the hidden service WooCommerce
HiddenServiceDir hidden_services/WooCommerce
# Redirecting to generated_woocommerce_1
HiddenServicePort 80 172.19.0.15:80
# For the hidden service c-lightning
HiddenServiceDir hidden_services/c-lightning
# Redirecting to btcpayserver_clightning_bitcoin
HiddenServicePort 9735 172.19.0.10:9735

View File

@ -15,7 +15,7 @@ namespace BTCPayServer.Tests
{
public static class TestUtils
{
#if DEBUG
#if DEBUG && !SHORT_TIMEOUT
public const int TestTimeout = 600_000;
#else
public const int TestTimeout = 60_000;
@ -30,6 +30,18 @@ namespace BTCPayServer.Tests
}
return directory;
}
public static string GetTestDataFullPath(string relativeFilePath)
{
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
while (directory != null && !directory.GetFiles("*.csproj").Any())
{
directory = directory.Parent;
}
return Path.Combine(directory.FullName, "TestData", relativeFilePath);
}
public static FormFile GetFormFile(string filename, string content)
{
File.WriteAllText(filename, content);

View File

@ -60,6 +60,7 @@ using Microsoft.Extensions.DependencyInjection;
using NBXplorer.DerivationStrategy;
using BTCPayServer.U2F.Models;
using BTCPayServer.Security.Bitpay;
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
namespace BTCPayServer.Tests
{
@ -532,6 +533,22 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Fast", "Fast")]
public async Task CanEnumerateTorServices()
{
var tor = new TorServices(new BTCPayNetworkProvider(NetworkType.Regtest), new BTCPayServerOptions()
{
TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc")
});
await tor.Refresh();
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.BTCPayServer));
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.P2P));
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
Assert.True(tor.Services.Where(t => t.ServiceType == TorServiceType.Other).Count() > 1);
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
public async Task CanSetLightningServer()
@ -551,7 +568,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()
@ -647,11 +665,12 @@ namespace BTCPayServer.Tests
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"];
@ -684,7 +703,7 @@ namespace BTCPayServer.Tests
FullNotifications = true,
ExtendedNotifications = true
});
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21, tester.NetworkProvider.BTC.NBitcoinNetwork);
bool receivedPayment = false;
bool paid = false;
bool confirmed = false;
@ -746,7 +765,7 @@ namespace BTCPayServer.Tests
acc.CreateStore();
var store2 = acc.GetController<StoresController>();
await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id);
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage, StringComparison.CurrentCultureIgnoreCase);
Assert.Contains(nameof(PairingResult.ReusedKey), (string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase);
}
}
@ -1140,7 +1159,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
@ -2002,7 +2021,7 @@ noninventoryitem:
//verify invoices where created
invoices = user.BitPay.GetInvoices();
Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem")));
var inventoryItemInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("inventoryitem"));
var inventoryItemInvoice = Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem")));
Assert.NotNull(inventoryItemInvoice);
//let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock
@ -2880,42 +2899,6 @@ 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(Timeout = TestTimeout)]
[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(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
@ -2978,13 +2961,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>();

View File

@ -76,7 +76,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.0.0.62
image: nicolasdorier/nbxplorer:2.0.0.66
restart: unless-stopped
ports:
- "32838:32838"
@ -127,7 +127,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.7.2-1-dev
image: btcpayserver/lightning:v0.7.3-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -140,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
@ -154,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
@ -174,7 +174,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.7.2-1-dev
image: btcpayserver/lightning:v0.7.3-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@ -186,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:
@ -223,7 +224,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.7.0-beta
image: btcpayserver/lnd:v0.7.1-beta-withseed
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -241,7 +242,6 @@ services:
bitcoin.defaultchanconfs=1
no-macaroons=1
debuglevel=debug
noseedbackup=1
trickledelay=1000
ports:
- "53280:8080"
@ -254,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"
@ -272,7 +272,6 @@ services:
bitcoin.defaultchanconfs=1
no-macaroons=1
debuglevel=debug
noseedbackup=1
trickledelay=1000
ports:
- "53281:8080"

View File

@ -8,19 +8,15 @@
<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\**" />
<Content Remove="wwwroot\bundles\jqueryvalidate\**" />
<Content Remove="wwwroot\css\**" />
<Content Remove="wwwroot\vendor\jquery-nice-select\**" />
<EmbeddedResource Remove="Build\**" />
<EmbeddedResource Remove="wwwroot\bundles\jqueryvalidate\**" />
<EmbeddedResource Remove="wwwroot\css\**" />
<EmbeddedResource Remove="wwwroot\vendor\jquery-nice-select\**" />
<None Remove="Build\**" />
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
<None Remove="wwwroot\css\**" />
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
</ItemGroup>
<ItemGroup>
@ -31,7 +27,7 @@
<EmbeddedResource Include="Currencies.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.1" />
<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" />
@ -80,11 +76,7 @@
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<None Include="wwwroot\vendor\bootstrap4-creativestart\creative.js" />
<None Include="wwwroot\main\bootstrap4-creativestart\creative.js" />
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.svg" />
<None Include="wwwroot\vendor\font-awesome\fonts\fontawesome-webfont.woff2" />
<None Include="wwwroot\vendor\font-awesome\less\animated.less" />
@ -158,6 +150,12 @@
<Content Update="Views\Server\LightningWalletServices.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Server\LndSeedBackup.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>
@ -221,4 +219,4 @@
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
</ItemGroup>
</Project>
</Project>

View File

@ -58,7 +58,16 @@ namespace BTCPayServer.Configuration
public static string GetDebugLog(IConfiguration configuration)
{
return configuration.GetValue<string>("debuglog", null);
var logfile = configuration.GetValue<string>("debuglog", null);
if (!string.IsNullOrEmpty(logfile))
{
if (!Path.IsPathRooted(logfile))
{
var networkType = DefaultConfiguration.GetNetworkType(configuration);
logfile = Path.Combine(configuration.GetDataDir(networkType), logfile);
}
}
return logfile;
}
public static LogEventLevel GetDebugLogLevel(IConfiguration configuration)
{

View File

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

View File

@ -24,6 +24,10 @@ namespace BTCPayServer.Configuration
"lnd server: 'server=https://lnd.example.com;macaroondirectorypath=/root/.lnd;certthumbprint=2abdf302...'" + Environment.NewLine +
"Error: {1}",
"LND (REST server)");
Load(configuration, cryptoCode, "lndseedbackup", ExternalServiceTypes.LNDSeedBackup, "Invalid setting {0}, " + Environment.NewLine +
"lnd seed backup: /etc/merchant_lnd/data/chain/bitcoin/regtest/walletunlock.json'" + Environment.NewLine +
"Error: {1}",
"LND Seed Backup");
Load(configuration, cryptoCode, "spark", ExternalServiceTypes.Spark, "Invalid setting {0}, " + Environment.NewLine +
$"Valid example: 'server=https://btcpay.example.com/spark/btc/;cookiefile=/etc/clightning_bitcoin_spark/.cookie'" + Environment.NewLine +
"Error: {1}",
@ -45,11 +49,17 @@ namespace BTCPayServer.Configuration
var connStr = configuration.GetOrDefault<string>(setting, string.Empty);
if (connStr.Length != 0)
{
if (!ExternalConnectionString.TryParse(connStr, out var connectionString, out var error))
ExternalConnectionString serviceConnection;
if (type == ExternalServiceTypes.LNDSeedBackup)
{
// just using CookieFilePath to hold variable instead of refactoring whole holder class to better conform
serviceConnection = new ExternalConnectionString { CookieFilePath = connStr };
}
else if (!ExternalConnectionString.TryParse(connStr, out serviceConnection, out var error))
{
throw new ConfigException(string.Format(CultureInfo.InvariantCulture, errorMessage, setting, error));
}
this.Add(new ExternalService() { Type = type, ConnectionString = connectionString, CryptoCode = cryptoCode, DisplayName = displayName, ServiceName = serviceName });
this.Add(new ExternalService() { Type = type, ConnectionString = serviceConnection, CryptoCode = cryptoCode, DisplayName = displayName, ServiceName = serviceName });
}
}
@ -73,9 +83,11 @@ namespace BTCPayServer.Configuration
{
LNDRest,
LNDGRPC,
LNDSeedBackup,
Spark,
RTL,
Charge,
P2P
P2P,
RPC
}
}

View File

@ -74,21 +74,32 @@ namespace BTCPayServer.Controllers
[AllowAnonymous]
public async Task<IActionResult> Login(string returnUrl = null)
{
if (User.Identity.IsAuthenticated && string.IsNullOrEmpty(returnUrl))
return RedirectToLocal();
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
if (!CanLoginOrRegister())
{
SetInsecureFlags();
}
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Login");
}
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
@ -199,6 +210,11 @@ namespace BTCPayServer.Controllers
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWithU2F(LoginWithU2FViewModel viewModel, string returnUrl = null)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Login");
}
ViewData["ReturnUrl"] = returnUrl;
var user = await _userManager.FindByIdAsync(viewModel.UserId);
@ -242,6 +258,11 @@ namespace BTCPayServer.Controllers
[AllowAnonymous]
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Login");
}
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
@ -264,6 +285,11 @@ namespace BTCPayServer.Controllers
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Login");
}
if (!ModelState.IsValid)
{
return View(model);
@ -305,6 +331,11 @@ namespace BTCPayServer.Controllers
[AllowAnonymous]
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Login");
}
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
@ -322,6 +353,11 @@ namespace BTCPayServer.Controllers
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Login");
}
if (!ModelState.IsValid)
{
return View(model);
@ -364,14 +400,19 @@ namespace BTCPayServer.Controllers
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true)
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true, bool useBasicLayout = false)
{
if (!CanLoginOrRegister())
{
SetInsecureFlags();
}
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
return RedirectToAction(nameof(HomeController.Index), "Home");
ViewData["ReturnUrl"] = returnUrl;
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
ViewData["AllowIsAdmin"] = _Options.AllowAdminRegistration;
ViewData["UseBasicLayout"] = useBasicLayout;
return View();
}
@ -380,6 +421,11 @@ namespace BTCPayServer.Controllers
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null, bool logon = true)
{
if (!CanLoginOrRegister())
{
return RedirectToAction("Register");
}
ViewData["ReturnUrl"] = returnUrl;
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
ViewData["AllowIsAdmin"] = _Options.AllowAdminRegistration;
@ -398,7 +444,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 +468,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 +495,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)
@ -659,6 +625,23 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(HomeController.Index), "Home");
}
}
private bool CanLoginOrRegister()
{
return _btcPayServerEnvironment.IsDevelopping || _btcPayServerEnvironment.IsSecure;
}
private void SetInsecureFlags()
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "You cannot login over an insecure connection. Please use HTTPS or Tor."
});
ViewData["disabled"] = true;
}
#endregion
}

View File

@ -68,7 +68,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost]
[Route("{appId}/settings/crowdfund")]
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm)
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command)
{
if (!string.IsNullOrEmpty( vm.TargetCurrency) && _currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
@ -156,16 +156,25 @@ namespace BTCPayServer.Controllers
app.TagAllInvoices = vm.UseAllStoreInvoices;
app.SetSettings(newSettings);
await _AppService.UpdateOrCreateApp(app);
_EventAggregator.Publish(new AppUpdated()
if (command == "save")
{
AppId = appId,
StoreId = app.StoreDataId,
Settings = newSettings
});
StatusMessage = "App updated";
return RedirectToAction(nameof(UpdateCrowdfund), new {appId});
await _AppService.UpdateOrCreateApp(app);
_EventAggregator.Publish(new AppUpdated()
{
AppId = appId,
StoreId = app.StoreDataId,
Settings = newSettings
});
TempData[WellKnownTempData.SuccessMessage] = "App updated";
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
}
else if (command == "viewapp")
{
return RedirectToAction(nameof(AppsPublicController.ViewCrowdfund), "AppsPublic", new { appId });
}
return NotFound();
}
}
}

View File

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

View File

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

View File

@ -165,26 +165,39 @@ namespace BTCPayServer.Controllers
}
}
var store = await _AppService.GetStore(app);
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
try
{
ItemCode = choice?.Id,
ItemDesc = title,
Currency = settings.Currency,
Price = price,
BuyerEmail = email,
OrderId = orderId,
NotificationURL =
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
NotificationEmail = settings.NotificationEmail,
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
FullNotifications = true,
ExtendedNotifications = true,
PosData = string.IsNullOrEmpty(posData) ? null : posData,
RedirectAutomatically = settings.RedirectAutomatically,
}, store, HttpContext.Request.GetAbsoluteRoot(),
new List<string>() { AppService.GetAppInternalTag(appId) },
cancellationToken);
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
{
ItemCode = choice?.Id,
ItemDesc = title,
Currency = settings.Currency,
Price = price,
BuyerEmail = email,
OrderId = orderId,
NotificationURL =
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
NotificationEmail = settings.NotificationEmail,
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
FullNotifications = true,
ExtendedNotifications = true,
PosData = string.IsNullOrEmpty(posData) ? null : posData,
RedirectAutomatically = settings.RedirectAutomatically,
}, store, HttpContext.Request.GetAbsoluteRoot(),
new List<string>() { AppService.GetAppInternalTag(appId) },
cancellationToken);
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
}
catch (BitpayHttpException e)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Html = e.Message.Replace("\n", "<br />", StringComparison.OrdinalIgnoreCase),
Severity = StatusMessageModel.StatusSeverity.Error,
AllowDismiss = true
});
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
}
}
[HttpGet]

View File

@ -12,6 +12,8 @@ using Newtonsoft.Json;
using BTCPayServer.Services;
using BTCPayServer.HostedServices;
using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Identity;
using BTCPayServer.Data;
namespace BTCPayServer.Controllers
{
@ -20,11 +22,15 @@ namespace BTCPayServer.Controllers
private readonly CssThemeManager _cachedServerSettings;
public IHttpClientFactory HttpClientFactory { get; }
SignInManager<ApplicationUser> SignInManager { get; }
public HomeController(IHttpClientFactory httpClientFactory, CssThemeManager cachedServerSettings)
public HomeController(IHttpClientFactory httpClientFactory,
CssThemeManager cachedServerSettings,
SignInManager<ApplicationUser> signInManager)
{
HttpClientFactory = httpClientFactory;
_cachedServerSettings = cachedServerSettings;
SignInManager = signInManager;
}
private async Task<ViewResult> GoToApp(string appId, AppType? appType)
@ -71,14 +77,26 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Index()
{
if (_cachedServerSettings.FirstRun)
{
return RedirectToAction(nameof(AccountController.Register), "Account");
}
var matchedDomainMapping = _cachedServerSettings.DomainToAppMapping.FirstOrDefault(item =>
item.Domain.Equals(Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));
if (matchedDomainMapping != null)
{
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? View("Home");
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? GoToHome();
}
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? View("Home");
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? GoToHome();
}
private IActionResult GoToHome()
{
if (SignInManager.IsSignedIn(User))
return View("Home");
else
return RedirectToAction(nameof(AccountController.Login), "Account");
}
[Route("translate")]
@ -103,7 +121,7 @@ namespace BTCPayServer.Controllers
try
{
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink);
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink, Network.Main);
#pragma warning disable CS0618 // Type or member is obsolete
if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
{

View File

@ -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 =>
@ -234,8 +232,8 @@ namespace BTCPayServer.Controllers
InvoiceId = invoice.Id,
DefaultLang = storeBlob.DefaultLang ?? "en",
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
CustomCSSLink = storeBlob.CustomCSS?.AbsoluteUri,
CustomLogoLink = storeBlob.CustomLogo?.AbsoluteUri,
CustomCSSLink = storeBlob.CustomCSS,
CustomLogoLink = storeBlob.CustomLogo,
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
BtcDue = accounting.Due.ToString(),
@ -400,12 +398,15 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int timezoneOffset = 0)
{
var fs = new SearchString(searchTerm);
var storeIds = fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List<string>().ToArray();
var model = new InvoicesModel
{
SearchTerm = searchTerm,
Skip = skip,
Count = count,
StatusMessage = StatusMessage,
StoreIds = storeIds,
TimezoneOffset = timezoneOffset
};
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
@ -497,7 +498,7 @@ namespace BTCPayServer.Controllers
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");
}
@ -518,22 +519,13 @@ namespace BTCPayServer.Controllers
{
return View(model);
}
StatusMessage = null;
if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0)
{
ModelState.AddModelError(nameof(model.StoreId), "You need to configure the derivation scheme in order to create an invoice");
return View(model);
}
if (StatusMessage != null)
{
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id
});
}
try
{
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
@ -554,7 +546,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)
@ -603,13 +595,6 @@ namespace BTCPayServer.Controllers
public string StatusString { get; set; }
}
[TempData]
public string StatusMessage
{
get;
set;
}
private string GetUserId()
{
return _UserManager.GetUserId(User);

View File

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

View File

@ -64,12 +64,6 @@ namespace BTCPayServer.Controllers
_StoreRepository = storeRepository;
}
[TempData]
public string StatusMessage
{
get; set;
}
[HttpGet]
public async Task<IActionResult> Index()
{
@ -84,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);
}
@ -137,7 +130,7 @@ namespace BTCPayServer.Controllers
}
}
StatusMessage = "Your profile has been updated";
TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated";
return RedirectToAction(nameof(Index));
}
@ -160,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));
}
@ -179,7 +172,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(SetPassword));
}
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
var model = new ChangePasswordViewModel();
return View(model);
}
@ -207,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));
}
@ -228,7 +221,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(ChangePassword));
}
var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
var model = new SetPasswordViewModel();
return View(model);
}
@ -255,92 +248,11 @@ namespace BTCPayServer.Controllers
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "Your password has been set.";
TempData[WellKnownTempData.SuccessMessage] = "Your password has been set.";
return RedirectToAction(nameof(SetPassword));
}
[HttpGet]
public async Task<IActionResult> ExternalLogins()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.ToList();
model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
model.StatusMessage = StatusMessage;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LinkLogin(string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action(nameof(LinkLoginCallback));
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return new ChallengeResult(provider, properties);
}
[HttpGet]
public async Task<IActionResult> LinkLoginCallback()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
if (info == null)
{
throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
}
var result = await _userManager.AddLoginAsync(user, info);
if (!result.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
}
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
StatusMessage = "The external login was added.";
return RedirectToAction(nameof(ExternalLogins));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
if (!result.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "The external login was removed.";
return RedirectToAction(nameof(ExternalLogins));
}
#region Helpers

View File

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

View File

@ -85,9 +85,9 @@ namespace BTCPayServer.Controllers
[Route("rates")]
[HttpGet]
[BitpayAPIConstraint]
public async Task<IActionResult> GetRates(string currencyPairs, CancellationToken cancellationToken)
public async Task<IActionResult> GetRates(string currencyPairs, string storeId = null, CancellationToken cancellationToken = default)
{
var result = await GetRates2(currencyPairs, null, cancellationToken);
var result = await GetRates2(currencyPairs, storeId, cancellationToken);
var rates = (result as JsonResult)?.Value as Rate[];
if (rates == null)
return result;

View File

@ -26,9 +26,8 @@ namespace BTCPayServer.Controllers
public partial class ServerController
{
[HttpGet("server/files/{fileId?}")]
public async Task<IActionResult> Files(string fileId = null, string statusMessage = null)
public async Task<IActionResult> Files(string fileId = null)
{
TempData["StatusMessage"] = statusMessage;
var fileUrl = string.IsNullOrEmpty(fileId) ? null : await _FileService.GetFileUrl(Request.GetAbsoluteRootUri(), fileId);
return View(new ViewFilesViewModel()
@ -54,14 +53,12 @@ namespace BTCPayServer.Controllers
}
catch (Exception e)
{
return RedirectToAction(nameof(Files), new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
statusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = e.Message
}
Severity = StatusMessageModel.StatusSeverity.Error,
Message = e.Message
});
return RedirectToAction(nameof(Files));
}
}
@ -119,16 +116,14 @@ namespace BTCPayServer.Controllers
}
var url = await _FileService.GetTemporaryFileUrl(Request.GetAbsoluteRootUri(), fileId, expiry, viewModel.IsDownload);
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html = $"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
});
return RedirectToAction(nameof(Files), new
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html =
$"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
}.ToString(),
fileId,
fileId
});
}
@ -165,9 +160,8 @@ namespace BTCPayServer.Controllers
}
[HttpGet("server/storage")]
public async Task<IActionResult> Storage(bool forceChoice = false, string statusMessage = null)
public async Task<IActionResult> Storage(bool forceChoice = false)
{
TempData["StatusMessage"] = statusMessage;
var savedSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
if (forceChoice || savedSettings == null)
{
@ -198,14 +192,12 @@ namespace BTCPayServer.Controllers
{
if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider))
{
return RedirectToAction(nameof(Storage), new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{provider} provider is not supported"
}.ToString()
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{provider} provider is not supported"
});
return RedirectToAction(nameof(Storage));
}
var data = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) ?? new StorageSettings();
@ -216,14 +208,12 @@ namespace BTCPayServer.Controllers
switch (storageProviderService)
{
case null:
return RedirectToAction(nameof(Storage), new
TempData.SetStatusMessageModel(new StatusMessageModel()
{
StatusMessage = new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{storageProvider} is not supported"
}.ToString()
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{storageProvider} is not supported"
});
return RedirectToAction(nameof(Storage));
case AzureBlobStorageFileProviderService fileProviderService:
return View(nameof(EditAzureBlobStorageStorageProvider),
fileProviderService.GetProviderConfiguration(data));
@ -288,11 +278,11 @@ namespace BTCPayServer.Controllers
data.Provider = storageProvider;
data.Configuration = JObject.FromObject(viewModel);
await _SettingsRepository.UpdateSetting(data);
TempData["StatusMessage"] = new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Storage settings updated successfully"
}.ToString();
});
return View(viewModel);
}
}

View File

@ -136,7 +136,7 @@ namespace BTCPayServer.Controllers
return View(vm);
}
await _SettingsRepository.UpdateSetting(rates);
StatusMessage = "Rate settings successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "Rate settings successfully updated";
return RedirectToAction(nameof(Rates));
}
@ -158,7 +158,6 @@ namespace BTCPayServer.Controllers
public IActionResult ListUsers(int skip = 0, int count = 50)
{
var users = new UsersViewModel();
users.StatusMessage = StatusMessage;
users.Users = _UserManager.Users.Skip(skip).Take(count)
.Select(u => new UsersViewModel.UserViewModel
{
@ -191,6 +190,8 @@ namespace BTCPayServer.Controllers
{
MaintenanceViewModel vm = new MaintenanceViewModel();
vm.CanUseSSH = _sshState.CanUseSSH;
if (!vm.CanUseSSH)
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPayServer configuration";
vm.DNSDomain = this.Request.Host.Host;
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
vm.DNSDomain = null;
@ -263,21 +264,21 @@ namespace BTCPayServer.Controllers
builder.Path = null;
builder.Query = null;
StatusMessage = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\"";
TempData[WellKnownTempData.SuccessMessage] = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\" (this page won't reload automatically)";
}
else if (command == "update")
{
var error = await RunSSH(vm, $"btcpay-update.sh");
if (error != null)
return error;
StatusMessage = $"The server might restart soon if an update is available...";
TempData[WellKnownTempData.SuccessMessage] = $"The server might restart soon if an update is available... (this page won't reload automatically)";
}
else if (command == "clean")
{
var error = await RunSSH(vm, $"btcpay-clean.sh");
if (error != null)
return error;
StatusMessage = $"The old docker images will be cleaned soon...";
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
}
else
{
@ -357,12 +358,10 @@ namespace BTCPayServer.Controllers
if (user == null)
return NotFound();
viewModel.StatusMessage = "";
var admins = await _UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
if (!viewModel.IsAdmin && admins.Count == 1)
{
viewModel.StatusMessage = "This is the only Admin, so their role can't be removed until another Admin is added.";
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added.";
return View(viewModel); // return
}
@ -374,10 +373,10 @@ namespace BTCPayServer.Controllers
else
await _UserManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
viewModel.StatusMessage = "User successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "User successfully updated";
}
return View(viewModel);
return RedirectToAction(nameof(User), new { userId = userId });
}
@ -420,15 +419,9 @@ namespace BTCPayServer.Controllers
return NotFound();
await _UserManager.DeleteAsync(user);
await _StoreRepository.CleanUnreachableStores();
StatusMessage = "User deleted";
TempData[WellKnownTempData.SuccessMessage] = "User deleted";
return RedirectToAction(nameof(ListUsers));
}
[TempData]
public string StatusMessage
{
get; set;
}
public IHttpClientFactory HttpClientFactory { get; }
[Route("server/policies")]
@ -489,7 +482,7 @@ namespace BTCPayServer.Controllers
}
await _SettingsRepository.UpdateSetting(settings);
TempData["StatusMessage"] = "Policies updated successfully";
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully";
return RedirectToAction(nameof(Policies));
}
@ -498,6 +491,8 @@ namespace BTCPayServer.Controllers
{
var result = new ServicesViewModel();
result.ExternalServices = _Options.ExternalServices.ToList();
// other services
foreach (var externalService in _Options.OtherExternalServices)
{
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
@ -543,6 +538,7 @@ namespace BTCPayServer.Controllers
}
}
// external storage services
var storageSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
result.ExternalStorageServices.Add(new ServicesViewModel.OtherExternalService()
{
@ -574,6 +570,17 @@ namespace BTCPayServer.Controllers
ServiceName = torService.Name,
};
}
if (torService.ServiceType == TorServiceType.RPC)
{
externalService = new ExternalService()
{
CryptoCode = torService.Network.CryptoCode,
DisplayName = "Full node RPC",
Type = ExternalServiceTypes.RPC,
ConnectionString = new ExternalConnectionString(new Uri($"btcrpc://btcrpc:btcpayserver4ever@{torService.OnionHost}:{torService.VirtualPort}?label=BTCPayNode", UriKind.Absolute)),
ServiceName = torService.Name
};
}
return externalService != null;
}
@ -582,16 +589,21 @@ namespace BTCPayServer.Controllers
var result = _Options.ExternalServices.GetService(serviceName, cryptoCode);
if (result != null)
return result;
_torServices.Services.FirstOrDefault(s => TryParseAsExternalService(s, out result));
return result;
foreach (var torService in _torServices.Services)
{
if (TryParseAsExternalService(torService, out var torExternalService) &&
torExternalService.ServiceName == serviceName)
return torExternalService;
}
return null;
}
[Route("server/services/{serviceName}/{cryptoCode}")]
public async Task<IActionResult> Service(string serviceName, string cryptoCode, bool showQR = false, uint? nonce = null)
{
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
if (!_dashBoard.IsFullySynched(cryptoCode, out _))
{
StatusMessage = $"Error: {cryptoCode} is not fully synched";
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
return RedirectToAction(nameof(Services));
}
var service = GetService(serviceName, cryptoCode);
@ -609,6 +621,30 @@ namespace BTCPayServer.Controllers
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
});
}
if (service.Type == ExternalServiceTypes.LNDSeedBackup)
{
var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath);
if (!model.IsWalletUnlockPresent)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Html = "Your LND does not seem to allow seed backup.<br />" +
"It's recommended, but not required, that you migrate as instructed by <a href=\"https://blog.btcpayserver.org/btcpay-lnd-migration\">our migration blog post</a>.<br />" +
"You will need to close all of your channels, and migrate your funds as <a href=\"https://blog.btcpayserver.org/btcpay-lnd-migration\">we documented</a>."
});
}
return View("LndSeedBackup", model);
}
if (service.Type == ExternalServiceTypes.RPC)
{
return View("RPCService", new LightningWalletServices()
{
ShowQR = showQR,
WalletName = service.ServiceName,
ServiceLink = service.ConnectionString.Server.AbsoluteUri.WithoutEndingSlash()
});
}
var connectionString = await service.ConnectionString.Expand(this.Request.GetAbsoluteUriNoPathBase(), service.Type, _Options.NetworkType);
switch (service.Type)
{
@ -618,7 +654,7 @@ namespace BTCPayServer.Controllers
case ExternalServiceTypes.Spark:
if (connectionString.AccessKey == null)
{
StatusMessage = $"Error: The access key of the service is not set";
TempData[WellKnownTempData.ErrorMessage] = $"The access key of the service is not set";
return RedirectToAction(nameof(Services));
}
LightningWalletServices vm = new LightningWalletServices();
@ -635,7 +671,52 @@ namespace BTCPayServer.Controllers
}
catch (Exception ex)
{
StatusMessage = $"Error: {ex.Message}";
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return RedirectToAction(nameof(Services));
}
}
[HttpGet]
[Route("server/services/{serviceName}/{cryptoCode}/removelndseed")]
public IActionResult RemoveLndSeed(string serviceName, string cryptoCode)
{
return View("Confirm", new ConfirmModel()
{
Title = "Delete LND Seed",
Description = "Please make sure you made a backup of the seed and password before deleting the LND backup seed from the server, are you sure to continue?",
Action = "Delete"
});
}
[HttpPost]
[Route("server/services/{serviceName}/{cryptoCode}/removelndseed")]
public async Task<IActionResult> RemoveLndSeedPost(string serviceName, string cryptoCode)
{
var service = GetService(serviceName, cryptoCode);
if (service == null)
return NotFound();
var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath);
if (!model.IsWalletUnlockPresent)
{
TempData[WellKnownTempData.ErrorMessage] = $"File with wallet password and seed info not present";
return RedirectToAction(nameof(Services));
}
if (string.IsNullOrEmpty(model.Seed))
{
TempData[WellKnownTempData.ErrorMessage] = $"Seed information was already removed";
return RedirectToAction(nameof(Services));
}
if (await model.RemoveSeedAndWrite(service.ConnectionString.CookieFilePath))
{
TempData[WellKnownTempData.SuccessMessage] = $"Seed successfully removed";
return RedirectToAction(nameof(Service), new { serviceName, cryptoCode });
}
else
{
TempData[WellKnownTempData.ErrorMessage] = $"Seed removal failed";
return RedirectToAction(nameof(Services));
}
}
@ -654,7 +735,7 @@ namespace BTCPayServer.Controllers
private IActionResult LndServices(ExternalService service, ExternalConnectionString connectionString, uint? nonce)
{
var model = new LndGrpcServicesViewModel();
var model = new LndServicesViewModel();
if (service.Type == ExternalServiceTypes.LNDGRPC)
{
model.Host = $"{connectionString.Server.DnsSafeHost}:{connectionString.Server.Port}";
@ -715,7 +796,7 @@ namespace BTCPayServer.Controllers
{
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
{
StatusMessage = $"Error: {cryptoCode} is not fully synched";
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
return RedirectToAction(nameof(Services));
}
var service = GetService(serviceName, cryptoCode);
@ -729,7 +810,7 @@ namespace BTCPayServer.Controllers
}
catch (Exception ex)
{
StatusMessage = $"Error: {ex.Message}";
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return RedirectToAction(nameof(Services));
}
@ -811,7 +892,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
if (errorMessage == null)
{
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved";
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
settings.Services.Add(viewModel.Settings);
await _SettingsRepository.UpdateSetting(settings);
@ -847,7 +928,7 @@ namespace BTCPayServer.Controllers
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
if (!viewModel.Settings.Enabled)
{
StatusMessage = $"The Dynamic DNS service has been disabled";
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS service has been disabled";
viewModel.Settings.LastUpdated = null;
}
else
@ -855,7 +936,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
if (errorMessage == null)
{
StatusMessage = $"The Dynamic DNS has been successfully queried, your configuration is saved";
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved";
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
}
else
@ -894,7 +975,7 @@ namespace BTCPayServer.Controllers
return NotFound();
settings.Services.RemoveAt(i);
await _SettingsRepository.UpdateSetting(settings);
StatusMessage = "Dynamic DNS service successfully removed";
TempData[WellKnownTempData.SuccessMessage] = "Dynamic DNS service successfully removed";
this.RouteData.Values.Remove(nameof(hostname));
return RedirectToAction(nameof(DynamicDnsServices));
}
@ -965,7 +1046,7 @@ namespace BTCPayServer.Controllers
try
{
await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent);
StatusMessage = "authorized_keys has been updated";
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
updated = true;
}
catch (Exception ex)
@ -994,11 +1075,11 @@ namespace BTCPayServer.Controllers
if (exception is null)
{
StatusMessage = "authorized_keys has been updated";
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
}
else
{
StatusMessage = $"Error: {exception.Message}";
TempData[WellKnownTempData.ErrorMessage] = exception.Message;
}
return RedirectToAction(nameof(SSHService));
}
@ -1014,7 +1095,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Theme(ThemeSettings settings)
{
await _SettingsRepository.UpdateSetting(settings);
TempData["StatusMessage"] = "Theme settings updated successfully";
TempData[WellKnownTempData.SuccessMessage] = "Theme settings updated successfully";
return View(settings);
}
@ -1032,7 +1113,7 @@ namespace BTCPayServer.Controllers
{
if (!model.Settings.IsComplete())
{
model.StatusMessage = "Error: Required fields missing";
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
return View(model);
}
@ -1043,18 +1124,18 @@ namespace BTCPayServer.Controllers
var client = model.Settings.CreateSmtpClient();
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
await client.SendMailAsync(message);
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
TempData[WellKnownTempData.SuccessMessage] = "Email sent to " + model.TestEmail + ", please, verify you received it";
}
catch (Exception ex)
{
model.StatusMessage = "Error: " + ex.Message;
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
}
return View(model);
}
else // if(command == "Save")
{
await _SettingsRepository.UpdateSetting(model.Settings);
model.StatusMessage = "Email settings saved";
TempData[WellKnownTempData.SuccessMessage] = "Email settings saved";
return View(model);
}
}
@ -1071,7 +1152,7 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(_Options.LogFile))
{
vm.StatusMessage = "Error: File Logging Option not specified. " +
TempData[WellKnownTempData.ErrorMessage] = "File Logging Option not specified. " +
"You need to set debuglog and optionally " +
"debugloglevel in the configuration or through runtime arguments";
}
@ -1080,7 +1161,7 @@ namespace BTCPayServer.Controllers
var di = Directory.GetParent(_Options.LogFile);
if (di == null)
{
vm.StatusMessage = "Error: Could not load log files";
TempData[WellKnownTempData.ErrorMessage] = "Could not load log files";
}
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_Options.LogFile);

View File

@ -88,8 +88,7 @@ namespace BTCPayServer.Controllers
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(getxpubResult.ExtPubKey, new DerivationStrategyOptions()
{
P2SH = segwit,
Legacy = !segwit
ScriptPubKeyType = segwit ? ScriptPubKeyType.SegwitP2SH : ScriptPubKeyType.Legacy
});
getxpubResult.DerivationScheme = derivation;
getxpubResult.RootFingerprint = (await hw.GetExtPubKey(network, new KeyPath(), normalOperationTimeout.Token)).ExtPubKey.PubKey.GetHDFingerPrint();
@ -174,11 +173,11 @@ namespace BTCPayServer.Controllers
{
if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
{
vm.StatusMessage = new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Config file was not in the correct format"
}.ToString();
});
vm.Confirmation = false;
return View(vm);
}
@ -188,11 +187,11 @@ namespace BTCPayServer.Controllers
{
if (!DerivationSchemeSettings.TryParseFromColdcard(await ReadAllText(vm.ColdcardPublicFile), network, out strategy))
{
vm.StatusMessage = new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Coldcard public file was not in the correct format"
}.ToString();
});
vm.Confirmation = false;
return View(vm);
}
@ -275,11 +274,11 @@ namespace BTCPayServer.Controllers
if (willBeExcluded != wasExcluded)
{
var label = willBeExcluded ? "disabled" : "enabled";
StatusMessage = $"On-Chain payments for {network.CryptoCode} has been {label}.";
TempData[WellKnownTempData.SuccessMessage] = $"On-Chain payments for {network.CryptoCode} has been {label}.";
}
else
{
StatusMessage = $"Derivation settings for {network.CryptoCode} has been modified.";
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified.";
}
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
}
@ -312,7 +311,7 @@ namespace BTCPayServer.Controllers
}
vm.HintAddress = "";
vm.StatusMessage =
TempData[WellKnownTempData.SuccessMessage] =
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
ModelState.Remove(nameof(vm.HintAddress));
ModelState.Remove(nameof(vm.DerivationScheme));

View File

@ -71,7 +71,7 @@ namespace BTCPayServer.Controllers
storeBlob.ChangellySettings = changellySettings;
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
StatusMessage = "Changelly settings modified";
TempData[WellKnownTempData.SuccessMessage] = "Changelly settings modified";
_changellyClientProvider.InvalidateClient(storeId);
return RedirectToAction(nameof(UpdateStore), new {
storeId});
@ -81,12 +81,12 @@ namespace BTCPayServer.Controllers
var client = new Changelly(_httpClientFactory.CreateClient(), changellySettings.ApiKey, changellySettings.ApiSecret,
changellySettings.ApiUrl);
var result = await client.GetCurrenciesFull();
vm.StatusMessage = "Test Successful";
TempData[WellKnownTempData.SuccessMessage] = "Test Successful";
return View(vm);
}
catch (Exception ex)
{
vm.StatusMessage = $"Error: {ex.Message}";
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return View(vm);
}
default:

View File

@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers
storeBlob.CoinSwitchSettings = coinSwitchSettings;
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
StatusMessage = "CoinSwitch settings modified";
TempData[WellKnownTempData.SuccessMessage] = "CoinSwitch settings modified";
return RedirectToAction(nameof(UpdateStore), new {
storeId});

View File

@ -36,17 +36,17 @@ namespace BTCPayServer.Controllers
{
if (!model.Settings.IsComplete())
{
model.StatusMessage = "Error: Required fields missing";
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
return View(model);
}
var client = model.Settings.CreateSmtpClient();
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
await client.SendMailAsync(message);
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
TempData[WellKnownTempData.SuccessMessage] = "Email sent to " + model.TestEmail + ", please, verify you received it";
}
catch (Exception ex)
{
model.StatusMessage = "Error: " + ex.Message;
TempData[WellKnownTempData.ErrorMessage] = "Error: " + ex.Message;
}
return View(model);
}
@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers
storeBlob.EmailSettings = model.Settings;
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
StatusMessage = "Email settings modified";
TempData[WellKnownTempData.SuccessMessage] = "Email settings modified";
return RedirectToAction(nameof(UpdateStore), new {
storeId});

View File

@ -146,7 +146,7 @@ namespace BTCPayServer.Controllers
store.SetStoreBlob(storeBlob);
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
await _Repo.UpdateStore(store);
StatusMessage = $"Lightning node modified ({network.CryptoCode})";
TempData[WellKnownTempData.SuccessMessage] = $"Lightning node modified ({network.CryptoCode})";
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
case "test" when paymentMethod == null:
ModelState.AddModelError(nameof(vm.ConnectionString), "Missing url parameter");
@ -163,11 +163,11 @@ namespace BTCPayServer.Controllers
await handler.TestConnection(info, cts.Token);
}
}
vm.StatusMessage = $"Connection to the lightning node succeeded ({info})";
TempData[WellKnownTempData.SuccessMessage] = $"Connection to the lightning node succeeded ({info})";
}
catch (Exception ex)
{
vm.StatusMessage = $"Error: {ex.Message}";
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
return View(vm);
}
return View(vm);

View File

@ -102,11 +102,6 @@ namespace BTCPayServer.Controllers
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly CssThemeManager _CssThemeManager;
[TempData]
public string StatusMessage
{
get; set;
}
[TempData]
public bool StoreNotConfigured
{
@ -168,7 +163,7 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store");
return View(vm);
}
StatusMessage = "User added successfully";
TempData[WellKnownTempData.SuccessMessage] = "User added successfully";
return RedirectToAction(nameof(StoreUsers));
}
@ -193,7 +188,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> DeleteStoreUserPost(string storeId, string userId)
{
await _Repo.RemoveStoreUser(storeId, userId);
StatusMessage = "User removed successfully";
TempData[WellKnownTempData.SuccessMessage] = "User removed successfully";
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
}
@ -314,7 +309,7 @@ namespace BTCPayServer.Controllers
if (CurrentStore.SetStoreBlob(blob))
{
await _Repo.UpdateStore(CurrentStore);
StatusMessage = "Rate settings updated";
TempData[WellKnownTempData.SuccessMessage] = "Rate settings updated";
}
return RedirectToAction(nameof(Rates), new
{
@ -347,7 +342,7 @@ namespace BTCPayServer.Controllers
blob.RateScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
CurrentStore.SetStoreBlob(blob);
await _Repo.UpdateStore(CurrentStore);
StatusMessage = "Rate rules scripting activated";
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting activated";
return RedirectToAction(nameof(Rates), new { storeId = CurrentStore.Id });
}
@ -358,12 +353,13 @@ namespace BTCPayServer.Controllers
var storeBlob = CurrentStore.GetStoreBlob();
var vm = new CheckoutExperienceViewModel();
SetCryptoCurrencies(vm, CurrentStore);
vm.CustomCSS = storeBlob.CustomCSS?.AbsoluteUri;
vm.CustomLogo = storeBlob.CustomLogo?.AbsoluteUri;
vm.CustomCSS = storeBlob.CustomCSS;
vm.CustomLogo = storeBlob.CustomLogo;
vm.HtmlTitle = storeBlob.HtmlTitle;
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
vm.ShowRecommendedFee = storeBlob.ShowRecommendedFee;
vm.RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget;
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
@ -417,12 +413,13 @@ namespace BTCPayServer.Controllers
{
return View(model);
}
blob.CustomLogo = string.IsNullOrWhiteSpace(model.CustomLogo) ? null : new Uri(model.CustomLogo, UriKind.Absolute);
blob.CustomCSS = string.IsNullOrWhiteSpace(model.CustomCSS) ? null : new Uri(model.CustomCSS, UriKind.Absolute);
blob.CustomLogo = model.CustomLogo;
blob.CustomCSS = model.CustomCSS;
blob.HtmlTitle = string.IsNullOrWhiteSpace(model.HtmlTitle) ? null : model.HtmlTitle;
blob.DefaultLang = model.DefaultLang;
blob.RequiresRefundEmail = model.RequiresRefundEmail;
blob.ShowRecommendedFee = model.ShowRecommendedFee;
blob.RecommendedFeeBlockTarget = model.RecommendedFeeBlockTarget;
blob.OnChainMinValue = onchainMinValue;
blob.LightningMaxValue = lightningMaxValue;
blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi;
@ -434,7 +431,7 @@ namespace BTCPayServer.Controllers
if (needUpdate)
{
await _Repo.UpdateStore(CurrentStore);
StatusMessage = "Store successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
}
return RedirectToAction(nameof(CheckoutExperience), new
@ -563,7 +560,7 @@ namespace BTCPayServer.Controllers
if (needUpdate)
{
await _Repo.UpdateStore(CurrentStore);
StatusMessage = "Store successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
}
return RedirectToAction(nameof(UpdateStore), new
@ -579,7 +576,7 @@ namespace BTCPayServer.Controllers
{
return View("Confirm", new ConfirmModel()
{
Action = "Delete this store",
Action = "Delete",
Title = "Delete this store",
Description = "This action is irreversible and will remove all information related to this store. (Invoices, Apps etc...)",
ButtonClass = "btn-danger"
@ -591,7 +588,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> DeleteStorePost(string storeId)
{
await _Repo.DeleteStore(CurrentStore.Id);
StatusMessage = "Success: Store successfully deleted";
TempData[WellKnownTempData.SuccessMessage] = "Store successfully deleted";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
@ -617,7 +614,6 @@ namespace BTCPayServer.Controllers
{
var model = new TokensViewModel();
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(CurrentStore.Id);
model.StatusMessage = StatusMessage;
model.StoreNotConfigured = StoreNotConfigured;
model.Tokens = tokens.Select(t => new TokenViewModel()
{
@ -643,7 +639,7 @@ namespace BTCPayServer.Controllers
return NotFound();
return View("Confirm", new ConfirmModel()
{
Action = "Revoke the token",
Action = "Revoke",
Title = "Revoke the token",
Description = $"The access token with the label \"{token.Label}\" will be revoked, do you wish to continue?",
ButtonClass = "btn-danger"
@ -657,9 +653,9 @@ namespace BTCPayServer.Controllers
if (token == null ||
token.StoreId != CurrentStore.Id ||
!await _TokenRepository.DeleteToken(tokenId))
StatusMessage = "Failure to revoke this token";
TempData[WellKnownTempData.ErrorMessage] = "Failure to revoke this token";
else
StatusMessage = "Token revoked";
TempData[WellKnownTempData.SuccessMessage] = "Token revoked";
return RedirectToAction(nameof(ListTokens));
}
@ -674,28 +670,21 @@ namespace BTCPayServer.Controllers
}
[HttpPost]
[Route("/api-tokens")]
[Route("{storeId}/Tokens/Create")]
public async Task<IActionResult> CreateToken(CreateTokenViewModel model)
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
return View(nameof(CreateToken), model);
}
model.Label = model.Label ?? String.Empty;
var userId = GetUserId();
if (userId == null)
return Challenge(AuthenticationSchemes.Cookie);
var store = CurrentStore;
var storeId = CurrentStore?.Id;
if (storeId == null)
{
storeId = model.StoreId;
store = await _Repo.FindStore(storeId, userId);
if (store == null)
return Challenge(AuthenticationSchemes.Cookie);
}
storeId = model.StoreId;
var store = CurrentStore ?? await _Repo.FindStore(storeId, userId);
if (store == null)
return Challenge(AuthenticationSchemes.Cookie);
var tokenRequest = new TokenRequest()
{
Label = model.Label,
@ -730,8 +719,20 @@ namespace BTCPayServer.Controllers
public string GeneratedPairingCode { get; set; }
[HttpGet]
[Route("/api-tokens")]
[Route("{storeId}/Tokens/Create")]
public IActionResult CreateToken(string storeId)
{
var model = new CreateTokenViewModel();
ViewBag.HidePublicKey = storeId == null;
ViewBag.ShowStores = storeId == null;
ViewBag.ShowMenu = storeId != null;
model.StoreId = storeId;
return View(model);
}
[HttpGet]
[Route("/api-tokens")]
[AllowAnonymous]
public async Task<IActionResult> CreateToken()
{
var userId = GetUserId();
@ -739,23 +740,27 @@ namespace BTCPayServer.Controllers
return Challenge(AuthenticationSchemes.Cookie);
var storeId = CurrentStore?.Id;
var model = new CreateTokenViewModel();
ViewBag.HidePublicKey = storeId == null;
ViewBag.ShowStores = storeId == null;
ViewBag.ShowMenu = storeId != null;
model.StoreId = storeId;
if (storeId == null)
ViewBag.HidePublicKey = true;
ViewBag.ShowStores = true;
ViewBag.ShowMenu = false;
var stores = await _Repo.GetStoresByUserId(userId);
model.Stores = new SelectList(stores.Where(s => s.Role == StoreRoles.Owner), nameof(CurrentStore.Id), nameof(CurrentStore.StoreName));
if (model.Stores.Count() == 0)
{
var stores = await _Repo.GetStoresByUserId(userId);
model.Stores = new SelectList(stores.Where(s => s.Role == StoreRoles.Owner), nameof(CurrentStore.Id), nameof(CurrentStore.StoreName), storeId);
if (model.Stores.Count() == 0)
{
StatusMessage = "Error: You need to be owner of at least one store before pairing";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
TempData[WellKnownTempData.ErrorMessage] = "You need to be owner of at least one store before pairing";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
return View(model);
}
[HttpPost]
[Route("/api-tokens")]
[AllowAnonymous]
public Task<IActionResult> CreateToken2(CreateTokenViewModel model)
{
return CreateToken(model.StoreId, model);
}
[HttpPost]
[Route("{storeId}/tokens/apikey")]
public async Task<IActionResult> GenerateAPIKey()
@ -764,7 +769,7 @@ namespace BTCPayServer.Controllers
if (store == null)
return NotFound();
await _TokenRepository.GenerateLegacyAPIKey(CurrentStore.Id);
StatusMessage = "API Key re-generated";
TempData[WellKnownTempData.SuccessMessage] = "API Key re-generated";
return RedirectToAction(nameof(ListTokens));
}
@ -781,7 +786,7 @@ namespace BTCPayServer.Controllers
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
if (pairing == null)
{
StatusMessage = "Unknown pairing code";
TempData[WellKnownTempData.ErrorMessage] = "Unknown pairing code";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
else
@ -820,9 +825,9 @@ namespace BTCPayServer.Controllers
StoreNotConfigured = store.GetSupportedPaymentMethods(_NetworkProvider)
.Where(p => !excludeFilter.Match(p.PaymentId))
.Count() == 0;
StatusMessage = "Pairing is successful";
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
if (pairingResult == PairingResult.Partial)
StatusMessage = "Server initiated pairing code: " + pairingCode;
TempData[WellKnownTempData.SuccessMessage] = "Server initiated pairing code: " + pairingCode;
return RedirectToAction(nameof(ListTokens), new
{
storeId = store.Id,
@ -831,7 +836,7 @@ namespace BTCPayServer.Controllers
}
else
{
StatusMessage = $"Pairing failed ({pairingResult})";
TempData[WellKnownTempData.ErrorMessage] = $"Pairing failed ({pairingResult})";
return RedirectToAction(nameof(ListTokens), new
{
storeId = store.Id
@ -889,7 +894,7 @@ namespace BTCPayServer.Controllers
if (CurrentStore.SetStoreBlob(blob))
{
await _Repo.UpdateStore(CurrentStore);
StatusMessage = "Store successfully updated";
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
}
return RedirectToAction(nameof(PayButton), new

View File

@ -71,13 +71,10 @@ namespace BTCPayServer.Controllers
if (store == null)
return NotFound();
await _Repo.RemoveStore(storeId, userId);
StatusMessage = "Store removed successfully";
TempData[WellKnownTempData.SuccessMessage] = "Store removed successfully";
return RedirectToAction(nameof(ListStores));
}
[TempData]
public string StatusMessage { get; set; }
[HttpGet]
public async Task<IActionResult> ListStores()
{
@ -107,7 +104,7 @@ namespace BTCPayServer.Controllers
}
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
CreatedStoreId = store.Id;
StatusMessage = "Store successfully created";
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id

View File

@ -69,6 +69,8 @@ namespace BTCPayServer.Controllers
WalletId walletId,
WalletPSBTViewModel vm, string command = null)
{
if (command == null)
return await WalletPSBT(walletId, vm);
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
if (psbt == null)
@ -78,7 +80,7 @@ namespace BTCPayServer.Controllers
}
switch (command)
{
case null:
case "decode":
vm.Decoded = psbt.ToString();
ModelState.Remove(nameof(vm.PSBT));
ModelState.Remove(nameof(vm.FileName));
@ -96,8 +98,8 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.PSBT), "You need to update your version of NBXplorer");
return View(vm);
}
StatusMessage = "PSBT updated!";
return RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64(), FileName = vm.FileName });
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
return RedirectToWalletPSBT(walletId, psbt, vm.FileName);
case "seed":
return SignWithSeed(walletId, psbt.ToBase64());
case "broadcast":
@ -258,6 +260,8 @@ namespace BTCPayServer.Controllers
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletPSBTReadyViewModel vm, string command = null)
{
if (command == null)
return await WalletPSBTReady(walletId, vm.PSBT, vm.SigningKey, vm.SigningKeyPath);
PSBT psbt = null;
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
try
@ -299,7 +303,7 @@ namespace BTCPayServer.Controllers
}
else if (command == "analyze-psbt")
{
return RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64() });
return RedirectToWalletPSBT(walletId, psbt);
}
else
{
@ -308,27 +312,6 @@ namespace BTCPayServer.Controllers
}
}
private IActionResult ViewPSBT<T>(PSBT psbt, IEnumerable<T> errors = null)
{
return ViewPSBT(psbt, null, errors?.Select(e => e.ToString()).ToList());
}
private IActionResult ViewPSBT(PSBT psbt, IEnumerable<string> errors = null)
{
return ViewPSBT(psbt, null, errors);
}
private IActionResult ViewPSBT(PSBT psbt, string fileName, IEnumerable<string> errors = null)
{
ModelState.Remove(nameof(WalletPSBTViewModel.PSBT));
ModelState.Remove(nameof(WalletPSBTViewModel.FileName));
return View(nameof(WalletPSBT), new WalletPSBTViewModel()
{
Decoded = psbt.ToString(),
FileName = fileName ?? string.Empty,
PSBT = psbt.ToBase64(),
Errors = errors?.ToList()
});
}
private IActionResult FilePSBT(PSBT psbt, string fileName)
{
return File(psbt.ToBytes(), "application/octet-stream", fileName);
@ -353,8 +336,8 @@ namespace BTCPayServer.Controllers
return View(vm);
}
sourcePSBT = sourcePSBT.Combine(psbt);
StatusMessage = "PSBT Successfully combined!";
return ViewPSBT(sourcePSBT);
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
return RedirectToWalletPSBT(walletId, sourcePSBT);
}
}
}

View File

@ -50,8 +50,6 @@ namespace BTCPayServer.Controllers
private readonly IFeeProviderFactory _feeRateProvider;
private readonly BTCPayWalletProvider _walletProvider;
public RateFetcher RateFetcher { get; }
[TempData]
public string StatusMessage { get; set; }
CurrencyNameTable _currencyTable;
public WalletsController(StoreRepository repo,
@ -458,21 +456,59 @@ namespace BTCPayServer.Controllers
case "analyze-psbt":
var name =
$"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt";
return RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.PSBT.ToBase64(), FileName = name });
return RedirectToWalletPSBT(walletId, psbt.PSBT, name);
default:
return View(vm);
}
}
private IActionResult RedirectToWalletPSBT(WalletId walletId, PSBT psbt, string fileName = null)
{
var vm = new PostRedirectViewModel()
{
AspController = "Wallets",
AspAction = nameof(WalletPSBT),
Parameters =
{
new KeyValuePair<string, string>("psbt", psbt.ToBase64())
}
};
if (!string.IsNullOrEmpty(fileName))
vm.Parameters.Add(new KeyValuePair<string, string>("fileName", fileName));
return View("PostRedirect", vm);
}
void SetAmbientPSBT(PSBT psbt)
{
if (psbt != null)
TempData["AmbientPSBT"] = psbt.ToBase64();
else
TempData.Remove("AmbientPSBT");
}
PSBT GetAmbientPSBT(Network network, bool peek)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
if ((peek ? TempData.Peek("AmbientPSBT") : TempData["AmbientPSBT"]) is string str)
{
try
{
return PSBT.Parse(str, network);
}
catch { }
}
return null;
}
private ViewResult ViewWalletSendLedger(PSBT psbt, BitcoinAddress hintChange = null)
{
SetAmbientPSBT(psbt);
return View("WalletSendLedger", new WalletSendLedgerModel()
{
PSBT = psbt.ToBase64(),
HintChange = hintChange?.ToString(),
WebsocketPath = this.Url.Action(nameof(LedgerConnection)),
SuccessPath = this.Url.Action(nameof(WalletPSBTReady))
WebsocketPath = this.Url.Action(nameof(LedgerConnection))
});
}
@ -573,7 +609,7 @@ namespace BTCPayServer.Controllers
var wallet = _walletProvider.GetWallet(network);
var derivationSettings = GetDerivationSchemeSettings(walletId);
wallet.InvalidateCache(derivationSettings.AccountDerivation);
StatusMessage = $"Transaction broadcasted successfully ({transaction.GetHash().ToString()})";
TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash().ToString()})";
}
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
}
@ -704,7 +740,6 @@ namespace BTCPayServer.Controllers
// getxpub
int account = 0,
// sendtoaddress
string psbt = null,
string hintChange = null
)
{
@ -714,6 +749,7 @@ namespace BTCPayServer.Controllers
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
if (network == null)
throw new FormatException("Invalid value for crypto code");
PSBT psbt = GetAmbientPSBT(network.NBitcoinNetwork, true);
var derivationSettings = GetDerivationSchemeSettings(walletId);
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
@ -723,7 +759,6 @@ namespace BTCPayServer.Controllers
{
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
var hw = new LedgerHardwareWalletService(webSocket);
var model = new WalletSendLedgerModel();
object result = null;
try
{
@ -777,18 +812,12 @@ namespace BTCPayServer.Controllers
await Repository.UpdateStore(storeData);
}
var psbtResponse = new CreatePSBTResponse()
{
PSBT = PSBT.Parse(psbt, network.NBitcoinNetwork),
ChangeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork)
};
derivationSettings.RebaseKeyPaths(psbtResponse.PSBT);
derivationSettings.RebaseKeyPaths(psbt);
var changeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork);
signTimeout.CancelAfter(TimeSpan.FromMinutes(5));
psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, accountKey.GetRootedKeyPath(), accountKey.AccountKey, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token);
result = new SendToAddressResult() { PSBT = psbtResponse.PSBT.ToBase64() };
psbt = await hw.SignTransactionAsync(psbt, accountKey.GetRootedKeyPath(), accountKey.AccountKey, changeAddress?.ScriptPubKey, signTimeout.Token);
SetAmbientPSBT(null);
result = new SendToAddressResult() { PSBT = psbt.ToBase64() };
}
}
catch (OperationCanceledException)
@ -863,7 +892,7 @@ namespace BTCPayServer.Controllers
var store = (await Repository.FindStore(walletId.StoreId, GetUserId()));
store.SetSupportedPaymentMethod(derivationScheme);
await Repository.UpdateStore(store);
StatusMessage = "Wallet settings updated";
TempData[WellKnownTempData.SuccessMessage] = "Wallet settings updated";
return RedirectToAction(nameof(WalletSettings));
}
}

View File

@ -21,8 +21,8 @@ namespace BTCPayServer.Data
public static bool SetBlob(this PaymentRequestData paymentRequestData, PaymentRequestBlob blob)
{
var original = new Serializer(Network.Main).ToString(paymentRequestData.GetBlob());
var newBlob = new Serializer(Network.Main).ToString(blob);
var original = new Serializer(null).ToString(paymentRequestData.GetBlob());
var newBlob = new Serializer(null).ToString(blob);
if (original == newBlob)
return false;
paymentRequestData.Blob = ZipUtils.Zip(newBlob);

View File

@ -21,6 +21,7 @@ namespace BTCPayServer.Data
MonitoringExpiration = 1440;
PaymentTolerance = 0;
ShowRecommendedFee = true;
RecommendedFeeBlockTarget = 1;
}
[Obsolete("Use NetworkFeeMode instead")]
@ -41,6 +42,8 @@ namespace BTCPayServer.Data
public bool ShowRecommendedFee { get; set; }
public int RecommendedFeeBlockTarget { get; set; }
CurrencyPair[] _DefaultCurrencyPairs;
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
public CurrencyPair[] DefaultCurrencyPairs
@ -89,10 +92,9 @@ namespace BTCPayServer.Data
public CurrencyValue LightningMaxValue { get; set; }
public bool LightningAmountInSatoshi { get; set; }
[JsonConverter(typeof(UriJsonConverter))]
public Uri CustomLogo { get; set; }
[JsonConverter(typeof(UriJsonConverter))]
public Uri CustomCSS { get; set; }
public string CustomLogo { get; set; }
public string CustomCSS { get; set; }
public string HtmlTitle { get; set; }
public bool RateScripting { get; set; }

View File

@ -45,11 +45,10 @@ namespace BTCPayServer.Data
}
#pragma warning restore CS0618
static Network Dummy = Network.Main;
public static StoreBlob GetStoreBlob(this StoreData storeData)
{
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(Dummy).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
if (result.PreferredExchange == null)
result.PreferredExchange = CoinAverageRateProvider.CoinAverageName;
return result;
@ -57,8 +56,8 @@ namespace BTCPayServer.Data
public static bool SetStoreBlob(this StoreData storeData, StoreBlob storeBlob)
{
var original = new Serializer(Dummy).ToString(storeData.GetStoreBlob());
var newBlob = new Serializer(Dummy).ToString(storeBlob);
var original = new Serializer(null).ToString(storeData.GetStoreBlob());
var newBlob = new Serializer(null).ToString(storeBlob);
if (original == newBlob)
return false;
storeData.StoreBlob = Encoding.UTF8.GetBytes(newBlob);

View File

@ -213,13 +213,12 @@ namespace BTCPayServer
{
foreach (var accountKey in AccountKeySettings)
{
if (accountKey.AccountKeyPath != null && accountKey.RootFingerprint is HDFingerprint fp)
if (accountKey.GetRootedKeyPath() is RootedKeyPath rootedKeyPath)
{
yield return new NBXplorer.Models.PSBTRebaseKeyRules()
{
AccountKey = accountKey.AccountKey,
AccountKeyPath = accountKey.AccountKeyPath,
MasterFingerprint = fp
AccountKeyPath = rootedKeyPath
};
}
}
@ -250,7 +249,7 @@ namespace BTCPayServer
{
foreach (var rebase in GetPSBTRebaseKeyRules())
{
psbt.RebaseKeyPaths(rebase.AccountKey, rebase.GetRootedKeyPath());
psbt.RebaseKeyPaths(rebase.AccountKey, rebase.AccountKeyPath);
}
}
}

View File

@ -34,6 +34,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using NBXplorer.DerivationStrategy;
using System.Net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Newtonsoft.Json.Linq;
namespace BTCPayServer
{
@ -109,6 +111,13 @@ namespace BTCPayServer
}
return value;
}
public static bool HasStatusMessage(this ITempDataDictionary tempData)
{
return (tempData.Peek(WellKnownTempData.SuccessMessage) ??
tempData.Peek(WellKnownTempData.ErrorMessage) ??
tempData.Peek("StatusMessageModel")) != null;
}
public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info)
{
return new PaymentMethodId(info.CryptoCode, PaymentTypes.Parse(info.PaymentType));
@ -206,6 +215,42 @@ namespace BTCPayServer
return false;
}
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
{
if (statusMessage == null)
{
tempData.Remove("StatusMessageModel");
return;
}
tempData["StatusMessageModel"] = JObject.FromObject(statusMessage).ToString(Formatting.None);
}
public static StatusMessageModel GetStatusMessageModel(this ITempDataDictionary tempData)
{
tempData.TryGetValue(WellKnownTempData.SuccessMessage, out var successMessage);
tempData.TryGetValue(WellKnownTempData.ErrorMessage, out var errorMessage);
tempData.TryGetValue("StatusMessageModel", out var model);
if (successMessage != null || errorMessage != null)
{
var parsedModel = new StatusMessageModel();
parsedModel.Message = (string)successMessage ?? (string)errorMessage;
if (successMessage != null)
{
parsedModel.Severity = StatusMessageModel.StatusSeverity.Success;
}
else
{
parsedModel.Severity = StatusMessageModel.StatusSeverity.Error;
}
return parsedModel;
}
else if (model != null && model is string str)
{
return JObject.Parse(str).ToObject<StatusMessageModel>();
}
return null;
}
public static bool IsOnion(this HttpRequest request)
{
if (request?.Host.Host == null)

View File

@ -81,7 +81,8 @@ namespace BTCPayServer.HostedServices
_cancellationTokenSource.Cancel();
try
{
await _testingConnection;
// Renci SSH sometimes is deadlocking, so we just wait at most 5 seconds
await Task.WhenAny(_testingConnection, Task.Delay(5000, _cancellationTokenSource.Token));
}
catch { }
Logs.PayServer.LogInformation($"{this.GetType().Name} successfully exited...");

View File

@ -21,16 +21,27 @@ namespace BTCPayServer.HostedServices
{
public void Update(ThemeSettings data)
{
if (String.IsNullOrWhiteSpace(data.ThemeCssUri))
_themeUri = "/main/themes/classic.css";
else
_themeUri = data.ThemeCssUri;
if (String.IsNullOrWhiteSpace(data.BootstrapCssUri))
_bootstrapUri = "/vendor/bootstrap4/css/bootstrap.css?v=" + DateTime.Now.Ticks;
_bootstrapUri = "/main/bootstrap/bootstrap.css";
else
_bootstrapUri = data.BootstrapCssUri;
if (String.IsNullOrWhiteSpace(data.CreativeStartCssUri))
_creativeStartUri = "/vendor/bootstrap4-creativestart/creative.css?v=" + DateTime.Now.Ticks;
_creativeStartUri = "/main/bootstrap4-creativestart/creative.css";
else
_creativeStartUri = data.CreativeStartCssUri;
FirstRun = data.FirstRun;
}
private string _themeUri;
public string ThemeUri
{
get { return _themeUri; }
}
private string _bootstrapUri;
@ -52,6 +63,8 @@ namespace BTCPayServer.HostedServices
public AppType? RootAppType { get; set; }
public string RootAppId { get; set; }
public bool FirstRun { get; set; }
public List<PoliciesSettings.DomainToAppMappingItem> DomainToAppMapping { get; set; } = new List<PoliciesSettings.DomainToAppMappingItem>();
internal void Update(PoliciesSettings data)
@ -74,7 +87,7 @@ namespace BTCPayServer.HostedServices
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
@ -91,6 +104,10 @@ namespace BTCPayServer.HostedServices
{
policies.Clear();
}
if (manager.ThemeUri != null && Uri.TryCreate(manager.ThemeUri, UriKind.Absolute, out uri))
{
policies.Clear();
}
}
}
}

View File

@ -11,6 +11,7 @@ using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using BTCPayServer.Events;
using BTCPayServer.Services.Invoices;
using System.Threading.Channels;
namespace BTCPayServer.HostedServices
{
@ -179,7 +180,7 @@ namespace BTCPayServer.HostedServices
{
if (invoiceId == null)
throw new ArgumentNullException(nameof(invoiceId));
_WatchRequests.Add(invoiceId);
_WatchRequests.Writer.TryWrite(invoiceId);
}
private async Task Wait(string invoiceId)
@ -205,7 +206,7 @@ namespace BTCPayServer.HostedServices
}
BlockingCollection<string> _WatchRequests = new BlockingCollection<string>(new ConcurrentQueue<string>());
Channel<string> _WatchRequests = Channel.CreateUnbounded<string>();
Task _Loop;
CancellationTokenSource _Cts;
@ -245,9 +246,7 @@ namespace BTCPayServer.HostedServices
async Task StartLoop(CancellationToken cancellation)
{
Logs.PayServer.LogInformation("Start watching invoices");
await Task.Delay(1).ConfigureAwait(false); // Small hack so that the caller does not block on GetConsumingEnumerable
foreach (var invoiceId in _WatchRequests.GetConsumingEnumerable(cancellation))
while (await _WatchRequests.Reader.WaitToReadAsync(cancellation) && _WatchRequests.Reader.TryRead(out var invoiceId))
{
int maxLoop = 5;
int loopCount = -1;

View File

@ -21,9 +21,6 @@ namespace BTCPayServer.HostedServices
internal override Task[] InitializeTasks()
{
// TODO: We should report auto configured services (like bitcoind, lnd or clightning)
if (string.IsNullOrEmpty(_options.TorrcFile))
return Array.Empty<Task>();
return new Task[] { CreateLoopTask(RefreshTorServices) };
}

View File

@ -55,6 +55,7 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using BTCPayServer.Security.Bitpay;
using Serilog;
namespace BTCPayServer.Hosting
{
@ -186,8 +187,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<CurrencyNameTable>();
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClientProvider>())
{
Fallback = new FeeRate(100L, 1),
BlockTarget = 20
Fallback = new FeeRate(100L, 1)
});
services.AddSingleton<CssThemeManager>();
@ -269,9 +269,24 @@ namespace BTCPayServer.Hosting
var rateLimits = new RateLimitService();
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=5r/min burst=3 nodelay");
services.AddSingleton(rateLimits);
services.AddLogging(logBuilder =>
{
var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration);
if (!string.IsNullOrEmpty(debugLogFile))
{
Serilog.Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Is(BTCPayServerOptions.GetDebugLogLevel(configuration))
.WriteTo.File(debugLogFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: MAX_DEBUG_LOG_FILE_SIZE, rollOnFileSizeLimit: true, retainedFileCountLimit: 1)
.CreateLogger();
logBuilder.AddSerilog(Serilog.Log.Logger);
}
});
return services;
}
private const long MAX_DEBUG_LOG_FILE_SIZE = 2000000; // If debug log is in use roll it every N MB.
private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services,
IConfiguration configuration)
{

View File

@ -222,6 +222,7 @@ namespace BTCPayServer.Hosting
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
{
Logs.Configure(loggerFactory);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();

View File

@ -10,6 +10,7 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Logging;
using System.Threading;
using Npgsql;
using Microsoft.AspNetCore.Identity;
namespace BTCPayServer
{
@ -19,16 +20,19 @@ namespace BTCPayServer
private StoreRepository _StoreRepository;
private BTCPayNetworkProvider _NetworkProvider;
private SettingsRepository _Settings;
private readonly UserManager<ApplicationUser> _userManager;
public MigrationStartupTask(
BTCPayNetworkProvider networkProvider,
StoreRepository storeRepository,
ApplicationDbContextFactory dbContextFactory,
UserManager<ApplicationUser> userManager,
SettingsRepository settingsRepository)
{
_DBContextFactory = dbContextFactory;
_StoreRepository = storeRepository;
_NetworkProvider = networkProvider;
_Settings = settingsRepository;
_userManager = userManager;
}
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
@ -72,6 +76,15 @@ namespace BTCPayServer
settings.ConvertWalletKeyPathRoots = true;
await _Settings.UpdateSetting(settings);
}
if (!settings.CheckedFirstRun)
{
var themeSettings = await _Settings.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
themeSettings.FirstRun = admin.Count == 0;
await _Settings.UpdateSetting(themeSettings);
settings.CheckedFirstRun = true;
await _Settings.UpdateSetting(settings);
}
}
catch (Exception ex)
{

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Models.AccountViewModels
{
public class ExternalLoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
}

View File

@ -11,7 +11,6 @@ namespace BTCPayServer.Models.AppViewModels
public class ViewCrowdfundViewModel
{
public string HubPath { get; set; }
public string StatusMessage{ get; set; }
public string StoreId { get; set; }
public string AppId { get; set; }
public string Title { get; set; }

View File

@ -49,11 +49,6 @@ namespace BTCPayServer.Models.InvoicingModels
public string Destination { get; set; }
public bool Current { get; set; }
}
public string StatusMessage
{
get; set;
}
public String Id
{
get; set;

View File

@ -13,9 +13,8 @@ namespace BTCPayServer.Models.InvoicingModels
public int Total { get; set; }
public string SearchTerm { get; set; }
public int? TimezoneOffset { get; set; }
public List<InvoiceModel> Invoices { get; set; } = new List<InvoiceModel>();
public string StatusMessage { get; set; }
public string[] StoreIds { get; set; }
}
public class InvoiceModel
@ -34,7 +33,6 @@ namespace BTCPayServer.Models.InvoicingModels
public bool ShowCheckout { get; set; }
public string ExceptionStatus { get; set; }
public string AmountCurrency { get; set; }
public string StatusMessage { get; set; }
public InvoiceDetailsModel Details { get; set; }
}

View File

@ -78,5 +78,6 @@ namespace BTCPayServer.Models.InvoicingModels
public string RootPath { get; set; }
public decimal CoinSwitchAmountMarkupPercentage { get; set; }
public bool RedirectAutomatically { get; set; }
public string RateBaseAmount { get; set; } = "1";
}
}

View File

@ -23,7 +23,5 @@ namespace BTCPayServer.Models.ManageViewModels
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string StatusMessage { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
namespace BTCPayServer.Models.ManageViewModels
{
public class ExternalLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationScheme> OtherLogins { get; set; }
public bool ShowRemoveButton { get; set; }
public string StatusMessage { get; set; }
}
}

View File

@ -27,10 +27,5 @@ namespace BTCPayServer.Models.ManageViewModels
[MaxLength(50)]
public string PhoneNumber { get; set; }
public string StatusMessage
{
get; set;
}
}
}

View File

@ -18,7 +18,5 @@ namespace BTCPayServer.Models.ManageViewModels
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string StatusMessage { get; set; }
}
}

View File

@ -15,7 +15,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public List<ViewPaymentRequestViewModel> Items { get; set; }
public string StatusMessage { get; set; }
public int Total { get; set; }
}
@ -61,7 +60,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public DateTime? ExpiryDate { get; set; }
[Required] public string Title { get; set; }
public string Description { get; set; }
public string StatusMessage { get; set; }
public SelectList Stores { get; set; }
[EmailAddress]
@ -142,7 +140,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public bool AnyPendingInvoice { get; set; }
public bool PendingInvoiceHasPayments { get; set; }
public string HubPath { get; set; }
public string StatusMessage { get; set; }
public class PaymentRequestInvoice
{

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Models
{
public class PostRedirectViewModel
{
public string AspAction { get; set; }
public string AspController { get; set; }
public List<KeyValuePair<string,string>> Parameters { get; set; } = new List<KeyValuePair<string, string>>();
}
}

View File

@ -10,10 +10,6 @@ namespace BTCPayServer.Models.ServerViewModels
{
public class EmailsViewModel
{
public string StatusMessage
{
get; set;
}
public EmailSettings Settings
{
get; set;

View File

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Models.ServerViewModels
{
public class LndRestServicesViewModel
{
public string BaseApiUrl { get; set; }
public string Macaroon { get; set; }
public string CertificateThumbprint { get; set; }
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace BTCPayServer.Models.ServerViewModels
{
public class LndSeedBackupViewModel
{
public bool IsWalletUnlockPresent { get; set; }
public string WalletPassword { get; set; }
public string Seed { get; set; }
public bool Removed { get; set; }
public async Task<bool> RemoveSeedAndWrite(string lndSeedFilePath)
{
var removedDate = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssZ", CultureInfo.InvariantCulture);
var seedFile = new LndSeedFile
{
wallet_password = "",
cipher_seed_mnemonic = new List<string> { $"Seed removed on {removedDate}" }
};
var json = JsonConvert.SerializeObject(seedFile);
try
{
await File.WriteAllTextAsync(lndSeedFilePath, json);
return true;
}
catch
{
// file access exception and such
return false;
}
}
public static LndSeedBackupViewModel Parse(string lndSeedFilePath)
{
try
{
if (!String.IsNullOrEmpty(lndSeedFilePath) && File.Exists(lndSeedFilePath))
{
var unlockFileContents = File.ReadAllText(lndSeedFilePath);
var unlockFile = JsonConvert.DeserializeObject<LndSeedFile>(unlockFileContents);
if (unlockFile.wallet_password != null)
{
return new LndSeedBackupViewModel
{
WalletPassword = unlockFile.wallet_password,
Seed = string.Join(' ', unlockFile.cipher_seed_mnemonic),
IsWalletUnlockPresent = true,
Removed = unlockFile.cipher_seed_mnemonic.Count == 1
};
}
}
}
catch
{
}
return new LndSeedBackupViewModel();
}
private class LndSeedFile
{
public string wallet_password { get; set; }
public List<string> cipher_seed_mnemonic { get; set; }
}
}
}

View File

@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
namespace BTCPayServer.Models.ServerViewModels
{
public class LndGrpcServicesViewModel
public class LndServicesViewModel
{
public string Host { get; set; }
public bool SSL { get; set; }

View File

@ -5,12 +5,6 @@ namespace BTCPayServer.Models.ServerViewModels
{
public class LogsViewModel
{
public string StatusMessage
{
get; set;
}
public List<FileInfo> LogFiles { get; set; } = new List<FileInfo>();
public string Log { get; set; }
public int LogFileCount { get; set; }

View File

@ -12,6 +12,5 @@ namespace BTCPayServer.Models.ServerViewModels
public string Email { get; set; }
[Display(Name = "Is admin")]
public bool IsAdmin { get; set; }
public string StatusMessage { get; set; }
}
}

View File

@ -17,7 +17,6 @@ namespace BTCPayServer.Models.ServerViewModels
public int Skip { get; set; }
public int Count { get; set; }
public int Total { get; set; }
public string StatusMessage { get; set; }
public List<UserViewModel> Users { get; set; } = new List<UserViewModel>();
}

View File

@ -10,32 +10,6 @@ namespace BTCPayServer.Models
{
}
public StatusMessageModel(string s)
{
if (string.IsNullOrEmpty(s))
return;
try
{
if (s.StartsWith("{", StringComparison.InvariantCultureIgnoreCase) &&
s.EndsWith("}", StringComparison.InvariantCultureIgnoreCase))
{
var model = JObject.Parse(s).ToObject<StatusMessageModel>();
Html = model.Html;
Message = model.Message;
Severity = model.Severity;
AllowDismiss = model.AllowDismiss;
}
else
{
ParseNonJsonStatus(s);
}
}
catch (Exception)
{
ParseNonJsonStatus(s);
}
}
public string Message { get; set; }
public string Html { get; set; }
public StatusSeverity Severity { get; set; }
@ -60,11 +34,6 @@ namespace BTCPayServer.Models
}
}
}
public override string ToString()
{
return JObject.FromObject(this).ToString(Formatting.None);
}
private void ParseNonJsonStatus(string s)
{

View File

@ -27,10 +27,8 @@ namespace BTCPayServer.Models.StoreViewModels
public string DefaultLang { get; set; }
[Display(Name = "Link to a custom CSS stylesheet")]
[Uri]
public string CustomCSS { get; set; }
[Display(Name = "Link to a custom logo")]
[Uri]
public string CustomLogo { get; set; }
[Display(Name = "Custom HTML title to display on Checkout page")]
@ -42,6 +40,10 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Show recommended fee")]
public bool ShowRecommendedFee { get; set; }
[Display(Name = "Recommended fee confirmation target blocks")]
[Range(1, double.PositiveInfinity)]
public int RecommendedFeeBlockTarget { get; set; }
[Display(Name = "Do not propose on chain payment if the value of the invoice is below...")]
[MaxLength(20)]
public string OnChainMinValue { get; set; }

View File

@ -32,7 +32,6 @@ namespace BTCPayServer.Models.StoreViewModels
public bool Confirmation { get; set; }
public bool Enabled { get; set; } = true;
public string StatusMessage { get; internal set; }
public KeyPath RootKeyPath { get; set; }
[Display(Name = "Coldcard Wallet File")]

View File

@ -21,7 +21,6 @@ namespace BTCPayServer.Models.StoreViewModels
get;
set;
}
public string StatusMessage { get; set; }
public string InternalLightningNode { get; internal set; }
public bool SkipPortTest { get; set; }
public bool Enabled { get; set; } = true;

View File

@ -53,11 +53,6 @@ namespace BTCPayServer.Models.StoreViewModels
{
get; set;
}
public string StatusMessage
{
get;
set;
}
[Display(Name = "API Key")]
public string ApiKey { get; set; }

View File

@ -27,7 +27,5 @@ namespace BTCPayServer.Models.StoreViewModels
public decimal AmountMarkupPercentage { get; set; } = new decimal(2);
public bool Enabled { get; set; }
public string StatusMessage { get; set; }
}
}

View File

@ -24,7 +24,5 @@ namespace BTCPayServer.Models.StoreViewModels
new SelectListItem { Value = "popup", Text = "Open in a popup" },
new SelectListItem { Value = "inline", Text = "Embed inside Checkout UI " },
};
public string StatusMessage { get; set; }
}
}

View File

@ -10,6 +10,5 @@ namespace BTCPayServer.Models.WalletViewModels
public string WebsocketPath { get; set; }
public string PSBT { get; set; }
public string HintChange { get; set; }
public string SuccessPath { get; set; }
}
}

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
using NBitcoin;
using NBXplorer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Bitcoin
@ -22,7 +23,8 @@ namespace BTCPayServer.Payments.Bitcoin
return NextNetworkFee.ToDecimal(MoneyUnit.BTC);
}
public decimal GetFeeRate() {
public decimal GetFeeRate()
{
return FeeRate.SatoshiPerByte;
}
@ -32,6 +34,21 @@ namespace BTCPayServer.Payments.Bitcoin
}
public Data.NetworkFeeMode NetworkFeeMode { get; set; }
FeeRate _NetworkFeeRate;
[JsonConverter(typeof(FeeRateJsonConverter))]
public FeeRate NetworkFeeRate
{
get
{
// Some old invoices don't have this field set, so we fallback on FeeRate
return _NetworkFeeRate ?? FeeRate;
}
set
{
_NetworkFeeRate = value;
}
}
// Those properties are JsonIgnore because their data is inside CryptoData class for legacy reason
[JsonIgnore]
public FeeRate FeeRate { get; set; }

View File

@ -34,6 +34,7 @@ namespace BTCPayServer.Payments.Bitcoin
class Prepare
{
public Task<FeeRate> GetFeeRate;
public Task<FeeRate> GetNetworkFeeRate;
public Task<BitcoinAddress> ReserveAddress;
}
@ -101,9 +102,12 @@ namespace BTCPayServer.Payments.Bitcoin
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network)
{
var storeBlob = store.GetStoreBlob();
return new Prepare()
{
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(storeBlob.RecommendedFeeBlockTarget),
GetNetworkFeeRate = storeBlob.NetworkFeeMode == NetworkFeeMode.Never ? null
: _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
ReserveAddress = _WalletProvider.GetWallet(network)
.ReserveAddressAsync(supportedPaymentMethod.AccountDerivation)
};
@ -125,12 +129,17 @@ namespace BTCPayServer.Payments.Bitcoin
switch (onchainMethod.NetworkFeeMode)
{
case NetworkFeeMode.Always:
onchainMethod.NextNetworkFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes
onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
onchainMethod.NextNetworkFee = onchainMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes
break;
case NetworkFeeMode.Never:
case NetworkFeeMode.MultiplePaymentsOnly:
onchainMethod.NetworkFeeRate = FeeRate.Zero;
onchainMethod.NextNetworkFee = Money.Zero;
break;
case NetworkFeeMode.MultiplePaymentsOnly:
onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
onchainMethod.NextNetworkFee = Money.Zero;
break;
}
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
return onchainMethod;

View File

@ -185,6 +185,7 @@ namespace BTCPayServer.Payments.Lightning
model.BtcPaid = Money.Parse(model.BtcPaid).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
model.NetworkFee = new Money(model.NetworkFee, MoneyUnit.BTC).ToUnit(MoneyUnit.Satoshi);
model.OrderAmount = Money.Parse(model.OrderAmount).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
model.RateBaseAmount = Money.FromUnit(1, MoneyUnit.BTC).Satoshi.ToString(CultureInfo.InvariantCulture);
}
}
public override string GetCryptoImage(PaymentMethodId paymentMethodId)

View File

@ -54,6 +54,7 @@ namespace BTCPayServer.Payments
throw new ArgumentNullException(nameof(txId));
if (network?.BlockExplorerLink == null)
return null;
txId = txId.Split('-').First();
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
}
public override string InvoiceViewPaymentPartialName { get; } = "ViewBitcoinLikePaymentData";

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