mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
Decouple User from Store
This commit is contained in:
parent
79200412fd
commit
467ecd0923
@ -1,6 +1,7 @@
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Invoicing;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
@ -37,15 +38,18 @@ namespace BTCPayServer.Tests
|
||||
Password = "Kitten0@",
|
||||
});
|
||||
UserId = account.RegisteredUserId;
|
||||
StoreId = account.RegisteredStoreId;
|
||||
var manage = parent.PayTester.GetController<ManageController>(account.RegisteredUserId);
|
||||
await manage.Index(new Models.ManageViewModels.IndexViewModel()
|
||||
|
||||
var store = parent.PayTester.GetController<StoresController>(account.RegisteredUserId);
|
||||
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
|
||||
StoreId = store.CreatedStoreId;
|
||||
|
||||
await store.UpdateStore(StoreId, new StoreViewModel()
|
||||
{
|
||||
ExtPubKey = extKey.Neuter().ToString(),
|
||||
SpeedPolicy = SpeedPolicy.MediumSpeed
|
||||
});
|
||||
Assert.IsType<ViewResult>(await manage.AskPairing(pairingCode.ToString()));
|
||||
await manage.Pairs(pairingCode.ToString());
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
}
|
||||
|
||||
public Bitpay BitPay
|
||||
|
@ -148,9 +148,11 @@ namespace BTCPayServer.Authentication
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteToken(string sin, string tokenName)
|
||||
public async Task<bool> DeleteToken(string sin, string tokenName, string storeId)
|
||||
{
|
||||
var token = await GetToken(sin, tokenName);
|
||||
if(token == null || (token.PairedId != null && token.PairedId != storeId))
|
||||
return false;
|
||||
using(var tx = _Engine.GetTransaction())
|
||||
{
|
||||
tx.RemoveKey<string>($"T_{sin}", tokenName);
|
||||
@ -158,6 +160,7 @@ namespace BTCPayServer.Authentication
|
||||
tx.RemoveKey<string>($"TbP_" + token.PairedId, token.Value);
|
||||
tx.Commit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<BitTokenEntity> GetToken(string sin, string tokenName)
|
||||
|
@ -234,8 +234,6 @@ namespace BTCPayServer.Controllers
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
RegisteredUserId = user.Id;
|
||||
var store = await storeRepository.CreateStore(user.Id);
|
||||
RegisteredStoreId = store.Id;
|
||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User created a new account with password.");
|
||||
@ -256,14 +254,6 @@ namespace BTCPayServer.Controllers
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test property
|
||||
/// </summary>
|
||||
public string RegisteredStoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Invoicing;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
@ -89,18 +90,18 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Invoices")]
|
||||
[Route("invoices")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 20)
|
||||
{
|
||||
var store = await FindStore(User);
|
||||
var model = new InvoicesModel();
|
||||
foreach(var invoice in await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
TextSearch = searchTerm,
|
||||
Count = count,
|
||||
Skip = skip,
|
||||
StoreId = store.Id
|
||||
UserId = GetUserId()
|
||||
}))
|
||||
{
|
||||
model.SearchTerm = searchTerm;
|
||||
@ -119,7 +120,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Invoices/Create")]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public IActionResult CreateInvoice()
|
||||
{
|
||||
@ -127,7 +129,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Invoices/Create")]
|
||||
[Route("invoices/create")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
|
||||
{
|
||||
@ -135,7 +138,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var store = await FindStore(User);
|
||||
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
|
||||
var result = await CreateInvoiceCore(new Invoice()
|
||||
{
|
||||
Price = model.Amount.Value,
|
||||
@ -154,6 +157,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public IActionResult SearchInvoice(InvoicesModel invoices)
|
||||
{
|
||||
@ -172,14 +176,9 @@ namespace BTCPayServer.Controllers
|
||||
set;
|
||||
}
|
||||
|
||||
private async Task<StoreData> FindStore(ClaimsPrincipal user)
|
||||
private string GetUserId()
|
||||
{
|
||||
var usr = await _UserManager.GetUserAsync(User);
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_UserManager.GetUserId(User)}'.");
|
||||
}
|
||||
return await _StoreRepository.GetStore(usr.Id);
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ namespace BTCPayServer.Controllers
|
||||
var entity = new InvoiceEntity
|
||||
{
|
||||
InvoiceTime = DateTimeOffset.UtcNow,
|
||||
DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This user has not configured his derivation strategy")
|
||||
DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy")
|
||||
};
|
||||
EmailAddressAttribute emailValidator = new EmailAddressAttribute();
|
||||
entity.ExpirationTime = entity.InvoiceTime + TimeSpan.FromMinutes(15.0);
|
||||
|
@ -34,7 +34,7 @@ namespace BTCPayServer.Controllers
|
||||
TokenRepository _TokenRepository;
|
||||
private readonly BTCPayWallet _Wallet;
|
||||
IHostingEnvironment _Env;
|
||||
IExternalUrlProvider _UrlProvider;
|
||||
private readonly IExternalUrlProvider _UrlProvider;
|
||||
StoreRepository _StoreRepository;
|
||||
|
||||
|
||||
@ -78,7 +78,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
var store = await _StoreRepository.GetStore(user.Id);
|
||||
|
||||
var model = new IndexViewModel
|
||||
{
|
||||
@ -86,11 +85,7 @@ namespace BTCPayServer.Controllers
|
||||
Email = user.Email,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsEmailConfirmed = user.EmailConfirmed,
|
||||
StatusMessage = StatusMessage,
|
||||
ExtPubKey = store.DerivationStrategy,
|
||||
StoreWebsite = store.StoreWebsite,
|
||||
StoreName = store.StoreName,
|
||||
SpeedPolicy = store.SpeedPolicy
|
||||
StatusMessage = StatusMessage
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
@ -111,33 +106,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
var store = await _StoreRepository.GetStore(user.Id);
|
||||
|
||||
if(model.ExtPubKey != store.DerivationStrategy)
|
||||
{
|
||||
store.DerivationStrategy = model.ExtPubKey;
|
||||
await _Wallet.TrackAsync(store.DerivationStrategy);
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if(model.SpeedPolicy != store.SpeedPolicy)
|
||||
{
|
||||
store.SpeedPolicy = model.SpeedPolicy;
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if(model.StoreName != store.StoreName)
|
||||
{
|
||||
store.StoreName = model.StoreName;
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if(model.StoreWebsite != store.StoreWebsite)
|
||||
{
|
||||
store.StoreWebsite = model.StoreWebsite;
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
|
||||
var email = user.Email;
|
||||
if(model.Email != email)
|
||||
{
|
||||
@ -161,7 +130,6 @@ namespace BTCPayServer.Controllers
|
||||
if(needUpdate)
|
||||
{
|
||||
var result = await _userManager.UpdateAsync(user);
|
||||
await _StoreRepository.UpdateStore(store);
|
||||
if(!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred updating user with ID '{user.Id}'.");
|
||||
@ -352,45 +320,6 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-access-request")]
|
||||
public async Task<IActionResult> AskPairing(string pairingCode)
|
||||
{
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if(pairing == null)
|
||||
{
|
||||
StatusMessage = "Unknown pairing code";
|
||||
return RedirectToAction(nameof(Pairs));
|
||||
}
|
||||
else
|
||||
{
|
||||
return View(new PairingModel()
|
||||
{
|
||||
Id = pairing.Id,
|
||||
Facade = pairing.Facade,
|
||||
Label = pairing.Label,
|
||||
SIN = pairing.SIN
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Pairs(string pairingCode)
|
||||
{
|
||||
var store = await _StoreRepository.GetStore(_userManager.GetUserId(User));
|
||||
if(pairingCode != null && await _TokenRepository.PairWithAsync(pairingCode, store.Id))
|
||||
{
|
||||
StatusMessage = "Pairing is successfull";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = "Pairing failed";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
|
||||
@ -524,74 +453,7 @@ namespace BTCPayServer.Controllers
|
||||
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
|
||||
return RedirectToAction(nameof(GenerateRecoveryCodes));
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> CreateToken(CreateTokenViewModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
string storeId = await GetStoreId();
|
||||
|
||||
var url = new Uri(_UrlProvider.GetAbsolute(""));
|
||||
var bitpay = new Bitpay(new NBitcoin.Key(), url);
|
||||
var pairing = await bitpay.RequestClientAuthorizationAsync(model.Label, new Facade(model.Facade));
|
||||
var link = pairing.CreateLink(url).ToString();
|
||||
await _TokenRepository.PairWithAsync(pairing.ToString(), storeId);
|
||||
StatusMessage = "New access token paired to this store";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
|
||||
private async Task<string> GetStoreId()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if(user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
return (await _StoreRepository.GetStore(user.Id)).Id;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult CreateToken()
|
||||
{
|
||||
var model = new CreateTokenViewModel();
|
||||
model.Facade = "merchant";
|
||||
if(_Env.IsDevelopment())
|
||||
{
|
||||
model.PublicKey = new Key().PubKey.ToHex();
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> DeleteToken(string name, string sin)
|
||||
{
|
||||
await _TokenRepository.DeleteToken(sin, name);
|
||||
StatusMessage = "Token revoked";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListTokens()
|
||||
{
|
||||
var model = new TokensViewModel();
|
||||
var tokens = await _TokenRepository.GetTokensByPairedIdAsync(await GetStoreId());
|
||||
model.StatusMessage = StatusMessage;
|
||||
model.Tokens = tokens.Select(t => new TokenViewModel()
|
||||
{
|
||||
Facade = t.Name,
|
||||
Label = t.Label,
|
||||
SIN = t.SIN,
|
||||
Id = t.Value
|
||||
}).ToArray();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult ResetAuthenticatorWarning()
|
||||
{
|
||||
|
298
BTCPayServer/Controllers/StoresController.cs
Normal file
298
BTCPayServer/Controllers/StoresController.cs
Normal file
@ -0,0 +1,298 @@
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Stores;
|
||||
using BTCPayServer.Wallet;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("stores")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Authorize(Policy = "CanAccessStore")]
|
||||
public class StoresController : Controller
|
||||
{
|
||||
public StoresController(
|
||||
StoreRepository repo,
|
||||
TokenRepository tokenRepo,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
AccessTokenController tokenController,
|
||||
BTCPayWallet wallet,
|
||||
IHostingEnvironment env)
|
||||
{
|
||||
_Repo = repo;
|
||||
_TokenRepository = tokenRepo;
|
||||
_UserManager = userManager;
|
||||
_TokenController = tokenController;
|
||||
_Wallet = wallet;
|
||||
_Env = env;
|
||||
}
|
||||
BTCPayWallet _Wallet;
|
||||
AccessTokenController _TokenController;
|
||||
StoreRepository _Repo;
|
||||
TokenRepository _TokenRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
IHostingEnvironment _Env;
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("create")]
|
||||
public IActionResult CreateStore()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("create")]
|
||||
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
|
||||
CreatedStoreId = store.Id;
|
||||
StatusMessage = "Store successfully created";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
|
||||
public string CreatedStoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListStores()
|
||||
{
|
||||
StoresViewModel result = new StoresViewModel();
|
||||
result.StatusMessage = StatusMessage;
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
foreach(var store in stores)
|
||||
{
|
||||
result.Stores.Add(new StoresViewModel.StoreViewModel()
|
||||
{
|
||||
Id = store.Id,
|
||||
Name = store.StoreName,
|
||||
WebSite = store.StoreWebsite
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
|
||||
var vm = new StoreViewModel();
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.ExtPubKey = store.DerivationStrategy;
|
||||
vm.StatusMessage = StatusMessage;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
|
||||
bool needUpdate = false;
|
||||
if(store.SpeedPolicy != model.SpeedPolicy)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.SpeedPolicy = model.SpeedPolicy;
|
||||
}
|
||||
if(store.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.StoreName = model.StoreName;
|
||||
}
|
||||
if(store.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
|
||||
if(store.DerivationStrategy != model.ExtPubKey)
|
||||
{
|
||||
needUpdate = true;
|
||||
try
|
||||
{
|
||||
await _Wallet.TrackAsync(model.ExtPubKey);
|
||||
store.DerivationStrategy = model.ExtPubKey;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.ExtPubKey), "Invalid Derivation Scheme");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
if(needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "Store successfully updated";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
{
|
||||
storeId = storeId
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/Tokens")]
|
||||
public async Task<IActionResult> ListTokens(string storeId)
|
||||
{
|
||||
var model = new TokensViewModel();
|
||||
var tokens = await _TokenRepository.GetTokensByPairedIdAsync(storeId);
|
||||
model.StatusMessage = StatusMessage;
|
||||
model.Tokens = tokens.Select(t => new TokenViewModel()
|
||||
{
|
||||
Facade = t.Name,
|
||||
Label = t.Label,
|
||||
SIN = t.SIN,
|
||||
Id = t.Value
|
||||
}).ToArray();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var pairingCode = await _TokenController.GetPairingCode(new PairingCodeRequest()
|
||||
{
|
||||
Facade = model.Facade,
|
||||
Label = model.Label,
|
||||
Id = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
|
||||
});
|
||||
|
||||
return RedirectToAction(nameof(RequestPairing), new
|
||||
{
|
||||
pairingCode = pairingCode.Data[0].PairingCode,
|
||||
selectedStore = storeId
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public IActionResult CreateToken()
|
||||
{
|
||||
var model = new CreateTokenViewModel();
|
||||
model.Facade = "merchant";
|
||||
if(_Env.IsDevelopment())
|
||||
{
|
||||
model.PublicKey = new Key().PubKey.ToHex();
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}/Tokens/Delete")]
|
||||
public async Task<IActionResult> DeleteToken(string storeId, string name, string sin)
|
||||
{
|
||||
if(await _TokenRepository.DeleteToken(sin, name, storeId))
|
||||
StatusMessage = "Token revoked";
|
||||
else
|
||||
StatusMessage = "Failure to revoke this token";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-access-request")]
|
||||
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
|
||||
{
|
||||
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
|
||||
if(pairing == null)
|
||||
{
|
||||
StatusMessage = "Unknown pairing code";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
else
|
||||
{
|
||||
var stores = await _Repo.GetStoresByUserId(GetUserId());
|
||||
return View(new PairingModel()
|
||||
{
|
||||
Id = pairing.Id,
|
||||
Facade = pairing.Facade,
|
||||
Label = pairing.Label,
|
||||
SIN = pairing.SIN,
|
||||
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
|
||||
Stores = stores.Select(s => new PairingModel.StoreViewModel()
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("api-access-request")]
|
||||
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
||||
{
|
||||
var store = await _Repo.FindStore(selectedStore, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
if(pairingCode != null && await _TokenRepository.PairWithAsync(pairingCode, store.Id))
|
||||
{
|
||||
StatusMessage = "Pairing is successfull";
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = "Pairing failed";
|
||||
return RedirectToAction(nameof(ListTokens), new
|
||||
{
|
||||
storeId = store.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,11 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<UserStore> UserStore
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
var options = optionsBuilder.Options.FindExtension<SqliteOptionsExtension>();
|
||||
|
@ -2,6 +2,7 @@
|
||||
using BTCPayServer.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -44,5 +45,11 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,5 +25,10 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Role
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ using Microsoft.AspNetCore.Identity;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Stores;
|
||||
using BTCPayServer.Controllers;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -30,6 +34,20 @@ namespace BTCPayServer.Hosting
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddAuthorization(o =>
|
||||
{
|
||||
o.AddPolicy("CanAccessStore", builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
|
||||
});
|
||||
|
||||
o.AddPolicy("OwnStore", builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
|
||||
});
|
||||
});
|
||||
services.AddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||
services.AddTransient<AccessTokenController>();
|
||||
// Add application services.
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
|
||||
@ -42,6 +60,7 @@ namespace BTCPayServer.Hosting
|
||||
})
|
||||
.AddJsonFormatters()
|
||||
.AddFormatterMappings();
|
||||
|
||||
services.AddMvc();
|
||||
}
|
||||
public void Configure(
|
||||
@ -67,4 +86,45 @@ namespace BTCPayServer.Hosting
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public OwnStoreAuthorizationRequirement()
|
||||
{
|
||||
}
|
||||
|
||||
public OwnStoreAuthorizationRequirement(string role)
|
||||
{
|
||||
Role = role;
|
||||
}
|
||||
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_StoreRepository = storeRepository;
|
||||
_UserManager = userManager;
|
||||
}
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
|
||||
{
|
||||
object storeId = null;
|
||||
if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
||||
context.Succeed(requirement);
|
||||
else
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User));
|
||||
if(store != null)
|
||||
if(requirement.Role == null || requirement.Role == store.Role)
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,6 +225,11 @@ namespace BTCPayServer.Invoicing
|
||||
query = query.Where(i => i.StoreDataId == queryObject.StoreId);
|
||||
}
|
||||
|
||||
if(queryObject.UserId != null)
|
||||
{
|
||||
query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId));
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(queryObject.TextSearch))
|
||||
{
|
||||
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch));
|
||||
@ -340,6 +345,10 @@ namespace BTCPayServer.Invoicing
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string UserId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string TextSearch
|
||||
{
|
||||
get; set;
|
||||
|
@ -12,7 +12,7 @@ using System;
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20170901023716_Init")]
|
||||
[Migration("20170913143004_Init")]
|
||||
partial class Init
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -107,6 +107,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
@ -199,7 +199,8 @@ namespace BTCPayServer.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StoreDataId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
StoreDataId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Role = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
@ -106,6 +106,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
@ -14,6 +14,12 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
|
@ -12,42 +12,26 @@ namespace BTCPayServer.Models.ManageViewModels
|
||||
{
|
||||
public string Username { get; set; }
|
||||
|
||||
public bool IsEmailConfirmed { get; set; }
|
||||
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[MaxLength(50)]
|
||||
public string Email { get; set; }
|
||||
|
||||
[ExtPubKeyValidator]
|
||||
public string ExtPubKey { get; set; }
|
||||
|
||||
[Display(Name = "Store Name")]
|
||||
[MaxLength(50)]
|
||||
public string StoreName
|
||||
public string Email
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public bool IsEmailConfirmed { get; set; }
|
||||
|
||||
[Phone]
|
||||
[Display(Name = "Phone number")]
|
||||
[MaxLength(50)]
|
||||
public string PhoneNumber { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
[Url]
|
||||
[Display(Name = "Store Website")]
|
||||
public string StoreWebsite
|
||||
public string StatusMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get; set;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ManageViewModels
|
||||
{
|
||||
public class PairingModel
|
||||
{
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Facade
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
19
BTCPayServer/Models/StoreViewModels/CreateStoreViewModel.cs
Normal file
19
BTCPayServer/Models/StoreViewModels/CreateStoreViewModel.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class CreateStoreViewModel
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[MinLength(1)]
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
51
BTCPayServer/Models/StoreViewModels/PairingModel.cs
Normal file
51
BTCPayServer/Models/StoreViewModels/PairingModel.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class PairingModel
|
||||
{
|
||||
public class StoreViewModel
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Label
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Facade
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string SIN
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public StoreViewModel[] Stores
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Pair to")]
|
||||
[Required]
|
||||
public string SelectedStore
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
47
BTCPayServer/Models/StoreViewModels/StoreViewModel.cs
Normal file
47
BTCPayServer/Models/StoreViewModels/StoreViewModel.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using BTCPayServer.Invoicing;
|
||||
using BTCPayServer.Validations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoreViewModel
|
||||
{
|
||||
[Display(Name = "Store Name")]
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[MinLength(1)]
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Url]
|
||||
[Display(Name = "Store Website")]
|
||||
public string StoreWebsite
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ExtPubKeyValidator]
|
||||
public string ExtPubKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
36
BTCPayServer/Models/StoreViewModels/StoresViewModel.cs
Normal file
36
BTCPayServer/Models/StoreViewModels/StoresViewModel.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoresViewModel
|
||||
{
|
||||
public string StatusMessage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public List<StoreViewModel> Stores
|
||||
{
|
||||
get; set;
|
||||
} = new List<StoreViewModel>();
|
||||
public class StoreViewModel
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string WebSite
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ManageViewModels
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class CreateTokenViewModel
|
||||
{
|
@ -26,18 +26,55 @@ namespace BTCPayServer.Stores
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StoreData> CreateStore(string userId)
|
||||
public async Task<StoreData> FindStore(string storeId, string userId)
|
||||
{
|
||||
if(userId == null)
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
return (await ctx
|
||||
.UserStore
|
||||
.Where(us => us.ApplicationUserId == userId && us.StoreDataId == storeId)
|
||||
.Select(us => new
|
||||
{
|
||||
Store = us.StoreData,
|
||||
Role = us.Role
|
||||
}).ToArrayAsync())
|
||||
.Select(us =>
|
||||
{
|
||||
us.Store.Role = us.Role;
|
||||
return us.Store;
|
||||
}).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StoreData[]> GetStoresByUserId(string userId)
|
||||
{
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
return await ctx.UserStore
|
||||
.Where(u => u.ApplicationUserId == userId)
|
||||
.Select(u => u.StoreData)
|
||||
.ToArrayAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StoreData> CreateStore(string ownerId, string name)
|
||||
{
|
||||
if(string.IsNullOrEmpty(name))
|
||||
throw new ArgumentException("name should not be empty", nameof(name));
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
StoreData store = new StoreData
|
||||
{
|
||||
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32))
|
||||
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)),
|
||||
StoreName = name
|
||||
};
|
||||
var userStore = new UserStore
|
||||
{
|
||||
StoreDataId = store.Id,
|
||||
ApplicationUserId = userId
|
||||
ApplicationUserId = ownerId,
|
||||
Role = "Owner"
|
||||
};
|
||||
await ctx.AddAsync(store).ConfigureAwait(false);
|
||||
await ctx.AddAsync(userStore).ConfigureAwait(false);
|
||||
@ -46,17 +83,6 @@ namespace BTCPayServer.Stores
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StoreData> GetStore(string userId)
|
||||
{
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
return await ctx
|
||||
.Stores
|
||||
.Where(s => s.UserStores.Any(us => us.ApplicationUserId == userId))
|
||||
.FirstOrDefaultAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateStore(StoreData store)
|
||||
{
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
|
@ -43,7 +43,7 @@
|
||||
<input type="submit" value="Create" class="btn btn-default" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="Index">Back to List</a>
|
||||
<a asp-action="ListInvoices">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,28 +0,0 @@
|
||||
@model PairingModel
|
||||
@{
|
||||
ViewData["Title"] = "Pairing permission";
|
||||
}
|
||||
|
||||
|
||||
<h4>Pairing permission:</h4>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<td>@Model.Label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Facade</th>
|
||||
<td>@Model.Facade</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SIN</th>
|
||||
<td>@Model.SIN</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<form asp-action="Pairs" method="post">
|
||||
<div>
|
||||
<input type="hidden" name="pairingCode" value="@Model.Id" />
|
||||
<button type="submit" class="btn btn-info" title="Approve this pairing demand">Approve</button>
|
||||
</div>
|
||||
</form>
|
@ -19,25 +19,6 @@
|
||||
<label asp-for="Username"></label>
|
||||
<input asp-for="Username" class="form-control" disabled />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="StoreName"></label>
|
||||
<input asp-for="StoreName" class="form-control" />
|
||||
<span asp-validation-for="StoreName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="StoreWebsite"></label>
|
||||
<input asp-for="StoreWebsite" class="form-control" />
|
||||
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SpeedPolicy"></label>
|
||||
<select asp-for="SpeedPolicy" class="form-control">
|
||||
<option value="0">Is unconfirmed</option>
|
||||
<option value="1">Has at least 1 confirmation</option>
|
||||
<option value="2">Has at least 6 confirmations</option>
|
||||
</select>
|
||||
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email"></label>
|
||||
@if(Model.IsEmailConfirmed)
|
||||
@ -54,11 +35,6 @@
|
||||
}
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ExtPubKey"></label>
|
||||
<input asp-for="ExtPubKey" class="form-control" />
|
||||
<span asp-validation-for="ExtPubKey" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="PhoneNumber"></label>
|
||||
<input asp-for="PhoneNumber" class="form-control" />
|
||||
|
@ -1,41 +0,0 @@
|
||||
@model TokensViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Access Tokens";
|
||||
ViewData.AddActivePage(ManageNavPages.Tokens);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<p>You can allow a public key to access the API of this store</p>
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<a asp-action="CreateToken" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span>Create a new token</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>SIN</th>
|
||||
<th>Facade</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var token in Model.Tokens)
|
||||
{
|
||||
<tr>
|
||||
<td>@token.Label</td>
|
||||
<td>@token.SIN</td>
|
||||
<td>@token.Facade</td>
|
||||
<td>
|
||||
<form asp-action="DeleteToken" method="post">
|
||||
<input type="hidden" name="name" value="@token.Facade">
|
||||
<input type="hidden" name="sin" value="@token.SIN">
|
||||
<button type="submit" class="btn btn-danger" role="button">Revoke</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -50,7 +50,8 @@
|
||||
<ul class="navbar-nav ml-auto">
|
||||
@if(SignInManager.IsSignedIn(User))
|
||||
{
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Invoices" asp-action="Index" class="nav-link js-scroll-trigger">Invoices</a></li>
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Stores" asp-action="ListStores" class="nav-link js-scroll-trigger">Stores</a></li>
|
||||
<li class="nav-item"><a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" class="nav-link js-scroll-trigger">Invoices</a></li>
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage" class="nav-link js-scroll-trigger">My settings</a>
|
||||
</li>
|
||||
|
31
BTCPayServer/Views/Stores/CreateStore.cshtml
Normal file
31
BTCPayServer/Views/Stores/CreateStore.cshtml
Normal file
@ -0,0 +1,31 @@
|
||||
@model BTCPayServer.Models.StoreViewModels.CreateStoreViewModel
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Create a new store";
|
||||
}
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">@ViewData["Title"]</h2>
|
||||
<hr class="primary">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<form asp-action="CreateStore">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Name" class="control-label"></label>*
|
||||
<input asp-for="Name" class="form-control" />
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Create" class="btn btn-default" />
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListStores">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -1,7 +1,7 @@
|
||||
@model CreateTokenViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Create a new token";
|
||||
ViewData.AddActivePage(ManageNavPages.Tokens);
|
||||
ViewData.AddActivePage(StoreNavPages.Tokens);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
@ -28,7 +28,7 @@
|
||||
<span asp-validation-for="Facade" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Add token" class="btn btn-default" />
|
||||
<input type="submit" value="Request pairing" class="btn btn-default" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
52
BTCPayServer/Views/Stores/ListStores.cshtml
Normal file
52
BTCPayServer/Views/Stores/ListStores.cshtml
Normal file
@ -0,0 +1,52 @@
|
||||
@model StoresViewModel
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Stores";
|
||||
}
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">@ViewData["Title"]</h2>
|
||||
<hr class="primary">
|
||||
<p>Create and manage store settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<a asp-action="CreateStore" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span> Create a new store</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Website</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var store in Model.Stores)
|
||||
{
|
||||
<tr>
|
||||
<td>@store.Name</td>
|
||||
<td>
|
||||
@if(!string.IsNullOrEmpty(store.WebSite))
|
||||
{
|
||||
<a href="@store.WebSite">@store.WebSite</a>
|
||||
}</td>
|
||||
<td><a asp-action="UpdateStore" asp-route-storeId="@store.Id">Settings</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
37
BTCPayServer/Views/Stores/ListTokens.cshtml
Normal file
37
BTCPayServer/Views/Stores/ListTokens.cshtml
Normal file
@ -0,0 +1,37 @@
|
||||
@model TokensViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Access Tokens";
|
||||
ViewData.AddActivePage(StoreNavPages.Tokens);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<p>You can allow a public key to access the API of this store</p>
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
<a asp-action="CreateToken" class="btn btn-success" role="button"><span class="glyphicon glyphicon-plus"></span>Create a new token</a>
|
||||
<table class="table">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>SIN</th>
|
||||
<th>Facade</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var token in Model.Tokens)
|
||||
{
|
||||
<tr>
|
||||
<td>@token.Label</td>
|
||||
<td>@token.SIN</td>
|
||||
<td>@token.Facade</td>
|
||||
<td>
|
||||
<form asp-action="DeleteToken" method="post">
|
||||
<input type="hidden" name="name" value="@token.Facade">
|
||||
<input type="hidden" name="sin" value="@token.SIN">
|
||||
<button type="submit" class="btn btn-danger" role="button">Revoke</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
44
BTCPayServer/Views/Stores/RequestPairing.cshtml
Normal file
44
BTCPayServer/Views/Stores/RequestPairing.cshtml
Normal file
@ -0,0 +1,44 @@
|
||||
@model PairingModel
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Pairing permission";
|
||||
}
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">@ViewData["Title"]</h2>
|
||||
<hr class="primary">
|
||||
<p>Create and manage store settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<td>@Model.Label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Facade</th>
|
||||
<td>@Model.Facade</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SIN</th>
|
||||
<td>@Model.SIN</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form asp-action="Pair" method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="SelectedStore"></label>
|
||||
<select asp-for="SelectedStore" asp-items="@(new SelectList(Model.Stores,"Id","Name"))" class="form-control"></select>
|
||||
<span asp-validation-for="SelectedStore" class="text-danger"></span>
|
||||
</div>
|
||||
<input type="hidden" name="pairingCode" value="@Model.Id" />
|
||||
<button type="submit" class="btn btn-info" title="Approve this pairing demand">Approve</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
30
BTCPayServer/Views/Stores/StoreNavPages.cs
Normal file
30
BTCPayServer/Views/Stores/StoreNavPages.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer.Views.Stores
|
||||
{
|
||||
public static class StoreNavPages
|
||||
{
|
||||
public static string ActivePageKey => "ActivePage";
|
||||
public static string Index => "Index";
|
||||
|
||||
|
||||
public static string Tokens => "Tokens";
|
||||
|
||||
public static string TokensNavClass(ViewContext viewContext) => PageNavClass(viewContext, Tokens);
|
||||
|
||||
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
|
||||
|
||||
public static string PageNavClass(ViewContext viewContext, string page)
|
||||
{
|
||||
var activePage = viewContext.ViewData["ActivePage"] as string;
|
||||
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
|
||||
}
|
||||
|
||||
public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
|
||||
}
|
||||
}
|
49
BTCPayServer/Views/Stores/UpdateStore.cshtml
Normal file
49
BTCPayServer/Views/Stores/UpdateStore.cshtml
Normal file
@ -0,0 +1,49 @@
|
||||
@model StoreViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Profile";
|
||||
ViewData.AddActivePage(BTCPayServer.Views.Stores.StoreNavPages.Index);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
@Html.Partial("_StatusMessage", Model.StatusMessage)
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="StoreName"></label>
|
||||
<input asp-for="StoreName" class="form-control" />
|
||||
<span asp-validation-for="StoreName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="StoreWebsite"></label>
|
||||
<input asp-for="StoreWebsite" class="form-control" />
|
||||
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SpeedPolicy"></label>
|
||||
<select asp-for="SpeedPolicy" class="form-control">
|
||||
<option value="0">Is unconfirmed</option>
|
||||
<option value="1">Has at least 1 confirmation</option>
|
||||
<option value="2">Has at least 6 confirmations</option>
|
||||
</select>
|
||||
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ExtPubKey"></label>
|
||||
<input asp-for="ExtPubKey" class="form-control" />
|
||||
<span asp-validation-for="ExtPubKey" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
30
BTCPayServer/Views/Stores/_Layout.cshtml
Normal file
30
BTCPayServer/Views/Stores/_Layout.cshtml
Normal file
@ -0,0 +1,30 @@
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">Manage your store</h2>
|
||||
<hr class="primary">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
@await Html.PartialAsync("_StoreNav")
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
@RenderBody()
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@section Scripts {
|
||||
@RenderSection("Scripts", required: false)
|
||||
}
|
||||
|
11
BTCPayServer/Views/Stores/_StoreNav.cshtml
Normal file
11
BTCPayServer/Views/Stores/_StoreNav.cshtml
Normal file
@ -0,0 +1,11 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@{
|
||||
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
|
||||
}
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="@StoreNavPages.IndexNavClass(ViewContext)"><a asp-action="UpdateStore">Information</a></li>
|
||||
<li class="@StoreNavPages.TokensNavClass(ViewContext)"><a asp-action="ListTokens">Access Tokens</a></li>
|
||||
</ul>
|
||||
|
1
BTCPayServer/Views/Stores/_ViewImports.cshtml
Normal file
1
BTCPayServer/Views/Stores/_ViewImports.cshtml
Normal file
@ -0,0 +1 @@
|
||||
@using BTCPayServer.Views.Stores
|
@ -4,4 +4,5 @@
|
||||
@using BTCPayServer.Models.AccountViewModels
|
||||
@using BTCPayServer.Models.InvoicingModels
|
||||
@using BTCPayServer.Models.ManageViewModels
|
||||
@using BTCPayServer.Models.StoreViewModels
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
Loading…
Reference in New Issue
Block a user