btcpayserver/BTCPayServer/Controllers/ServerController.cs

402 lines
15 KiB
C#
Raw Normal View History

2018-07-22 11:38:14 +02:00
using BTCPayServer.Configuration;
using Microsoft.Extensions.Logging;
2018-07-22 11:38:14 +02:00
using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.ServerViewModels;
2018-07-22 11:38:14 +02:00
using BTCPayServer.Payments.Lightning;
2017-09-27 07:18:09 +02:00
using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
2017-09-27 07:18:09 +02:00
using BTCPayServer.Validations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
2018-07-22 11:38:14 +02:00
using NBitcoin.DataEncoders;
using System;
using System.Collections.Generic;
2017-09-27 07:18:09 +02:00
using System.ComponentModel.DataAnnotations;
using System.Linq;
2017-09-27 07:18:09 +02:00
using System.Net;
using System.Net.Http;
2017-09-27 07:18:09 +02:00
using System.Net.Mail;
using System.Threading.Tasks;
namespace BTCPayServer.Controllers
{
2018-04-29 19:33:42 +02:00
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key)]
public class ServerController : Controller
{
private UserManager<ApplicationUser> _UserManager;
SettingsRepository _SettingsRepository;
2018-05-02 20:32:42 +02:00
private BTCPayRateProviderFactory _RateProviderFactory;
private StoreRepository _StoreRepository;
2018-07-22 11:38:14 +02:00
LightningConfigurationProvider _LnConfigProvider;
BTCPayServerOptions _Options;
public ServerController(UserManager<ApplicationUser> userManager,
2018-07-22 11:38:14 +02:00
Configuration.BTCPayServerOptions options,
2018-05-02 20:32:42 +02:00
BTCPayRateProviderFactory rateProviderFactory,
SettingsRepository settingsRepository,
2018-07-22 11:38:14 +02:00
LightningConfigurationProvider lnConfigProvider,
Services.Stores.StoreRepository storeRepository)
{
2018-07-22 11:38:14 +02:00
_Options = options;
_UserManager = userManager;
_SettingsRepository = settingsRepository;
_RateProviderFactory = rateProviderFactory;
_StoreRepository = storeRepository;
2018-07-22 11:38:14 +02:00
_LnConfigProvider = lnConfigProvider;
}
[Route("server/rates")]
public async Task<IActionResult> Rates()
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
2018-04-18 11:23:39 +02:00
var vm = new RatesViewModel()
{
CacheMinutes = rates.CacheInMinutes,
PrivateKey = rates.PrivateKey,
PublicKey = rates.PublicKey
2018-04-18 11:23:39 +02:00
};
await FetchRateLimits(vm);
return View(vm);
}
2018-04-18 11:23:39 +02:00
private static async Task FetchRateLimits(RatesViewModel vm)
{
2018-04-18 11:23:39 +02:00
var coinAverage = GetCoinaverageService(vm, false);
if (coinAverage != null)
{
2018-04-18 11:23:39 +02:00
try
{
vm.RateLimits = await coinAverage.GetRateLimitsAsync();
}
catch { }
}
}
[Route("server/rates")]
[HttpPost]
public async Task<IActionResult> Rates(RatesViewModel vm)
{
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
rates.PrivateKey = vm.PrivateKey;
rates.PublicKey = vm.PublicKey;
rates.CacheInMinutes = vm.CacheMinutes;
try
{
2018-04-18 11:23:39 +02:00
var service = GetCoinaverageService(vm, true);
2018-07-22 11:38:14 +02:00
if (service != null)
2018-04-18 11:23:39 +02:00
await service.TestAuthAsync();
}
catch
{
ModelState.AddModelError(nameof(vm.PrivateKey), "Invalid API key pair");
}
if (!ModelState.IsValid)
2018-04-18 11:23:39 +02:00
{
await FetchRateLimits(vm);
return View(vm);
2018-04-18 11:23:39 +02:00
}
await _SettingsRepository.UpdateSetting(rates);
StatusMessage = "Rate settings successfully updated";
return RedirectToAction(nameof(Rates));
}
2018-04-18 11:23:39 +02:00
private static CoinAverageRateProvider GetCoinaverageService(RatesViewModel vm, bool withAuth)
{
var settings = new CoinAverageSettings()
{
KeyPair = (vm.PublicKey, vm.PrivateKey)
};
if (!withAuth || settings.GetCoinAverageSignature() != null)
{
2018-05-02 20:32:42 +02:00
return new CoinAverageRateProvider()
2018-04-18 11:23:39 +02:00
{ Authenticator = settings };
}
return null;
}
[Route("server/users")]
public IActionResult ListUsers()
{
var users = new UsersViewModel();
2017-12-04 06:39:02 +01:00
users.StatusMessage = StatusMessage;
users.Users
= _UserManager.Users.Select(u => new UsersViewModel.UserViewModel()
{
Name = u.UserName,
2017-12-04 06:39:02 +01:00
Email = u.Email,
Id = u.Id
}).ToList();
return View(users);
}
2017-09-27 07:18:09 +02:00
2018-03-22 11:55:14 +01:00
[Route("server/users/{userId}")]
public new async Task<IActionResult> User(string userId)
{
var user = await _UserManager.FindByIdAsync(userId);
if (user == null)
return NotFound();
var roles = await _UserManager.GetRolesAsync(user);
var userVM = new UserViewModel();
userVM.Id = user.Id;
2018-04-19 18:44:24 +02:00
userVM.Email = user.Email;
2018-03-22 11:55:14 +01:00
userVM.IsAdmin = IsAdmin(roles);
return View(userVM);
}
private static bool IsAdmin(IList<string> roles)
{
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
}
[Route("server/users/{userId}")]
[HttpPost]
public new async Task<IActionResult> User(string userId, UserViewModel viewModel)
{
var user = await _UserManager.FindByIdAsync(userId);
if (user == null)
return NotFound();
var roles = await _UserManager.GetRolesAsync(user);
var isAdmin = IsAdmin(roles);
bool updated = false;
if (isAdmin != viewModel.IsAdmin)
2018-03-22 11:55:14 +01:00
{
if (viewModel.IsAdmin)
await _UserManager.AddToRoleAsync(user, Roles.ServerAdmin);
else
await _UserManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
updated = true;
}
if (updated)
2018-03-22 11:55:14 +01:00
{
viewModel.StatusMessage = "User successfully updated";
}
return View(viewModel);
}
2017-12-04 06:39:02 +01:00
[Route("server/users/{userId}/delete")]
public async Task<IActionResult> DeleteUser(string userId)
{
var user = userId == null ? null : await _UserManager.FindByIdAsync(userId);
if (user == null)
return NotFound();
return View("Confirm", new ConfirmModel()
{
Title = "Delete user " + user.Email,
Description = "This user will be permanently deleted",
Action = "Delete"
});
}
[Route("server/users/{userId}/delete")]
[HttpPost]
public async Task<IActionResult> DeleteUserPost(string userId)
{
var user = userId == null ? null : await _UserManager.FindByIdAsync(userId);
if (user == null)
return NotFound();
await _UserManager.DeleteAsync(user);
await _StoreRepository.CleanUnreachableStores();
2017-12-04 06:39:02 +01:00
StatusMessage = "User deleted";
return RedirectToAction(nameof(ListUsers));
}
[TempData]
public string StatusMessage
{
get; set;
}
[Route("server/emails")]
public async Task<IActionResult> Emails()
{
var data = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
return View(new EmailsViewModel() { Settings = data });
}
2017-09-27 07:18:09 +02:00
[Route("server/policies")]
public async Task<IActionResult> Policies()
{
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
return View(data);
}
[Route("server/policies")]
[HttpPost]
public async Task<IActionResult> Policies(PoliciesSettings settings)
2018-04-19 18:39:51 +02:00
{
await _SettingsRepository.UpdateSetting(settings);
TempData["StatusMessage"] = "Policies updated successfully";
return View(settings);
}
2018-07-22 11:38:14 +02:00
[Route("server/services")]
public IActionResult Services()
{
var result = new ServicesViewModel();
foreach (var cryptoCode in _Options.ExternalServicesByCryptoCode.Keys)
2018-07-22 11:38:14 +02:00
{
{
int i = 0;
foreach (var grpcService in _Options.ExternalServicesByCryptoCode.GetServices<ExternalLNDGRPC>(cryptoCode))
2018-07-22 11:38:14 +02:00
{
result.LNDServices.Add(new ServicesViewModel.LNDServiceViewModel()
{
Crypto = cryptoCode,
Type = "gRPC",
Index = i++,
});
}
2018-07-22 11:38:14 +02:00
}
}
return View(result);
}
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
public IActionResult LNDGRPCServices(string cryptoCode, int index, uint? nonce)
2018-07-22 11:38:14 +02:00
{
var external = GetExternalLNDConnectionString(cryptoCode, index);
if (external == null)
2018-07-22 11:38:14 +02:00
return NotFound();
var model = new LNDGRPCServicesViewModel();
2018-07-22 14:28:21 +02:00
model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}";
2018-07-22 11:38:14 +02:00
model.SSL = external.BaseUri.Scheme == "https";
if (external.CertificateThumbprint != null)
{
model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint);
}
if (external.Macaroon != null)
{
model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon);
}
if (nonce != null)
2018-07-22 11:38:14 +02:00
{
var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce.Value);
var lnConfig = _LnConfigProvider.GetConfig(configKey);
2018-07-22 11:38:14 +02:00
if (lnConfig != null)
{
model.QRCodeLink = $"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}lnd-config/{configKey}/lnd.config";
2018-07-22 14:28:21 +02:00
model.QRCode = $"config={model.QRCodeLink}";
2018-07-22 11:38:14 +02:00
}
}
2018-07-22 11:38:14 +02:00
return View(model);
}
private static uint GetConfigKey(string type, string cryptoCode, int index, uint nonce)
{
return (uint)HashCode.Combine(type, cryptoCode, index, nonce);
}
[Route("lnd-config/{configKey}/lnd.config")]
[AllowAnonymous]
public IActionResult GetLNDConfig(uint configKey)
2018-07-22 11:38:14 +02:00
{
var conf = _LnConfigProvider.GetConfig(configKey);
2018-07-22 11:38:14 +02:00
if (conf == null)
return NotFound();
return Json(conf);
}
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
2018-07-22 11:38:14 +02:00
[HttpPost]
public IActionResult LNDGRPCServicesPOST(string cryptoCode, int index)
2018-07-22 11:38:14 +02:00
{
var external = GetExternalLNDConnectionString(cryptoCode, index);
if (external == null)
2018-07-22 11:38:14 +02:00
return NotFound();
LightningConfigurations confs = new LightningConfigurations();
LightningConfiguration conf = new LightningConfiguration();
conf.Type = "grpc";
conf.CryptoCode = cryptoCode;
conf.Host = external.BaseUri.DnsSafeHost;
conf.Port = external.BaseUri.Port;
conf.SSL = external.BaseUri.Scheme == "https";
conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon);
conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint);
confs.Configurations.Add(conf);
var nonce = RandomUtils.GetUInt32();
var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce);
_LnConfigProvider.KeepConfig(configKey, confs);
return RedirectToAction(nameof(LNDGRPCServices), new { cryptoCode = cryptoCode, nonce = nonce });
}
private LightningConnectionString GetExternalLNDConnectionString(string cryptoCode, int index)
{
var connectionString = _Options.ExternalServicesByCryptoCode.GetServices<ExternalLNDGRPC>(cryptoCode).Skip(index).Select(c => c.ConnectionString).FirstOrDefault();
if (connectionString == null)
return null;
connectionString = connectionString.Clone();
if(connectionString.MacaroonFilePath != null)
{
try
{
connectionString.Macaroon = System.IO.File.ReadAllBytes(connectionString.MacaroonFilePath);
connectionString.MacaroonFilePath = null;
}
catch
{
Logging.Logs.Configuration.LogWarning($"{cryptoCode}: The macaroon file path of the external LND grpc config was not found ({connectionString.MacaroonFilePath})");
return null;
}
}
return connectionString;
2018-07-22 11:38:14 +02:00
}
2018-04-19 18:39:51 +02:00
[Route("server/theme")]
public async Task<IActionResult> Theme()
{
var data = (await _SettingsRepository.GetSettingAsync<ThemeSettings>()) ?? new ThemeSettings();
return View(data);
}
[Route("server/theme")]
[HttpPost]
public async Task<IActionResult> Theme(ThemeSettings settings)
{
await _SettingsRepository.UpdateSetting(settings);
2018-04-19 18:39:51 +02:00
TempData["StatusMessage"] = "Theme settings updated successfully";
return View(settings);
}
2017-09-27 07:18:09 +02:00
[Route("server/emails")]
[HttpPost]
public async Task<IActionResult> Emails(EmailsViewModel model, string command)
{
if (command == "Test")
{
try
{
2018-07-22 11:38:14 +02:00
if (!model.Settings.IsComplete())
2018-05-04 08:54:12 +02:00
{
model.StatusMessage = "Error: Required fields missing";
return View(model);
}
var client = model.Settings.CreateSmtpClient();
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
}
catch (Exception ex)
{
model.StatusMessage = "Error: " + ex.Message;
}
return View(model);
}
2018-05-04 08:54:12 +02:00
else // if(command == "Save")
{
await _SettingsRepository.UpdateSetting(model.Settings);
model.StatusMessage = "Email settings saved";
return View(model);
}
}
}
}