2021-09-07 05:44:11 +02:00
#nullable enable
2020-06-29 04:44:35 +02:00
using System ;
2017-09-15 18:15:17 +02:00
using System.Collections.Generic ;
2022-06-23 06:41:52 +02:00
using System.ComponentModel.DataAnnotations ;
2021-09-09 13:31:35 +02:00
using System.Diagnostics.CodeAnalysis ;
2019-06-25 20:41:32 +02:00
using System.Globalization ;
2018-11-07 14:29:35 +01:00
using System.IO ;
2017-09-15 18:15:17 +02:00
using System.Linq ;
2017-09-27 07:18:09 +02:00
using System.Net ;
2018-04-14 15:35:52 +02:00
using System.Net.Http ;
2017-09-15 18:15:17 +02:00
using System.Threading.Tasks ;
2022-02-21 15:46:43 +01:00
using BTCPayServer.Abstractions.Constants ;
2022-11-14 14:29:23 +01:00
using BTCPayServer.Abstractions.Contracts ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Extensions ;
using BTCPayServer.Abstractions.Models ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Configuration ;
using BTCPayServer.Data ;
using BTCPayServer.HostedServices ;
using BTCPayServer.Logging ;
using BTCPayServer.Models.ServerViewModels ;
2024-02-23 09:51:41 +01:00
using BTCPayServer.Models.StoreViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services ;
using BTCPayServer.Services.Apps ;
using BTCPayServer.Services.Mails ;
using BTCPayServer.Services.Stores ;
2019-04-22 09:41:20 +02:00
using BTCPayServer.Storage.Services ;
using BTCPayServer.Storage.Services.Providers ;
2020-06-28 10:55:27 +02:00
using Microsoft.AspNetCore.Authorization ;
2023-06-30 08:08:23 +02:00
using Microsoft.AspNetCore.Cors ;
2020-06-28 10:55:27 +02:00
using Microsoft.AspNetCore.Identity ;
using Microsoft.AspNetCore.Mvc ;
2019-04-12 07:13:14 +02:00
using Microsoft.AspNetCore.Mvc.Rendering ;
2020-09-05 12:16:48 +02:00
using Microsoft.AspNetCore.Routing ;
2023-01-19 06:27:33 +01:00
using Microsoft.Extensions.Hosting ;
2024-10-17 15:51:40 +02:00
using Microsoft.Extensions.Localization ;
2020-06-28 10:55:27 +02:00
using Microsoft.Extensions.Logging ;
2021-01-02 13:44:28 +01:00
using Microsoft.Extensions.Options ;
2021-12-15 13:30:46 +01:00
using MimeKit ;
2020-06-28 10:55:27 +02:00
using NBitcoin ;
using NBitcoin.DataEncoders ;
using Renci.SshNet ;
2020-11-17 13:46:23 +01:00
using AuthenticationSchemes = BTCPayServer . Abstractions . Constants . AuthenticationSchemes ;
2017-09-15 18:15:17 +02:00
namespace BTCPayServer.Controllers
{
2024-11-26 06:17:40 +01:00
[ Authorize ( Policy = Client . Policies . CanModifyServerSettings ,
2020-11-17 13:46:23 +01:00
AuthenticationSchemes = AuthenticationSchemes . Cookie ) ]
2022-01-07 04:32:00 +01:00
public partial class UIServerController : Controller
2017-10-27 10:53:04 +02:00
{
2020-06-29 05:07:48 +02:00
private readonly UserManager < ApplicationUser > _UserManager ;
2021-03-14 20:24:32 +01:00
private readonly UserService _userService ;
2020-06-29 05:07:48 +02:00
readonly SettingsRepository _SettingsRepository ;
2022-05-24 06:18:16 +02:00
readonly PoliciesSettings _policiesSettings ;
2018-08-03 05:14:09 +02:00
private readonly NBXplorerDashboard _dashBoard ;
2020-06-29 05:07:48 +02:00
private readonly StoreRepository _StoreRepository ;
readonly LightningConfigurationProvider _LnConfigProvider ;
2019-03-17 04:57:18 +01:00
private readonly TorServices _torServices ;
2020-06-29 05:07:48 +02:00
private readonly BTCPayServerOptions _Options ;
2019-07-09 11:20:38 +02:00
private readonly AppService _AppService ;
2019-08-27 16:30:25 +02:00
private readonly CheckConfigurationHostedService _sshState ;
2020-09-05 12:16:48 +02:00
private readonly EventAggregator _eventAggregator ;
2021-01-02 13:44:28 +01:00
private readonly IOptions < ExternalServicesOptions > _externalServiceOptions ;
2021-11-22 09:16:08 +01:00
private readonly Logs Logs ;
2019-04-22 09:41:20 +02:00
private readonly StoredFileRepository _StoredFileRepository ;
2022-11-14 14:29:23 +01:00
private readonly IFileService _fileService ;
2019-04-22 09:41:20 +02:00
private readonly IEnumerable < IStorageProviderService > _StorageProviderServices ;
2024-12-11 12:11:51 +01:00
private readonly CallbackGenerator _callbackGenerator ;
2024-05-09 02:18:02 +02:00
private readonly UriResolver _uriResolver ;
2022-05-27 06:36:47 +02:00
private readonly EmailSenderFactory _emailSenderFactory ;
2023-11-29 10:51:40 +01:00
private readonly TransactionLinkProviders _transactionLinkProviders ;
2024-07-24 13:16:20 +02:00
private readonly LocalizerService _localizer ;
2024-10-17 15:51:40 +02:00
public IStringLocalizer StringLocalizer { get ; }
2017-09-15 18:15:17 +02:00
2022-01-07 04:32:00 +01:00
public UIServerController (
2021-03-14 20:24:32 +01:00
UserManager < ApplicationUser > userManager ,
UserService userService ,
2019-04-22 09:41:20 +02:00
StoredFileRepository storedFileRepository ,
2022-11-14 14:29:23 +01:00
IFileService fileService ,
2019-04-22 09:41:20 +02:00
IEnumerable < IStorageProviderService > storageProviderServices ,
2019-04-12 07:13:14 +02:00
BTCPayServerOptions options ,
2018-07-20 08:24:19 +02:00
SettingsRepository settingsRepository ,
2022-05-24 06:18:16 +02:00
PoliciesSettings policiesSettings ,
2018-08-03 05:14:09 +02:00
NBXplorerDashboard dashBoard ,
2018-12-12 10:19:13 +01:00
IHttpClientFactory httpClientFactory ,
2018-07-22 11:38:14 +02:00
LightningConfigurationProvider lnConfigProvider ,
2019-03-17 04:57:18 +01:00
TorServices torServices ,
2019-04-12 07:13:14 +02:00
StoreRepository storeRepository ,
2019-08-27 16:30:25 +02:00
AppService appService ,
2020-09-05 12:16:48 +02:00
CheckConfigurationHostedService sshState ,
EventAggregator eventAggregator ,
2021-11-22 09:16:08 +01:00
IOptions < ExternalServicesOptions > externalServiceOptions ,
2022-05-27 06:36:47 +02:00
Logs logs ,
2024-12-11 12:11:51 +01:00
CallbackGenerator callbackGenerator ,
2024-05-09 02:18:02 +02:00
UriResolver uriResolver ,
2023-01-19 06:27:33 +01:00
EmailSenderFactory emailSenderFactory ,
2023-01-21 19:08:12 +01:00
IHostApplicationLifetime applicationLifetime ,
2023-11-29 10:51:40 +01:00
IHtmlHelper html ,
2024-07-24 13:16:20 +02:00
TransactionLinkProviders transactionLinkProviders ,
2024-07-25 15:46:02 +02:00
LocalizerService localizer ,
2024-10-17 15:51:40 +02:00
IStringLocalizer stringLocalizer ,
2024-07-25 15:46:02 +02:00
BTCPayServerEnvironment environment
2022-05-27 06:36:47 +02:00
)
2017-10-27 10:53:04 +02:00
{
2022-05-24 06:18:16 +02:00
_policiesSettings = policiesSettings ;
2018-07-22 11:38:14 +02:00
_Options = options ;
2019-04-22 09:41:20 +02:00
_StoredFileRepository = storedFileRepository ;
2022-11-14 14:29:23 +01:00
_fileService = fileService ;
2019-04-22 09:41:20 +02:00
_StorageProviderServices = storageProviderServices ;
2017-10-27 10:53:04 +02:00
_UserManager = userManager ;
2021-03-14 20:24:32 +01:00
_userService = userService ;
2017-10-27 10:53:04 +02:00
_SettingsRepository = settingsRepository ;
2018-08-03 05:14:09 +02:00
_dashBoard = dashBoard ;
2018-12-12 10:19:13 +01:00
HttpClientFactory = httpClientFactory ;
2018-07-20 08:24:19 +02:00
_StoreRepository = storeRepository ;
2018-07-22 11:38:14 +02:00
_LnConfigProvider = lnConfigProvider ;
2019-03-17 04:57:18 +01:00
_torServices = torServices ;
2019-07-09 11:20:38 +02:00
_AppService = appService ;
2019-08-27 16:30:25 +02:00
_sshState = sshState ;
2020-09-05 12:16:48 +02:00
_eventAggregator = eventAggregator ;
2021-01-02 13:44:28 +01:00
_externalServiceOptions = externalServiceOptions ;
2021-11-22 09:16:08 +01:00
Logs = logs ;
2024-12-11 12:11:51 +01:00
_callbackGenerator = callbackGenerator ;
2024-05-09 02:18:02 +02:00
_uriResolver = uriResolver ;
2022-05-27 06:36:47 +02:00
_emailSenderFactory = emailSenderFactory ;
2023-01-19 06:27:33 +01:00
ApplicationLifetime = applicationLifetime ;
2023-01-21 19:08:12 +01:00
Html = html ;
2023-11-29 10:51:40 +01:00
_transactionLinkProviders = transactionLinkProviders ;
2024-07-24 13:16:20 +02:00
_localizer = localizer ;
2024-07-25 15:46:02 +02:00
Environment = environment ;
2024-10-17 15:51:40 +02:00
StringLocalizer = stringLocalizer ;
2018-03-22 11:55:14 +01:00
}
2024-02-23 09:51:41 +01:00
[HttpGet("server/stores")]
public async Task < IActionResult > ListStores ( )
{
var stores = await _StoreRepository . GetStores ( ) ;
var vm = new ListStoresViewModel
{
Stores = stores
. Select ( s = > new ListStoresViewModel . StoreViewModel
{
StoreId = s . Id ,
StoreName = s . StoreName ,
Archived = s . Archived ,
Users = s . UserStores
} )
. OrderBy ( s = > ! s . Archived )
. ToList ( )
} ;
return View ( vm ) ;
}
2024-02-21 14:43:44 +01:00
[HttpGet("server/maintenance")]
2018-07-24 10:04:57 +02:00
public IActionResult Maintenance ( )
{
2024-02-21 14:43:44 +01:00
var vm = new MaintenanceViewModel
{
CanUseSSH = _sshState . CanUseSSH ,
DNSDomain = Request . Host . Host
} ;
2019-10-31 07:19:38 +01:00
if ( ! vm . CanUseSSH )
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "Maintenance feature requires access to SSH properly configured in BTCPay Server configuration." ] . Value ;
2018-07-24 10:04:57 +02:00
if ( IPAddress . TryParse ( vm . DNSDomain , out var unused ) )
vm . DNSDomain = null ;
2024-02-21 14:43:44 +01:00
2018-07-24 10:04:57 +02:00
return View ( vm ) ;
}
2019-02-10 19:12:02 +01:00
2024-02-21 14:43:44 +01:00
[HttpPost("server/maintenance")]
2018-07-24 10:04:57 +02:00
public async Task < IActionResult > Maintenance ( MaintenanceViewModel vm , string command )
{
2019-08-27 16:30:25 +02:00
vm . CanUseSSH = _sshState . CanUseSSH ;
2023-01-19 06:27:33 +01:00
if ( command ! = "soft-restart" & & ! vm . CanUseSSH )
2020-10-15 14:28:09 +02:00
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "Maintenance feature requires access to SSH properly configured in BTCPay Server configuration." ] . Value ;
2020-10-15 14:28:09 +02:00
return View ( vm ) ;
}
2018-07-24 10:04:57 +02:00
if ( ! ModelState . IsValid )
return View ( vm ) ;
2024-02-21 20:54:39 +01:00
2018-07-24 10:04:57 +02:00
if ( command = = "changedomain" )
{
if ( string . IsNullOrWhiteSpace ( vm . DNSDomain ) )
{
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"Required field" ) ;
return View ( vm ) ;
}
vm . DNSDomain = vm . DNSDomain . Trim ( ) . ToLowerInvariant ( ) ;
2018-08-13 09:48:10 +02:00
if ( vm . DNSDomain . Equals ( this . Request . Host . Host , StringComparison . OrdinalIgnoreCase ) )
return View ( vm ) ;
2018-07-24 10:04:57 +02:00
if ( IPAddress . TryParse ( vm . DNSDomain , out var unused ) )
{
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"This should be a domain name" ) ;
return View ( vm ) ;
}
if ( vm . DNSDomain . Equals ( this . Request . Host . Host , StringComparison . InvariantCultureIgnoreCase ) )
{
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"The server is already set to use this domain" ) ;
return View ( vm ) ;
}
var builder = new UriBuilder ( ) ;
2024-01-18 01:47:39 +01:00
try
2018-07-24 10:04:57 +02:00
{
2024-01-18 01:47:39 +01:00
builder . Scheme = this . Request . Scheme ;
builder . Host = vm . DNSDomain ;
var addresses1 = GetAddressAsync ( this . Request . Host . Host ) ;
var addresses2 = GetAddressAsync ( vm . DNSDomain ) ;
await Task . WhenAll ( addresses1 , addresses2 ) ;
var addressesSet = addresses1 . GetAwaiter ( ) . GetResult ( ) . Select ( c = > c . ToString ( ) ) . ToHashSet ( ) ;
var hasCommonAddress = addresses2 . GetAwaiter ( ) . GetResult ( ) . Select ( c = > c . ToString ( ) ) . Any ( s = > addressesSet . Contains ( s ) ) ;
if ( ! hasCommonAddress )
2018-07-24 10:04:57 +02:00
{
2024-01-18 01:47:39 +01:00
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"Invalid host ({vm.DNSDomain} is not pointing to this BTCPay instance)" ) ;
2018-07-24 10:04:57 +02:00
return View ( vm ) ;
}
}
2024-01-18 01:47:39 +01:00
catch ( Exception ex )
{
var messages = new List < object > ( ) ;
messages . Add ( ex . Message ) ;
if ( ex . InnerException ! = null )
messages . Add ( ex . InnerException . Message ) ;
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"Invalid domain ({string.Join(" , ", messages.ToArray())})" ) ;
return View ( vm ) ;
}
2018-07-24 10:04:57 +02:00
2019-08-27 16:30:25 +02:00
var error = await RunSSH ( vm , $"changedomain.sh {vm.DNSDomain}" ) ;
2018-07-24 10:04:57 +02:00
if ( error ! = null )
return error ;
builder . Path = null ;
builder . Query = null ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Domain name changing... the server will restart, please use \"{0}\" (this page won't reload automatically)" , builder . Uri . AbsoluteUri ] . Value ;
2018-07-24 10:04:57 +02:00
}
2018-07-24 15:10:37 +02:00
else if ( command = = "update" )
{
2019-08-27 16:30:25 +02:00
var error = await RunSSH ( vm , $"btcpay-update.sh" ) ;
2018-07-24 15:10:37 +02:00
if ( error ! = null )
return error ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "The server might restart soon if an update is available... (this page won't reload automatically)" ] . Value ;
2018-07-24 15:10:37 +02:00
}
2019-04-01 10:10:05 +02:00
else if ( command = = "clean" )
{
2019-08-27 16:30:25 +02:00
var error = await RunSSH ( vm , $"btcpay-clean.sh" ) ;
2019-04-01 10:10:05 +02:00
if ( error ! = null )
return error ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "The old docker images will be cleaned soon..." ] . Value ;
2019-04-01 10:10:05 +02:00
}
2020-10-15 14:28:09 +02:00
else if ( command = = "restart" )
{
var error = await RunSSH ( vm , $"btcpay-restart.sh" ) ;
if ( error ! = null )
return error ;
2023-01-19 12:22:58 +01:00
Logs . PayServer . LogInformation ( "A hard restart has been requested" ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "BTCPay will restart momentarily." ] . Value ;
2020-10-15 14:28:09 +02:00
}
2023-01-19 06:27:33 +01:00
else if ( command = = "soft-restart" )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "BTCPay will restart momentarily." ] . Value ;
2023-01-19 12:22:58 +01:00
Logs . PayServer . LogInformation ( "A soft restart has been requested" ) ;
_ = Task . Delay ( 3000 ) . ContinueWith ( ( t ) = > ApplicationLifetime . StopApplication ( ) ) ;
2023-01-19 06:27:33 +01:00
}
2018-07-24 10:04:57 +02:00
else
{
return NotFound ( ) ;
}
return RedirectToAction ( nameof ( Maintenance ) ) ;
}
2019-01-30 04:52:34 +01:00
private Task < IPAddress [ ] > GetAddressAsync ( string domainOrIP )
{
if ( IPAddress . TryParse ( domainOrIP , out var ip ) )
return Task . FromResult ( new [ ] { ip } ) ;
return Dns . GetHostAddressesAsync ( domainOrIP ) ;
}
2018-07-24 10:04:57 +02:00
public static string RunId = Encoders . Hex . EncodeData ( NBitcoin . RandomUtils . GetBytes ( 32 ) ) ;
[HttpGet]
[Route("runid")]
[AllowAnonymous]
2021-09-07 05:44:11 +02:00
public IActionResult SeeRunId ( string? expected = null )
2018-07-24 10:04:57 +02:00
{
if ( expected = = RunId )
return Ok ( ) ;
return BadRequest ( ) ;
}
2021-09-07 05:44:11 +02:00
private async Task < IActionResult ? > RunSSH ( MaintenanceViewModel vm , string command )
2018-07-24 10:04:57 +02:00
{
2021-09-07 05:44:11 +02:00
SshClient ? sshClient = null ;
2018-08-12 16:23:26 +02:00
2018-07-24 10:04:57 +02:00
try
{
2019-08-27 16:30:25 +02:00
sshClient = await _Options . SSHSettings . ConnectAsync ( ) ;
2018-07-24 10:04:57 +02:00
}
catch ( Exception ex )
{
var message = ex . Message ;
if ( ex is AggregateException aggrEx & & aggrEx . InnerException ? . Message ! = null )
{
message = aggrEx . InnerException . Message ;
}
2019-08-27 16:30:25 +02:00
ModelState . AddModelError ( string . Empty , $"Connection problem ({message})" ) ;
2018-07-24 10:04:57 +02:00
return View ( vm ) ;
}
2019-08-27 16:30:25 +02:00
_ = RunSSHCore ( sshClient , $". /etc/profile.d/btcpay-env.sh && nohup {command} > /dev/null 2>&1 & disown" ) ;
return null ;
}
2018-07-24 10:04:57 +02:00
2021-11-22 09:16:08 +01:00
private async Task RunSSHCore ( SshClient sshClient , string ssh )
2019-08-27 16:30:25 +02:00
{
try
{
Logs . PayServer . LogInformation ( "Running SSH command: " + ssh ) ;
var result = await sshClient . RunBash ( ssh , TimeSpan . FromMinutes ( 1.0 ) ) ;
Logs . PayServer . LogInformation ( $"SSH command executed with exit status {result.ExitStatus}. Output: {result.Output}" ) ;
}
catch ( Exception ex )
{
Logs . PayServer . LogWarning ( "Error while executing SSH command: " + ex . Message ) ;
}
finally
2018-07-24 10:04:57 +02:00
{
sshClient . Dispose ( ) ;
2019-08-27 16:30:25 +02:00
}
2018-07-24 10:04:57 +02:00
}
2021-04-17 06:29:50 +02:00
2018-12-12 10:19:13 +01:00
public IHttpClientFactory HttpClientFactory { get ; }
2023-01-19 06:27:33 +01:00
public IHostApplicationLifetime ApplicationLifetime { get ; }
2023-01-21 19:08:12 +01:00
public IHtmlHelper Html { get ; }
2024-07-25 15:46:02 +02:00
public BTCPayServerEnvironment Environment { get ; }
2017-12-04 06:39:02 +01:00
2017-10-27 10:53:04 +02:00
[Route("server/policies")]
public async Task < IActionResult > Policies ( )
2024-07-24 13:16:20 +02:00
{
await UpdateViewBag ( ) ;
return View ( _policiesSettings ) ;
}
private async Task UpdateViewBag ( )
2017-10-27 10:53:04 +02:00
{
2020-10-05 11:26:11 +02:00
ViewBag . UpdateUrlPresent = _Options . UpdateUrl ! = null ;
2024-02-21 14:43:44 +01:00
ViewBag . AppsList = await GetAppSelectList ( ) ;
2024-07-24 13:16:20 +02:00
ViewBag . LangDictionaries = await GetLangDictionariesSelectList ( ) ;
2017-10-27 10:53:04 +02:00
}
2019-06-25 20:41:32 +02:00
2022-06-15 14:06:16 +02:00
[HttpPost("server/policies")]
2021-04-17 06:29:50 +02:00
public async Task < IActionResult > Policies ( [ FromServices ] BTCPayNetworkProvider btcPayNetworkProvider , PoliciesSettings settings , string command = "" )
2018-04-19 18:39:51 +02:00
{
2024-07-24 13:16:20 +02:00
await UpdateViewBag ( ) ;
2023-01-06 14:18:07 +01:00
2019-06-25 20:41:32 +02:00
if ( command = = "add-domain" )
2019-04-12 07:13:14 +02:00
{
2019-06-25 20:41:32 +02:00
ModelState . Clear ( ) ;
settings . DomainToAppMapping . Add ( new PoliciesSettings . DomainToAppMappingItem ( ) ) ;
return View ( settings ) ;
}
if ( command . StartsWith ( "remove-domain" , StringComparison . InvariantCultureIgnoreCase ) )
{
ModelState . Clear ( ) ;
2019-07-19 09:50:17 +02:00
var index = int . Parse ( command . Substring ( command . IndexOf ( ":" , StringComparison . InvariantCultureIgnoreCase ) + 1 ) , CultureInfo . InvariantCulture ) ;
2019-06-25 20:41:32 +02:00
settings . DomainToAppMapping . RemoveAt ( index ) ;
return View ( settings ) ;
}
2023-11-29 10:51:40 +01:00
settings . BlockExplorerLinks = settings . BlockExplorerLinks
2024-10-04 09:58:13 +02:00
. Where ( tuple = > _transactionLinkProviders . GetDefaultBlockExplorerLink ( tuple . PaymentMethodId ) ! = tuple . Link )
2023-11-29 10:51:40 +01:00
. Where ( tuple = > tuple . Link is not null )
. ToList ( ) ;
2021-04-17 06:29:50 +02:00
2019-06-25 20:41:32 +02:00
if ( ! ModelState . IsValid )
{
return View ( settings ) ;
}
var appIdsToFetch = settings . DomainToAppMapping . Select ( item = > item . AppId ) . ToList ( ) ;
if ( ! string . IsNullOrEmpty ( settings . RootAppId ) )
{
appIdsToFetch . Add ( settings . RootAppId ) ;
2019-04-12 07:13:14 +02:00
}
else
{
settings . RootAppType = null ;
}
2019-06-25 20:41:32 +02:00
if ( appIdsToFetch . Any ( ) )
{
2019-07-09 11:20:38 +02:00
var apps = ( await _AppService . GetApps ( appIdsToFetch . ToArray ( ) ) )
2023-03-17 03:56:32 +01:00
. ToDictionary ( data = > data . Id , data = > data . AppType ) ;
2019-09-20 11:51:14 +02:00
;
2019-07-09 11:20:38 +02:00
if ( ! string . IsNullOrEmpty ( settings . RootAppId ) )
2019-06-25 20:41:32 +02:00
{
2019-07-09 11:20:38 +02:00
settings . RootAppType = apps [ settings . RootAppId ] ;
}
2019-06-25 20:41:32 +02:00
2019-07-09 11:20:38 +02:00
foreach ( var domainToAppMappingItem in settings . DomainToAppMapping )
{
domainToAppMappingItem . AppType = apps [ domainToAppMappingItem . AppId ] ;
2019-06-25 20:41:32 +02:00
}
}
2024-07-24 13:16:20 +02:00
2019-06-25 20:41:32 +02:00
2018-04-19 18:39:51 +02:00
await _SettingsRepository . UpdateSetting ( settings ) ;
2023-11-29 10:51:40 +01:00
_ = _transactionLinkProviders . RefreshTransactionLinkTemplates ( ) ;
2024-07-24 13:16:20 +02:00
if ( _policiesSettings . LangDictionary ! = settings . LangDictionary )
await _localizer . Load ( ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Policies updated successfully" ] . Value ;
2019-04-12 07:13:14 +02:00
return RedirectToAction ( nameof ( Policies ) ) ;
2018-04-19 18:39:51 +02:00
}
2018-07-22 11:38:14 +02:00
[Route("server/services")]
2022-06-15 14:06:16 +02:00
public IActionResult Services ( )
2018-07-22 11:38:14 +02:00
{
2022-06-15 14:06:16 +02:00
var result = new ServicesViewModel { ExternalServices = _externalServiceOptions . Value . ExternalServices . ToList ( ) } ;
2019-11-03 20:18:09 +01:00
// other services
2021-01-02 13:44:28 +01:00
foreach ( var externalService in _externalServiceOptions . Value . OtherExternalServices )
2018-07-22 11:38:14 +02:00
{
2019-03-01 05:20:21 +01:00
result . OtherExternalServices . Add ( new ServicesViewModel . OtherExternalService ( )
2018-12-07 10:42:39 +01:00
{
Name = externalService . Key ,
2022-06-15 14:06:16 +02:00
Link = Request . GetAbsoluteUriNoPathBase ( externalService . Value ) . AbsoluteUri
2018-12-07 10:42:39 +01:00
} ) ;
}
2022-05-24 06:18:16 +02:00
if ( CanShowSSHService ( ) )
2018-12-07 10:42:39 +01:00
{
2019-03-01 05:20:21 +01:00
result . OtherExternalServices . Add ( new ServicesViewModel . OtherExternalService ( )
2018-12-07 10:42:39 +01:00
{
Name = "SSH" ,
2022-06-15 14:06:16 +02:00
Link = Url . Action ( nameof ( SSHService ) )
2018-12-07 10:42:39 +01:00
} ) ;
}
2019-07-24 10:59:30 +02:00
result . OtherExternalServices . Add ( new ServicesViewModel . OtherExternalService ( )
{
Name = "Dynamic DNS" ,
2022-06-15 14:06:16 +02:00
Link = Url . Action ( nameof ( DynamicDnsServices ) )
2019-07-24 10:59:30 +02:00
} ) ;
2019-04-12 07:13:14 +02:00
foreach ( var torService in _torServices . Services )
2019-03-17 12:49:26 +01:00
{
if ( torService . VirtualPort = = 80 )
{
2019-03-18 08:45:46 +01:00
result . TorHttpServices . Add ( new ServicesViewModel . OtherExternalService ( )
2019-03-17 12:49:26 +01:00
{
Name = torService . Name ,
Link = $"http://{torService.OnionHost}"
} ) ;
}
2019-05-07 06:58:55 +02:00
else if ( TryParseAsExternalService ( torService , out var externalService ) )
{
result . ExternalServices . Add ( externalService ) ;
}
2019-03-18 08:45:46 +01:00
else
{
2019-03-18 09:13:02 +01:00
result . TorOtherServices . Add ( new ServicesViewModel . OtherExternalService ( )
2019-03-18 08:45:46 +01:00
{
Name = torService . Name ,
Link = $"{torService.OnionHost}:{torService.VirtualPort}"
} ) ;
}
2019-03-17 12:49:26 +01:00
}
2023-01-06 14:18:07 +01:00
2018-07-22 11:38:14 +02:00
return View ( result ) ;
}
2019-06-26 07:02:22 +02:00
private async Task < List < SelectListItem > > GetAppSelectList ( )
2019-06-25 20:41:32 +02:00
{
2023-03-17 03:56:32 +01:00
var types = _AppService . GetAvailableAppTypes ( ) ;
2019-08-03 13:49:50 +02:00
var apps = ( await _AppService . GetAllApps ( null , true ) )
2023-04-10 04:07:03 +02:00
. Select ( a = >
2023-03-17 03:56:32 +01:00
new SelectListItem ( $"{types[a.AppType]} - {a.AppName} - {a.StoreName}" , a . Id ) ) . ToList ( ) ;
2019-08-03 13:49:50 +02:00
apps . Insert ( 0 , new SelectListItem ( "(None)" , null ) ) ;
return apps ;
2019-06-25 20:41:32 +02:00
}
2024-07-24 13:16:20 +02:00
private async Task < List < SelectListItem > > GetLangDictionariesSelectList ( )
{
var dictionaries = await this . _localizer . GetDictionaries ( ) ;
return dictionaries . Select ( d = > new SelectListItem ( d . DictionaryName , d . DictionaryName ) ) . OrderBy ( d = > d . Value ) . ToList ( ) ;
}
2021-09-09 13:31:35 +02:00
private static bool TryParseAsExternalService ( TorService torService , [ MaybeNullWhen ( false ) ] out ExternalService externalService )
2019-05-07 06:58:55 +02:00
{
externalService = null ;
if ( torService . ServiceType = = TorServiceType . P2P )
{
externalService = new ExternalService ( )
{
CryptoCode = torService . Network . CryptoCode ,
DisplayName = "Full node P2P" ,
Type = ExternalServiceTypes . P2P ,
ConnectionString = new ExternalConnectionString ( new Uri ( $"bitcoin-p2p://{torService.OnionHost}:{torService.VirtualPort}" , UriKind . Absolute ) ) ,
ServiceName = torService . Name ,
} ;
}
2019-11-07 06:33:10 +01:00
if ( torService . ServiceType = = TorServiceType . RPC )
{
externalService = new ExternalService ( )
{
CryptoCode = torService . Network . CryptoCode ,
DisplayName = "Full node RPC" ,
Type = ExternalServiceTypes . RPC ,
2019-11-07 09:08:24 +01:00
ConnectionString = new ExternalConnectionString ( new Uri ( $"btcrpc://btcrpc:btcpayserver4ever@{torService.OnionHost}:{torService.VirtualPort}?label=BTCPayNode" , UriKind . Absolute ) ) ,
2019-11-07 06:33:10 +01:00
ServiceName = torService . Name
} ;
}
2019-05-07 06:58:55 +02:00
return externalService ! = null ;
}
2021-09-07 05:44:11 +02:00
private ExternalService ? GetService ( string serviceName , string cryptoCode )
2019-05-07 06:58:55 +02:00
{
2021-01-02 13:44:28 +01:00
var result = _externalServiceOptions . Value . ExternalServices . GetService ( serviceName , cryptoCode ) ;
2019-05-07 06:58:55 +02:00
if ( result ! = null )
return result ;
2019-11-07 07:41:39 +01:00
foreach ( var torService in _torServices . Services )
{
if ( TryParseAsExternalService ( torService , out var torExternalService ) & &
2019-11-07 07:48:56 +01:00
torExternalService . ServiceName = = serviceName )
2019-11-07 07:41:39 +01:00
return torExternalService ;
}
return null ;
2019-05-07 06:58:55 +02:00
}
2020-01-21 10:27:10 +01:00
[Route("server/services/{serviceName}/{cryptoCode?}")]
2021-08-07 14:52:49 +02:00
public async Task < IActionResult > Service ( string serviceName , string cryptoCode , bool showQR = false , ulong? nonce = null )
2018-12-20 14:40:32 +01:00
{
2020-08-04 07:16:25 +02:00
var service = GetService ( serviceName , cryptoCode ) ;
if ( service = = null )
return NotFound ( ) ;
if ( ! string . IsNullOrEmpty ( cryptoCode ) & & ! _dashBoard . IsFullySynched ( cryptoCode , out _ ) & & service . Type ! = ExternalServiceTypes . RPC )
2018-12-20 14:40:32 +01:00
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "{0} is not fully synched" , cryptoCode ] . Value ;
2018-12-20 14:40:32 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
try
{
2020-06-28 10:55:27 +02:00
2019-05-07 06:58:55 +02:00
if ( service . Type = = ExternalServiceTypes . P2P )
{
return View ( "P2PService" , new LightningWalletServices ( )
{
ShowQR = showQR ,
WalletName = service . ServiceName ,
2019-05-07 07:44:26 +02:00
ServiceLink = service . ConnectionString . Server . AbsoluteUri . WithoutEndingSlash ( )
2019-05-07 06:58:55 +02:00
} ) ;
}
2019-11-05 05:04:35 +01:00
if ( service . Type = = ExternalServiceTypes . LNDSeedBackup )
{
2019-11-05 20:40:06 +01:00
var model = LndSeedBackupViewModel . Parse ( service . ConnectionString . CookieFilePath ) ;
2019-11-15 09:55:55 +01:00
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>."
} ) ;
}
2019-11-05 05:04:35 +01:00
return View ( "LndSeedBackup" , model ) ;
}
2019-11-07 06:33:10 +01:00
if ( service . Type = = ExternalServiceTypes . RPC )
{
return View ( "RPCService" , new LightningWalletServices ( )
{
ShowQR = showQR ,
WalletName = service . ServiceName ,
ServiceLink = service . ConnectionString . Server . AbsoluteUri . WithoutEndingSlash ( )
} ) ;
}
2019-06-10 11:16:12 +02:00
var connectionString = await service . ConnectionString . Expand ( this . Request . GetAbsoluteUriNoPathBase ( ) , service . Type , _Options . NetworkType ) ;
2019-03-01 05:20:21 +01:00
switch ( service . Type )
2018-12-20 14:40:32 +01:00
{
2019-03-01 05:20:21 +01:00
case ExternalServiceTypes . Charge :
return LightningChargeServices ( service , connectionString , showQR ) ;
case ExternalServiceTypes . RTL :
2020-05-19 21:58:07 +02:00
case ExternalServiceTypes . ThunderHub :
2019-03-01 05:20:21 +01:00
case ExternalServiceTypes . Spark :
2022-11-18 12:19:01 +01:00
case ExternalServiceTypes . Torq :
2019-03-01 05:20:21 +01:00
if ( connectionString . AccessKey = = null )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "The access key of the service is not set" ] . Value ;
2019-03-01 05:20:21 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
LightningWalletServices vm = new LightningWalletServices ( ) ;
vm . ShowQR = showQR ;
vm . WalletName = service . DisplayName ;
2020-05-19 21:58:07 +02:00
string tokenParam = "access-key" ;
if ( service . Type = = ExternalServiceTypes . ThunderHub )
tokenParam = "token" ;
vm . ServiceLink = $"{connectionString.Server}?{tokenParam}={connectionString.AccessKey}" ;
2019-03-01 05:20:21 +01:00
return View ( "LightningWalletServices" , vm ) ;
2020-01-23 14:20:37 +01:00
case ExternalServiceTypes . CLightningRest :
return LndServices ( service , connectionString , nonce , "CLightningRestServices" ) ;
2019-03-01 05:20:21 +01:00
case ExternalServiceTypes . LNDGRPC :
case ExternalServiceTypes . LNDRest :
return LndServices ( service , connectionString , nonce ) ;
2020-01-21 10:27:10 +01:00
case ExternalServiceTypes . Configurator :
return View ( "ConfiguratorService" ,
new LightningWalletServices ( )
{
ShowQR = showQR ,
WalletName = service . ServiceName ,
ServiceLink = $"{connectionString.Server}?password={connectionString.AccessKey}"
} ) ;
2019-03-01 05:20:21 +01:00
default :
throw new NotSupportedException ( service . Type . ToString ( ) ) ;
2019-02-28 14:20:14 +01:00
}
2018-12-12 10:19:13 +01:00
}
2019-02-10 19:12:02 +01:00
catch ( Exception ex )
2018-12-12 10:19:13 +01:00
{
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . ErrorMessage ] = ex . Message ;
2018-12-12 10:19:13 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
}
2021-09-07 04:55:53 +02:00
[HttpGet("server/services/{serviceName}/{cryptoCode}/removelndseed")]
2019-11-16 06:06:37 +01:00
public IActionResult RemoveLndSeed ( string serviceName , string cryptoCode )
{
2024-10-17 15:51:40 +02:00
return View ( "Confirm" , new ConfirmModel ( StringLocalizer [ "Delete LND seed" ] , StringLocalizer [ "This action will permanently delete your LND seed and password. You will not be able to recover them if you don't have a backup. Are you sure?" ] , StringLocalizer [ "Delete" ] ) ) ;
2019-11-16 06:06:37 +01:00
}
2021-09-07 04:55:53 +02:00
[HttpPost("server/services/{serviceName}/{cryptoCode}/removelndseed")]
2019-11-16 06:06:37 +01:00
public async Task < IActionResult > RemoveLndSeedPost ( string serviceName , string cryptoCode )
2019-11-08 00:06:16 +01:00
{
var service = GetService ( serviceName , cryptoCode ) ;
if ( service = = null )
return NotFound ( ) ;
var model = LndSeedBackupViewModel . Parse ( service . ConnectionString . CookieFilePath ) ;
if ( ! model . IsWalletUnlockPresent )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "File with wallet password and seed info not present" ] . Value ;
2019-11-08 00:06:16 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
2019-11-15 09:55:55 +01:00
if ( string . IsNullOrEmpty ( model . Seed ) )
2019-11-08 00:06:16 +01:00
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "Seed information was already removed" ] . Value ;
2019-11-08 00:06:16 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
if ( await model . RemoveSeedAndWrite ( service . ConnectionString . CookieFilePath ) )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Seed successfully removed" ] . Value ;
2019-11-08 00:06:16 +01:00
return RedirectToAction ( nameof ( Service ) , new { serviceName , cryptoCode } ) ;
}
else
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "Seed removal failed" ] . Value ;
2019-11-08 00:06:16 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
}
2019-03-01 05:20:21 +01:00
private IActionResult LightningChargeServices ( ExternalService service , ExternalConnectionString connectionString , bool showQR = false )
2019-02-28 14:20:14 +01:00
{
2019-03-01 05:20:21 +01:00
ChargeServiceViewModel vm = new ChargeServiceViewModel ( ) ;
vm . Uri = connectionString . Server . AbsoluteUri ;
vm . APIToken = connectionString . APIToken ;
2019-03-01 07:38:11 +01:00
var builder = new UriBuilder ( connectionString . Server ) ;
builder . UserName = "api-token" ;
builder . Password = vm . APIToken ;
vm . AuthenticatedUri = builder . ToString ( ) ;
2019-03-01 05:20:21 +01:00
return View ( nameof ( LightningChargeServices ) , vm ) ;
2019-02-28 14:20:14 +01:00
}
2021-08-07 14:52:49 +02:00
private IActionResult LndServices ( ExternalService service , ExternalConnectionString connectionString , ulong? nonce , string view = nameof ( LndServices ) )
2018-07-22 11:38:14 +02:00
{
2019-11-03 20:18:09 +01:00
var model = new LndServicesViewModel ( ) ;
2019-03-01 05:20:21 +01:00
if ( service . Type = = ExternalServiceTypes . LNDGRPC )
2018-12-07 11:31:07 +01:00
{
2019-03-01 05:20:21 +01:00
model . Host = $"{connectionString.Server.DnsSafeHost}:{connectionString.Server.Port}" ;
model . SSL = connectionString . Server . Scheme = = "https" ;
2018-12-07 11:31:07 +01:00
model . ConnectionType = "GRPC" ;
2018-12-20 06:16:23 +01:00
model . GRPCSSLCipherSuites = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256" ;
2018-12-07 11:31:07 +01:00
}
2020-01-23 14:20:37 +01:00
else if ( service . Type = = ExternalServiceTypes . LNDRest | | service . Type = = ExternalServiceTypes . CLightningRest )
2018-12-07 11:31:07 +01:00
{
2019-03-01 05:20:21 +01:00
model . Uri = connectionString . Server . AbsoluteUri ;
2018-12-07 11:31:07 +01:00
model . ConnectionType = "REST" ;
}
2018-07-22 11:38:14 +02:00
2019-03-01 05:20:21 +01:00
if ( connectionString . CertificateThumbprint ! = null )
2018-07-22 11:38:14 +02:00
{
2019-03-01 05:20:21 +01:00
model . CertificateThumbprint = connectionString . CertificateThumbprint ;
2018-07-22 11:38:14 +02:00
}
2019-03-01 05:20:21 +01:00
if ( connectionString . Macaroon ! = null )
2018-07-22 11:38:14 +02:00
{
2019-03-01 05:20:21 +01:00
model . Macaroon = Encoders . Hex . EncodeData ( connectionString . Macaroon ) ;
2018-07-22 11:38:14 +02:00
}
2019-03-01 05:20:21 +01:00
model . AdminMacaroon = connectionString . Macaroons ? . AdminMacaroon ? . Hex ;
model . InvoiceMacaroon = connectionString . Macaroons ? . InvoiceMacaroon ? . Hex ;
model . ReadonlyMacaroon = connectionString . Macaroons ? . ReadonlyMacaroon ? . Hex ;
2018-07-23 04:53:39 +02:00
if ( nonce ! = null )
2018-07-22 11:38:14 +02:00
{
2019-03-01 05:20:21 +01:00
var configKey = GetConfigKey ( "lnd" , service . ServiceName , service . CryptoCode , nonce . Value ) ;
2018-07-23 04:53:39 +02:00
var lnConfig = _LnConfigProvider . GetConfig ( configKey ) ;
2018-07-22 11:38:14 +02:00
if ( lnConfig ! = null )
{
2024-12-20 16:16:04 +01:00
model . QRCodeLink = Url . ActionAbsolute ( Request , nameof ( GetLNDConfig ) , new { configKey } ) . ToString ( ) ;
2018-07-22 14:28:21 +02:00
model . QRCode = $"config={model.QRCodeLink}" ;
2018-07-22 11:38:14 +02:00
}
}
2018-07-23 04:53:39 +02:00
2020-01-23 14:20:37 +01:00
return View ( view , model ) ;
2018-07-22 11:38:14 +02:00
}
2018-07-23 04:53:39 +02:00
2021-08-07 14:52:49 +02:00
private static ulong GetConfigKey ( string type , string serviceName , string cryptoCode , ulong nonce )
2018-07-23 04:53:39 +02:00
{
2021-08-07 14:52:49 +02:00
return ( ( ulong ) ( uint ) HashCode . Combine ( type , serviceName , cryptoCode , nonce ) | ( nonce & 0xffffffff00000000 UL ) ) ;
2018-07-23 04:53:39 +02:00
}
[Route("lnd-config/{configKey}/lnd.config")]
2018-07-22 11:43:11 +02:00
[AllowAnonymous]
2023-06-30 08:08:23 +02:00
[EnableCors(CorsPolicies.All)]
[IgnoreAntiforgeryToken]
2021-08-07 14:52:49 +02:00
public IActionResult GetLNDConfig ( ulong configKey )
2018-07-22 11:38:14 +02:00
{
2018-07-23 04:53:39 +02:00
var conf = _LnConfigProvider . GetConfig ( configKey ) ;
2018-07-22 11:38:14 +02:00
if ( conf = = null )
return NotFound ( ) ;
return Json ( conf ) ;
}
2019-03-01 05:20:21 +01:00
[Route("server/services/{serviceName}/{cryptoCode}")]
2018-07-22 11:38:14 +02:00
[HttpPost]
2019-03-01 05:20:21 +01:00
public async Task < IActionResult > ServicePost ( string serviceName , string cryptoCode )
2018-07-22 11:38:14 +02:00
{
2024-01-18 01:47:39 +01:00
if ( ! _dashBoard . IsFullySynched ( cryptoCode , out _ ) )
2019-03-01 05:20:21 +01:00
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "{0} is not fully synched" , cryptoCode ] . Value ;
2019-03-01 05:20:21 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
2019-05-07 06:58:55 +02:00
var service = GetService ( serviceName , cryptoCode ) ;
2019-03-01 05:20:21 +01:00
if ( service = = null )
2018-07-22 11:38:14 +02:00
return NotFound ( ) ;
2019-03-01 05:20:21 +01:00
2021-09-07 05:44:11 +02:00
ExternalConnectionString ? connectionString = null ;
2019-03-01 05:20:21 +01:00
try
{
2019-06-10 11:16:12 +02:00
connectionString = await service . ConnectionString . Expand ( this . Request . GetAbsoluteUriNoPathBase ( ) , service . Type , _Options . NetworkType ) ;
2019-03-01 05:20:21 +01:00
}
catch ( Exception ex )
{
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . ErrorMessage ] = ex . Message ;
2019-03-01 05:20:21 +01:00
return RedirectToAction ( nameof ( Services ) ) ;
}
2018-07-22 11:38:14 +02:00
LightningConfigurations confs = new LightningConfigurations ( ) ;
2019-03-01 05:20:21 +01:00
if ( service . Type = = ExternalServiceTypes . LNDGRPC )
2018-12-07 11:31:07 +01:00
{
2018-12-20 08:52:04 +01:00
LightningConfiguration grpcConf = new LightningConfiguration ( ) ;
grpcConf . Type = "grpc" ;
2019-03-01 05:20:21 +01:00
grpcConf . Host = connectionString . Server . DnsSafeHost ;
grpcConf . Port = connectionString . Server . Port ;
grpcConf . SSL = connectionString . Server . Scheme = = "https" ;
2018-12-20 08:52:04 +01:00
confs . Configurations . Add ( grpcConf ) ;
2018-12-07 11:31:07 +01:00
}
2020-01-23 14:20:37 +01:00
else if ( service . Type = = ExternalServiceTypes . LNDRest | | service . Type = = ExternalServiceTypes . CLightningRest )
2018-12-07 11:31:07 +01:00
{
var restconf = new LNDRestConfiguration ( ) ;
2020-06-28 10:55:27 +02:00
restconf . Type = service . Type = = ExternalServiceTypes . LNDRest ? "lnd-rest" : "clightning-rest" ;
2019-03-01 05:20:21 +01:00
restconf . Uri = connectionString . Server . AbsoluteUri ;
2018-12-07 11:31:07 +01:00
confs . Configurations . Add ( restconf ) ;
}
2018-12-20 08:52:04 +01:00
else
2019-03-01 05:20:21 +01:00
throw new NotSupportedException ( service . Type . ToString ( ) ) ;
2018-12-20 08:52:04 +01:00
var commonConf = ( LNDConfiguration ) confs . Configurations [ confs . Configurations . Count - 1 ] ;
commonConf . ChainType = _Options . NetworkType . ToString ( ) ;
commonConf . CryptoCode = cryptoCode ;
2019-03-01 05:20:21 +01:00
commonConf . Macaroon = connectionString . Macaroon = = null ? null : Encoders . Hex . EncodeData ( connectionString . Macaroon ) ;
commonConf . CertificateThumbprint = connectionString . CertificateThumbprint = = null ? null : connectionString . CertificateThumbprint ;
commonConf . AdminMacaroon = connectionString . Macaroons ? . AdminMacaroon ? . Hex ;
commonConf . ReadonlyMacaroon = connectionString . Macaroons ? . ReadonlyMacaroon ? . Hex ;
commonConf . InvoiceMacaroon = connectionString . Macaroons ? . InvoiceMacaroon ? . Hex ;
2018-12-20 08:52:04 +01:00
2021-08-07 14:52:49 +02:00
var nonce = RandomUtils . GetUInt64 ( ) ;
2019-03-01 05:20:21 +01:00
var configKey = GetConfigKey ( "lnd" , serviceName , cryptoCode , nonce ) ;
2018-07-23 04:53:39 +02:00
_LnConfigProvider . KeepConfig ( configKey , confs ) ;
2019-03-01 05:20:21 +01:00
return RedirectToAction ( nameof ( Service ) , new { cryptoCode = cryptoCode , serviceName = serviceName , nonce = nonce } ) ;
2018-07-22 11:38:14 +02:00
}
2018-07-24 10:04:57 +02:00
2019-07-24 10:59:30 +02:00
[Route("server/services/dynamic-dns")]
2019-07-25 11:29:18 +02:00
public async Task < IActionResult > DynamicDnsServices ( )
{
var settings = ( await _SettingsRepository . GetSettingAsync < DynamicDnsSettings > ( ) ) ? ? new DynamicDnsSettings ( ) ;
return View ( settings . Services . Select ( s = > new DynamicDnsViewModel ( )
{
Settings = s
} ) . ToArray ( ) ) ;
}
[Route("server/services/dynamic-dns/{hostname}")]
public async Task < IActionResult > DynamicDnsServices ( string hostname )
2019-07-24 10:59:30 +02:00
{
var settings = ( await _SettingsRepository . GetSettingAsync < DynamicDnsSettings > ( ) ) ? ? new DynamicDnsSettings ( ) ;
2019-07-25 11:29:18 +02:00
var service = settings . Services . FirstOrDefault ( s = > s . Hostname . Equals ( hostname , StringComparison . OrdinalIgnoreCase ) ) ;
if ( service = = null )
return NotFound ( ) ;
2019-07-24 10:59:30 +02:00
var vm = new DynamicDnsViewModel ( ) ;
2019-07-25 11:29:18 +02:00
vm . Modify = true ;
vm . Settings = service ;
return View ( nameof ( DynamicDnsService ) , vm ) ;
2019-07-24 10:59:30 +02:00
}
[Route("server/services/dynamic-dns")]
[HttpPost]
2021-09-07 05:44:11 +02:00
public async Task < IActionResult > DynamicDnsService ( DynamicDnsViewModel viewModel , string? command = null )
2019-07-24 10:59:30 +02:00
{
2019-07-25 11:29:18 +02:00
if ( ! ModelState . IsValid )
{
return View ( viewModel ) ;
}
if ( command = = "Save" )
{
var settings = ( await _SettingsRepository . GetSettingAsync < DynamicDnsSettings > ( ) ) ? ? new DynamicDnsSettings ( ) ;
var i = settings . Services . FindIndex ( d = > d . Hostname . Equals ( viewModel . Settings . Hostname , StringComparison . OrdinalIgnoreCase ) ) ;
if ( i ! = - 1 )
{
ModelState . AddModelError ( nameof ( viewModel . Settings . Hostname ) , "This hostname already exists" ) ;
return View ( viewModel ) ;
}
2019-07-25 12:07:56 +02:00
if ( viewModel . Settings . Hostname ! = null )
viewModel . Settings . Hostname = viewModel . Settings . Hostname . Trim ( ) . ToLowerInvariant ( ) ;
2019-07-25 11:29:18 +02:00
string errorMessage = await viewModel . Settings . SendUpdateRequest ( HttpClientFactory . CreateClient ( ) ) ;
if ( errorMessage = = null )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "The Dynamic DNS has been successfully queried, your configuration is saved" ] . Value ;
2019-07-25 11:29:18 +02:00
viewModel . Settings . LastUpdated = DateTimeOffset . UtcNow ;
settings . Services . Add ( viewModel . Settings ) ;
await _SettingsRepository . UpdateSetting ( settings ) ;
return RedirectToAction ( nameof ( DynamicDnsServices ) ) ;
}
else
{
ModelState . AddModelError ( string . Empty , errorMessage ) ;
return View ( viewModel ) ;
}
}
else
{
return View ( new DynamicDnsViewModel ( ) { Settings = new DynamicDnsService ( ) } ) ;
}
}
[Route("server/services/dynamic-dns/{hostname}")]
[HttpPost]
2021-09-07 05:44:11 +02:00
public async Task < IActionResult > DynamicDnsService ( DynamicDnsViewModel viewModel , string hostname , string? command = null )
2019-07-25 11:29:18 +02:00
{
2019-07-25 13:54:49 +02:00
if ( ! ModelState . IsValid )
{
return View ( viewModel ) ;
}
2019-07-25 11:29:18 +02:00
var settings = ( await _SettingsRepository . GetSettingAsync < DynamicDnsSettings > ( ) ) ? ? new DynamicDnsSettings ( ) ;
2019-09-20 11:51:14 +02:00
2019-07-25 11:29:18 +02:00
var i = settings . Services . FindIndex ( d = > d . Hostname . Equals ( hostname , StringComparison . OrdinalIgnoreCase ) ) ;
if ( i = = - 1 )
return NotFound ( ) ;
if ( viewModel . Settings . Password = = null )
viewModel . Settings . Password = settings . Services [ i ] . Password ;
2019-07-25 12:07:56 +02:00
if ( viewModel . Settings . Hostname ! = null )
viewModel . Settings . Hostname = viewModel . Settings . Hostname . Trim ( ) . ToLowerInvariant ( ) ;
2019-07-24 10:59:30 +02:00
if ( ! viewModel . Settings . Enabled )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "The Dynamic DNS service has been disabled" ] . Value ;
2019-07-24 10:59:30 +02:00
viewModel . Settings . LastUpdated = null ;
}
else
{
2019-07-25 11:29:18 +02:00
string errorMessage = await viewModel . Settings . SendUpdateRequest ( HttpClientFactory . CreateClient ( ) ) ;
if ( errorMessage = = null )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "The Dynamic DNS has been successfully queried, your configuration is saved" ] . Value ;
2019-07-25 11:29:18 +02:00
viewModel . Settings . LastUpdated = DateTimeOffset . UtcNow ;
}
else
{
ModelState . AddModelError ( string . Empty , errorMessage ) ;
return View ( viewModel ) ;
}
2019-07-24 10:59:30 +02:00
}
2019-07-25 11:29:18 +02:00
settings . Services [ i ] = viewModel . Settings ;
await _SettingsRepository . UpdateSetting ( settings ) ;
this . RouteData . Values . Remove ( nameof ( hostname ) ) ;
return RedirectToAction ( nameof ( DynamicDnsServices ) ) ;
2019-07-24 10:59:30 +02:00
}
2021-12-31 08:59:02 +01:00
2021-09-07 04:55:53 +02:00
[HttpGet("server/services/dynamic-dns/{hostname}/delete")]
2019-07-25 12:07:56 +02:00
public async Task < IActionResult > DeleteDynamicDnsService ( string hostname )
{
2021-09-07 04:55:53 +02:00
var settings = await _SettingsRepository . GetSettingAsync < DynamicDnsSettings > ( ) ? ? new DynamicDnsSettings ( ) ;
2019-07-25 12:07:56 +02:00
var i = settings . Services . FindIndex ( d = > d . Hostname . Equals ( hostname , StringComparison . OrdinalIgnoreCase ) ) ;
if ( i = = - 1 )
return NotFound ( ) ;
2021-09-07 04:55:53 +02:00
return View ( "Confirm" ,
new ConfirmModel ( "Delete dynamic DNS service" ,
2023-01-21 19:08:12 +01:00
$"Deleting the dynamic DNS service for <strong>{Html.Encode(hostname)}</strong> means your BTCPay Server will stop updating the associated DNS record periodically." , "Delete" ) ) ;
2019-07-25 12:07:56 +02:00
}
2021-12-31 08:59:02 +01:00
2021-09-07 04:55:53 +02:00
[HttpPost("server/services/dynamic-dns/{hostname}/delete")]
2019-07-25 12:07:56 +02:00
public async Task < IActionResult > DeleteDynamicDnsServicePost ( string hostname )
{
var settings = ( await _SettingsRepository . GetSettingAsync < DynamicDnsSettings > ( ) ) ? ? new DynamicDnsSettings ( ) ;
var i = settings . Services . FindIndex ( d = > d . Hostname . Equals ( hostname , StringComparison . OrdinalIgnoreCase ) ) ;
if ( i = = - 1 )
return NotFound ( ) ;
settings . Services . RemoveAt ( i ) ;
await _SettingsRepository . UpdateSetting ( settings ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Dynamic DNS service successfully removed" ] . Value ;
2021-09-07 04:55:53 +02:00
RouteData . Values . Remove ( nameof ( hostname ) ) ;
2019-07-25 12:07:56 +02:00
return RedirectToAction ( nameof ( DynamicDnsServices ) ) ;
}
2019-07-24 10:59:30 +02:00
2021-09-07 04:55:53 +02:00
[HttpGet("server/services/ssh")]
2019-09-19 12:17:20 +02:00
public async Task < IActionResult > SSHService ( )
2018-08-12 14:38:45 +02:00
{
2022-05-24 06:18:16 +02:00
if ( ! CanShowSSHService ( ) )
2018-08-12 14:38:45 +02:00
return NotFound ( ) ;
2019-03-01 08:41:36 +01:00
2019-09-20 11:51:14 +02:00
var settings = _Options . SSHSettings ;
2019-04-12 07:13:14 +02:00
var server = Extensions . IsLocalNetwork ( settings . Server ) ? this . Request . Host . Host : settings . Server ;
2018-08-12 14:38:45 +02:00
SSHServiceViewModel vm = new SSHServiceViewModel ( ) ;
string port = settings . Port = = 22 ? "" : $" -p {settings.Port}" ;
2019-03-01 08:41:36 +01:00
vm . CommandLine = $"ssh {settings.Username}@{server}{port}" ;
2018-08-12 14:38:45 +02:00
vm . Password = settings . Password ;
vm . KeyFilePassword = settings . KeyFilePassword ;
vm . HasKeyFile = ! string . IsNullOrEmpty ( settings . KeyFile ) ;
2019-09-20 11:51:14 +02:00
// Let's try to just read the authorized key file
if ( CanAccessAuthorizedKeyFile ( ) )
{
try
{
vm . SSHKeyFileContent = await System . IO . File . ReadAllTextAsync ( settings . AuthorizedKeysFile ) ;
}
catch { }
}
// If that fail, just fallback to ssh
if ( vm . SSHKeyFileContent = = null & & _sshState . CanUseSSH )
2019-09-19 12:17:20 +02:00
{
try
{
2022-01-14 09:50:29 +01:00
using var sshClient = await _Options . SSHSettings . ConnectAsync ( ) ;
var result = await sshClient . RunBash ( "cat ~/.ssh/authorized_keys" , TimeSpan . FromSeconds ( 10 ) ) ;
vm . SSHKeyFileContent = result . Output ;
2019-09-19 12:17:20 +02:00
}
2019-09-20 11:51:14 +02:00
catch { }
2019-09-19 12:17:20 +02:00
}
2018-08-12 14:38:45 +02:00
return View ( vm ) ;
}
2022-05-24 06:18:16 +02:00
bool CanShowSSHService ( )
2019-09-20 11:51:14 +02:00
{
2022-05-24 06:18:16 +02:00
return ! _policiesSettings . DisableSSHService & &
2021-04-17 06:29:50 +02:00
_Options . SSHSettings ! = null & & ( _sshState . CanUseSSH | | CanAccessAuthorizedKeyFile ( ) ) ;
2019-09-20 11:51:14 +02:00
}
private bool CanAccessAuthorizedKeyFile ( )
{
2019-09-20 12:33:07 +02:00
return _Options . SSHSettings ? . AuthorizedKeysFile ! = null & & System . IO . File . Exists ( _Options . SSHSettings . AuthorizedKeysFile ) ;
2019-09-20 11:51:14 +02:00
}
2021-09-07 04:55:53 +02:00
[HttpPost("server/services/ssh")]
2021-09-07 05:44:11 +02:00
public async Task < IActionResult > SSHService ( SSHServiceViewModel viewModel , string? command = null )
2019-09-19 12:17:20 +02:00
{
2022-05-24 06:18:16 +02:00
if ( ! CanShowSSHService ( ) )
2021-04-17 06:29:50 +02:00
return NotFound ( ) ;
2019-09-20 11:51:14 +02:00
2021-04-17 06:29:50 +02:00
if ( command is "Save" )
2019-09-19 12:17:20 +02:00
{
2021-04-17 06:29:50 +02:00
string newContent = viewModel ? . SSHKeyFileContent ? ? string . Empty ;
newContent = newContent . Replace ( "\r\n" , "\n" , StringComparison . OrdinalIgnoreCase ) ;
bool updated = false ;
2021-09-07 05:44:11 +02:00
Exception ? exception = null ;
2021-04-17 06:29:50 +02:00
// Let's try to just write the file
if ( CanAccessAuthorizedKeyFile ( ) )
2019-09-20 11:51:14 +02:00
{
2021-04-17 06:29:50 +02:00
try
{
await System . IO . File . WriteAllTextAsync ( _Options . SSHSettings . AuthorizedKeysFile , newContent ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "authorized_keys has been updated" ] . Value ;
2021-04-17 06:29:50 +02:00
updated = true ;
}
catch ( Exception ex )
{
exception = ex ;
}
2019-09-20 11:51:14 +02:00
}
2021-04-17 06:29:50 +02:00
// If that fail, fallback to ssh
if ( ! updated & & _sshState . CanUseSSH )
2019-09-19 12:17:20 +02:00
{
2021-04-17 06:29:50 +02:00
try
2019-09-20 11:51:14 +02:00
{
2021-04-17 06:29:50 +02:00
using ( var sshClient = await _Options . SSHSettings . ConnectAsync ( ) )
{
await sshClient . RunBash ( $"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys" , TimeSpan . FromSeconds ( 10 ) ) ;
}
updated = true ;
exception = null ;
}
catch ( Exception ex )
{
exception = ex ;
2019-09-20 11:51:14 +02:00
}
2019-09-19 12:17:20 +02:00
}
2021-04-17 06:29:50 +02:00
if ( exception is null )
2019-09-20 11:51:14 +02:00
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "authorized_keys has been updated" ] . Value ;
2019-09-20 11:51:14 +02:00
}
2021-04-17 06:29:50 +02:00
else
{
TempData [ WellKnownTempData . ErrorMessage ] = exception . Message ;
}
return RedirectToAction ( nameof ( SSHService ) ) ;
2019-09-20 11:51:14 +02:00
}
2021-12-31 08:59:02 +01:00
2021-09-07 04:55:53 +02:00
if ( command is "disable" )
2019-09-20 11:51:14 +02:00
{
2021-04-17 06:29:50 +02:00
return RedirectToAction ( nameof ( SSHServiceDisable ) ) ;
2019-09-19 12:17:20 +02:00
}
2021-12-31 08:59:02 +01:00
2021-04-17 06:29:50 +02:00
return NotFound ( ) ;
}
2021-09-07 04:55:53 +02:00
[HttpGet("server/services/ssh/disable")]
2021-04-17 06:29:50 +02:00
public IActionResult SSHServiceDisable ( )
{
2021-09-07 04:55:53 +02:00
return View ( "Confirm" , new ConfirmModel ( "Disable modification of SSH settings" , "This action is permanent and will remove the ability to change the SSH settings via the BTCPay Server user interface." , "Disable" ) ) ;
2021-04-17 06:29:50 +02:00
}
2021-09-07 04:55:53 +02:00
[HttpPost("server/services/ssh/disable")]
2021-04-17 06:29:50 +02:00
public async Task < IActionResult > SSHServiceDisablePost ( )
{
var policies = await _SettingsRepository . GetSettingAsync < PoliciesSettings > ( ) ? ? new PoliciesSettings ( ) ;
policies . DisableSSHService = true ;
await _SettingsRepository . UpdateSetting ( policies ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Changes to the SSH settings are now permanently disabled in the BTCPay Server user interface" ] . Value ;
2021-04-17 06:29:50 +02:00
return RedirectToAction ( nameof ( Services ) ) ;
2019-09-19 12:17:20 +02:00
}
2024-02-21 20:54:39 +01:00
[HttpGet("server/branding")]
public async Task < IActionResult > Branding ( )
2018-04-19 18:39:51 +02:00
{
2024-02-21 20:54:39 +01:00
var server = await _SettingsRepository . GetSettingAsync < ServerSettings > ( ) ? ? new ServerSettings ( ) ;
var theme = await _SettingsRepository . GetSettingAsync < ThemeSettings > ( ) ? ? new ThemeSettings ( ) ;
var vm = new BrandingViewModel
{
ServerName = server . ServerName ,
ContactUrl = server . ContactUrl ,
CustomTheme = theme . CustomTheme ,
CustomThemeExtension = theme . CustomThemeExtension ,
2024-05-09 02:18:02 +02:00
CustomThemeCssUrl = await _uriResolver . Resolve ( Request . GetAbsoluteRootUri ( ) , theme . CustomThemeCssUrl ) ,
LogoUrl = await _uriResolver . Resolve ( Request . GetAbsoluteRootUri ( ) , theme . LogoUrl )
2024-02-21 20:54:39 +01:00
} ;
return View ( vm ) ;
2018-04-19 18:39:51 +02:00
}
2021-09-07 05:31:18 +02:00
2024-02-21 20:54:39 +01:00
[HttpPost("server/branding")]
public async Task < IActionResult > Branding (
BrandingViewModel vm ,
2022-12-14 05:37:31 +01:00
[FromForm] bool RemoveLogoFile ,
[FromForm] bool RemoveCustomThemeFile )
2017-10-27 10:53:04 +02:00
{
2022-11-14 14:29:23 +01:00
var settingsChanged = false ;
2024-02-21 20:54:39 +01:00
var server = await _SettingsRepository . GetSettingAsync < ServerSettings > ( ) ? ? new ServerSettings ( ) ;
var theme = await _SettingsRepository . GetSettingAsync < ThemeSettings > ( ) ? ? new ThemeSettings ( ) ;
2023-01-06 14:18:07 +01:00
2022-11-14 14:29:23 +01:00
var userId = GetUserId ( ) ;
if ( userId is null )
return NotFound ( ) ;
2023-01-06 14:18:07 +01:00
2024-05-09 02:18:02 +02:00
vm . LogoUrl = await _uriResolver . Resolve ( this . Request . GetAbsoluteRootUri ( ) , theme . LogoUrl ) ;
vm . CustomThemeCssUrl = await _uriResolver . Resolve ( this . Request . GetAbsoluteRootUri ( ) , theme . CustomThemeCssUrl ) ;
2024-02-21 20:54:39 +01:00
2024-03-19 15:04:09 +01:00
if ( server . ServerName ! = vm . ServerName )
2024-02-21 20:54:39 +01:00
{
server . ServerName = vm . ServerName ;
settingsChanged = true ;
2024-03-19 15:04:09 +01:00
}
if ( server . ContactUrl ! = vm . ContactUrl )
{
server . ContactUrl = ! string . IsNullOrWhiteSpace ( vm . ContactUrl )
? vm . ContactUrl . IsValidEmail ( ) ? $"mailto:{vm.ContactUrl}" : vm . ContactUrl
: null ;
settingsChanged = true ;
}
if ( settingsChanged )
{
2024-02-21 20:54:39 +01:00
await _SettingsRepository . UpdateSetting ( server ) ;
}
if ( vm . CustomThemeFile ! = null )
2022-12-14 05:37:31 +01:00
{
2024-02-21 20:54:39 +01:00
if ( vm . CustomThemeFile . ContentType . Equals ( "text/css" , StringComparison . InvariantCulture ) )
2022-12-14 05:37:31 +01:00
{
// add new file
try
{
2024-02-21 20:54:39 +01:00
var storedFile = await _fileService . AddFile ( vm . CustomThemeFile , userId ) ;
2024-05-09 02:18:02 +02:00
theme . CustomThemeCssUrl = new UnresolvedUri . FileIdUri ( storedFile . Id ) ;
vm . CustomThemeCssUrl = await _uriResolver . Resolve ( Request . GetAbsoluteRootUri ( ) , theme . CustomThemeCssUrl ) ;
2022-12-14 05:37:31 +01:00
settingsChanged = true ;
}
catch ( Exception e )
{
2024-11-07 02:43:22 +01:00
ModelState . AddModelError ( nameof ( vm . CustomThemeFile ) , StringLocalizer [ "Could not save CSS file: {0}" , e . Message ] ) ;
2022-12-14 05:37:31 +01:00
}
}
else
{
2024-11-07 02:43:22 +01:00
ModelState . AddModelError ( nameof ( vm . CustomThemeFile ) , StringLocalizer [ "The uploaded file needs to be a CSS file" ] ) ;
2022-12-14 05:37:31 +01:00
}
}
2024-05-09 02:18:02 +02:00
else if ( RemoveCustomThemeFile & & theme . CustomThemeCssUrl is not null )
2022-12-14 05:37:31 +01:00
{
2024-05-09 02:18:02 +02:00
vm . CustomThemeCssUrl = null ;
theme . CustomThemeCssUrl = null ;
theme . CustomTheme = false ;
theme . CustomThemeExtension = ThemeExtension . Custom ;
2022-12-14 05:37:31 +01:00
settingsChanged = true ;
}
2023-01-06 14:18:07 +01:00
2024-02-21 20:54:39 +01:00
if ( vm . LogoFile ! = null )
2022-11-14 14:29:23 +01:00
{
2024-02-21 20:54:39 +01:00
if ( vm . LogoFile . Length > 1_000_000 )
2022-11-14 14:29:23 +01:00
{
2024-11-07 02:43:22 +01:00
ModelState . AddModelError ( nameof ( vm . LogoFile ) , StringLocalizer [ "The uploaded file should be less than {0}" , "1MB" ] ) ;
2023-02-14 09:03:12 +01:00
}
2024-02-21 20:54:39 +01:00
else if ( ! vm . LogoFile . ContentType . StartsWith ( "image/" , StringComparison . InvariantCulture ) )
2023-02-14 09:03:12 +01:00
{
2024-11-07 02:43:22 +01:00
ModelState . AddModelError ( nameof ( vm . LogoFile ) , StringLocalizer [ "The uploaded file needs to be an image" ] ) ;
2023-02-14 09:03:12 +01:00
}
else
{
2024-02-21 20:54:39 +01:00
var formFile = await vm . LogoFile . Bufferize ( ) ;
2023-02-14 09:03:12 +01:00
if ( ! FileTypeDetector . IsPicture ( formFile . Buffer , formFile . FileName ) )
2022-11-14 14:29:23 +01:00
{
2024-11-07 02:43:22 +01:00
ModelState . AddModelError ( nameof ( vm . LogoFile ) , StringLocalizer [ "The uploaded file needs to be an image" ] ) ;
2022-11-14 14:29:23 +01:00
}
2023-02-14 09:03:12 +01:00
else
2022-11-14 14:29:23 +01:00
{
2024-02-21 20:54:39 +01:00
vm . LogoFile = formFile ;
2023-07-24 15:57:24 +02:00
// add new file
2023-02-14 09:03:12 +01:00
try
{
2024-02-21 20:54:39 +01:00
var storedFile = await _fileService . AddFile ( vm . LogoFile , userId ) ;
2024-05-09 02:18:02 +02:00
theme . LogoUrl = new UnresolvedUri . FileIdUri ( storedFile . Id ) ;
vm . LogoUrl = await _uriResolver . Resolve ( Request . GetAbsoluteRootUri ( ) , theme . LogoUrl ) ;
2023-02-14 09:03:12 +01:00
settingsChanged = true ;
}
catch ( Exception e )
{
2024-11-07 02:43:22 +01:00
ModelState . AddModelError ( nameof ( vm . LogoFile ) , StringLocalizer [ "Could not save logo: {0}" , e . Message ] ) ;
2023-02-14 09:03:12 +01:00
}
2022-11-14 14:29:23 +01:00
}
}
}
2024-05-09 02:18:02 +02:00
else if ( RemoveLogoFile & & theme . LogoUrl is not null )
2023-02-04 17:24:19 +01:00
{
2024-05-09 02:18:02 +02:00
vm . LogoUrl = null ;
theme . LogoUrl = null ;
2023-02-04 17:24:19 +01:00
settingsChanged = true ;
}
2023-01-06 14:18:07 +01:00
2024-05-09 02:18:02 +02:00
if ( vm . CustomTheme & & theme . CustomThemeExtension ! = vm . CustomThemeExtension )
2022-12-14 05:37:31 +01:00
{
// Require a custom theme to be defined in that case
2024-05-09 02:18:02 +02:00
if ( string . IsNullOrEmpty ( vm . CustomThemeCssUrl ) & & theme . CustomThemeCssUrl is null )
2022-12-14 05:37:31 +01:00
{
2024-05-09 02:18:02 +02:00
ModelState . AddModelError ( nameof ( vm . CustomThemeCssUrl ) , "Please provide a custom theme" ) ;
2022-12-14 05:37:31 +01:00
}
else
{
2024-02-21 20:54:39 +01:00
theme . CustomThemeExtension = vm . CustomThemeExtension ;
2022-12-14 05:37:31 +01:00
settingsChanged = true ;
}
}
2023-01-06 14:18:07 +01:00
2024-05-09 02:18:02 +02:00
if ( theme . CustomTheme ! = vm . CustomTheme & & ! RemoveCustomThemeFile )
2022-11-14 14:29:23 +01:00
{
2024-02-21 20:54:39 +01:00
theme . CustomTheme = vm . CustomTheme ;
2022-11-14 14:29:23 +01:00
settingsChanged = true ;
}
if ( settingsChanged )
2021-09-07 05:31:18 +02:00
{
2024-02-21 20:54:39 +01:00
await _SettingsRepository . UpdateSetting ( theme ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Settings updated successfully" ] . Value ;
2024-05-09 02:18:02 +02:00
return RedirectToAction ( nameof ( Branding ) ) ;
2021-09-07 05:31:18 +02:00
}
2024-02-21 20:54:39 +01:00
return View ( vm ) ;
2017-10-27 10:53:04 +02:00
}
2017-09-27 07:18:09 +02:00
2024-02-28 12:43:18 +01:00
[HttpGet("server/emails")]
2019-03-04 22:56:23 +01:00
public async Task < IActionResult > Emails ( )
{
2024-12-06 05:47:14 +01:00
var email = await _emailSenderFactory . GetSettings ( ) ? ? new EmailSettings ( ) ;
2024-02-21 14:43:44 +01:00
var vm = new ServerEmailsViewModel ( email )
{
EnableStoresToUseServerEmailSettings = ! _policiesSettings . DisableStoresToUseServerEmailSettings
} ;
return View ( vm ) ;
2019-03-04 22:56:23 +01:00
}
2024-02-28 12:43:18 +01:00
[HttpPost("server/emails")]
2024-02-21 14:43:44 +01:00
public async Task < IActionResult > Emails ( ServerEmailsViewModel model , string command )
2017-10-27 10:53:04 +02:00
{
if ( command = = "Test" )
{
try
{
2020-10-05 08:42:19 +02:00
if ( model . PasswordSet )
{
2024-12-06 05:47:14 +01:00
var settings = await _emailSenderFactory . GetSettings ( ) ? ? new EmailSettings ( ) ;
2020-10-05 08:42:19 +02:00
model . Settings . Password = settings . Password ;
}
2022-06-23 06:41:52 +02:00
model . Settings . Validate ( "Settings." , ModelState ) ;
if ( string . IsNullOrEmpty ( model . TestEmail ) )
ModelState . AddModelError ( nameof ( model . TestEmail ) , new RequiredAttribute ( ) . FormatErrorMessage ( nameof ( model . TestEmail ) ) ) ;
if ( ! ModelState . IsValid )
2020-10-05 08:42:19 +02:00
return View ( model ) ;
2024-03-19 14:58:33 +01:00
var serverSettings = await _SettingsRepository . GetSettingAsync < ServerSettings > ( ) ;
var serverName = string . IsNullOrEmpty ( serverSettings ? . ServerName ) ? "BTCPay Server" : serverSettings . ServerName ;
2021-12-15 13:30:46 +01:00
using ( var client = await model . Settings . CreateSmtpClient ( ) )
2024-03-19 14:58:33 +01:00
using ( var message = model . Settings . CreateMailMessage ( MailboxAddress . Parse ( model . TestEmail ) , $"{serverName}: Email test" , "You received it, the BTCPay Server SMTP settings work." , false ) )
2020-01-12 07:32:26 +01:00
{
2021-12-15 13:30:46 +01:00
await client . SendAsync ( message ) ;
await client . DisconnectAsync ( true ) ;
2020-01-12 07:32:26 +01:00
}
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Email sent to {0}. Please verify you received it." , model . TestEmail ] . Value ;
2017-10-27 10:53:04 +02:00
}
catch ( Exception ex )
{
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . ErrorMessage ] = ex . Message ;
2017-10-27 10:53:04 +02:00
}
return View ( model ) ;
}
2024-02-21 14:43:44 +01:00
if ( _policiesSettings . DisableStoresToUseServerEmailSettings = = model . EnableStoresToUseServerEmailSettings )
{
_policiesSettings . DisableStoresToUseServerEmailSettings = ! model . EnableStoresToUseServerEmailSettings ;
await _SettingsRepository . UpdateSetting ( _policiesSettings ) ;
}
2022-06-22 05:05:32 +02:00
if ( command = = "ResetPassword" )
2020-10-05 08:42:19 +02:00
{
2021-09-09 13:31:35 +02:00
var settings = await _SettingsRepository . GetSettingAsync < EmailSettings > ( ) ? ? new EmailSettings ( ) ;
2020-10-05 08:42:19 +02:00
settings . Password = null ;
2022-06-23 06:41:52 +02:00
await _SettingsRepository . UpdateSetting ( settings ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Email server password reset" ] . Value ;
2020-10-05 08:42:19 +02:00
return RedirectToAction ( nameof ( Emails ) ) ;
}
2024-02-21 14:43:44 +01:00
// save
if ( model . Settings . From is not null & & ! MailboxAddressValidator . IsMailboxAddress ( model . Settings . From ) )
2017-10-27 10:53:04 +02:00
{
2024-10-17 15:51:40 +02:00
ModelState . AddModelError ( "Settings.From" , StringLocalizer [ "Invalid email" ] ) ;
2024-02-21 14:43:44 +01:00
return View ( model ) ;
}
2024-12-06 05:47:14 +01:00
var oldSettings = await _emailSenderFactory . GetSettings ( ) ? ? new EmailSettings ( ) ;
2024-02-28 12:43:18 +01:00
if ( new ServerEmailsViewModel ( oldSettings ) . PasswordSet )
2024-02-21 14:43:44 +01:00
{
model . Settings . Password = oldSettings . Password ;
2017-10-27 10:53:04 +02:00
}
2024-02-21 14:43:44 +01:00
await _SettingsRepository . UpdateSetting ( model . Settings ) ;
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = StringLocalizer [ "Email settings saved" ] . Value ;
2024-02-21 14:43:44 +01:00
return RedirectToAction ( nameof ( Emails ) ) ;
2017-10-27 10:53:04 +02:00
}
2018-11-07 14:29:35 +01:00
[Route("server/logs/{file?}")]
2024-10-27 13:43:47 +01:00
public async Task < IActionResult > LogsView ( string? file = null , int offset = 0 , bool download = false )
2018-11-07 14:29:35 +01:00
{
if ( offset < 0 )
{
offset = 0 ;
}
var vm = new LogsViewModel ( ) ;
if ( string . IsNullOrEmpty ( _Options . LogFile ) )
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "File Logging Option not specified. You need to set debuglog and optionally debugloglevel in the configuration or through runtime arguments" ] . Value ;
2018-11-07 14:29:35 +01:00
}
else
{
var di = Directory . GetParent ( _Options . LogFile ) ;
2021-09-09 13:31:35 +02:00
if ( di is null )
2018-11-07 14:29:35 +01:00
{
2024-10-17 15:51:40 +02:00
TempData [ WellKnownTempData . ErrorMessage ] = StringLocalizer [ "Could not load log files" ] . Value ;
2021-09-09 13:31:35 +02:00
return View ( "Logs" , vm ) ;
2018-11-07 14:29:35 +01:00
}
var fileNameWithoutExtension = Path . GetFileNameWithoutExtension ( _Options . LogFile ) ;
var fileExtension = Path . GetExtension ( _Options . LogFile ) ? ? string . Empty ;
2021-09-07 05:44:11 +02:00
// We are checking if "di" is null above yet accessing GetFiles on it, this could lead to an exception?
2018-11-07 14:29:35 +01:00
var logFiles = di . GetFiles ( $"{fileNameWithoutExtension}*{fileExtension}" ) ;
vm . LogFileCount = logFiles . Length ;
vm . LogFiles = logFiles
. OrderBy ( info = > info . LastWriteTime )
. Skip ( offset )
. Take ( 5 )
. ToList ( ) ;
vm . LogFileOffset = offset ;
2019-05-30 04:46:09 +02:00
if ( string . IsNullOrEmpty ( file ) | | ! file . EndsWith ( fileExtension , StringComparison . Ordinal ) )
2019-02-10 19:12:02 +01:00
return View ( "Logs" , vm ) ;
2018-11-07 14:29:35 +01:00
vm . Log = "" ;
2019-05-30 04:46:09 +02:00
var fi = vm . LogFiles . FirstOrDefault ( o = > o . Name = = file ) ;
if ( fi = = null )
return NotFound ( ) ;
2018-11-09 13:43:10 +01:00
try
2018-11-07 14:29:35 +01:00
{
2024-10-27 13:43:47 +01:00
var fileStream = new FileStream (
2019-05-30 04:46:09 +02:00
fi . FullName ,
2018-11-09 13:43:10 +01:00
FileMode . Open ,
FileAccess . Read ,
2022-01-14 09:50:29 +01:00
FileShare . ReadWrite ) ;
2024-10-27 13:43:47 +01:00
if ( download )
{
return new FileStreamResult ( fileStream , "text/plain" )
{
FileDownloadName = file
} ;
}
await using ( fileStream )
{
using var reader = new StreamReader ( fileStream ) ;
vm . Log = await reader . ReadToEndAsync ( ) ;
}
2018-11-07 14:29:35 +01:00
}
2018-11-09 13:43:10 +01:00
catch
{
return NotFound ( ) ;
}
2018-11-07 14:29:35 +01:00
}
return View ( "Logs" , vm ) ;
}
2017-10-27 10:53:04 +02:00
}
2017-09-15 18:15:17 +02:00
}