Can invite user to manage your store

This commit is contained in:
nicolas.dorier
2018-03-23 16:24:57 +09:00
parent 3b2cf2f1de
commit 39b34ff4ed
19 changed files with 514 additions and 174 deletions

View File

@@ -417,7 +417,7 @@ namespace BTCPayServer.Controllers
if (stores.Count() == 0)
{
StatusMessage = "Error: You need to create at least one store before creating a transaction";
return RedirectToAction(nameof(StoresController.ListStores), "Stores");
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
return View(new CreateInvoiceModel() { Stores = stores });
}
@@ -434,9 +434,18 @@ namespace BTCPayServer.Controllers
return View(model);
}
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
StatusMessage = null;
if (store.Role != StoreRoles.Owner)
{
StatusMessage = "Error: You need to be owner of this store to create an invoice";
}
if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0)
{
StatusMessage = "Error: You need to configure the derivation scheme in order to create an invoice";
}
if(StatusMessage != null)
{
return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new
{
storeId = store.Id

View File

@@ -15,6 +15,7 @@ namespace BTCPayServer.Controllers
{
public partial class StoresController
{
[HttpGet]
[Route("{storeId}/lightning/{cryptoCode}")]
public async Task<IActionResult> AddLightningNode(string storeId, string cryptoCode)

View File

@@ -27,10 +27,11 @@ namespace BTCPayServer.Controllers
{
[Route("stores")]
[Authorize(AuthenticationSchemes = "Identity.Application")]
[Authorize(Policy = "CanAccessStore")]
[Authorize(Policy = StorePolicies.OwnStore)]
[AutoValidateAntiforgeryToken]
public partial class StoresController : Controller
{
public string CreatedStoreId { get; set; }
public StoresController(
NBXplorerDashboard dashboard,
IServiceProvider serviceProvider,
@@ -83,32 +84,6 @@ namespace BTCPayServer.Controllers
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]
[Route("{storeId}/wallet/{cryptoCode}")]
public async Task<IActionResult> Wallet(string storeId, string cryptoCode)
@@ -125,79 +100,84 @@ namespace BTCPayServer.Controllers
private string GetStoreUrl(string storeId)
{
return HttpContext.Request.GetAbsoluteRoot() + "/stores/" + storeId + "/";
}
[HttpGet]
[Route("{storeId}/users")]
public async Task<IActionResult> StoreUsers(string storeId)
{
StoreUsersViewModel vm = new StoreUsersViewModel();
await FillUsers(storeId, vm);
return View(vm);
}
private async Task FillUsers(string storeId, StoreUsersViewModel vm)
{
var users = await _Repo.GetStoreUsers(storeId);
vm.StoreId = storeId;
vm.Users = users.Select(u => new StoreUsersViewModel.StoreUserViewModel()
{
Email = u.Email,
Id = u.Id,
Role = u.Role
}).ToList();
}
[HttpPost]
[Route("{storeId}/users")]
public async Task<IActionResult> StoreUsers(string storeId, StoreUsersViewModel vm)
{
await FillUsers(storeId, vm);
if(!ModelState.IsValid)
{
return View(vm);
}
var user = await _UserManager.FindByEmailAsync(vm.Email);
if(user == null)
{
ModelState.AddModelError(nameof(vm.Email), "User not found");
return View(vm);
}
if(!StoreRoles.AllRoles.Contains(vm.Role))
{
ModelState.AddModelError(nameof(vm.Role), "Invalid role");
return View(vm);
}
if(!await _Repo.AddStoreUser(storeId, user.Id, vm.Role))
{
ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store");
return View(vm);
}
StatusMessage = "User added successfully";
return RedirectToAction(nameof(StoreUsers));
}
[HttpGet]
public async Task<IActionResult> ListStores()
[Route("{storeId}/users/{userId}/delete")]
public async Task<IActionResult> DeleteStoreUser(string storeId, string userId)
{
StoresViewModel result = new StoresViewModel();
result.StatusMessage = StatusMessage;
var stores = await _Repo.GetStoresByUserId(GetUserId());
var balances = stores
.Select(s => s.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationStrategy>()
.Select(d => ((Wallet: _WalletProvider.GetWallet(d.Network),
DerivationStrategy: d.DerivationStrategyBase)))
.Where(_ => _.Wallet != null)
.Select(async _ => (await GetBalanceString(_)) + " " + _.Wallet.Network.CryptoCode))
.ToArray();
await Task.WhenAll(balances.SelectMany(_ => _));
for (int i = 0; i < stores.Length; i++)
{
var store = stores[i];
result.Stores.Add(new StoresViewModel.StoreViewModel()
{
Id = store.Id,
Name = store.StoreName,
WebSite = store.StoreWebsite,
Balances = balances[i].Select(t => t.Result).ToArray()
});
}
return View(result);
}
private static async Task<string> GetBalanceString((BTCPayWallet Wallet, DerivationStrategyBase DerivationStrategy) _)
{
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
try
{
return (await _.Wallet.GetBalance(_.DerivationStrategy, cts.Token)).ToString();
}
catch
{
return "--";
}
}
}
[HttpGet]
[Route("{storeId}/delete")]
public async Task<IActionResult> DeleteStore(string storeId)
{
var store = await _Repo.FindStore(storeId, GetUserId());
StoreUsersViewModel vm = new StoreUsersViewModel();
var store = await _Repo.FindStore(storeId, userId);
if (store == null)
return NotFound();
var user = await _UserManager.FindByIdAsync(userId);
if (user == null)
return NotFound();
return View("Confirm", new ConfirmModel()
{
Title = "Delete store " + store.StoreName,
Description = "This store will still be accessible to users sharing it",
Title = $"Remove store user",
Description = $"Are you sure to remove access to remove {store.Role} access to {user.Email}?",
Action = "Delete"
});
}
[HttpPost]
[Route("{storeId}/delete")]
public async Task<IActionResult> DeleteStorePost(string storeId)
[Route("{storeId}/users/{userId}/delete")]
public async Task<IActionResult> DeleteStoreUserPost(string storeId, string userId)
{
var userId = GetUserId();
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
await _Repo.RemoveStore(storeId, userId);
StatusMessage = "Store removed successfully";
return RedirectToAction(nameof(ListStores));
await _Repo.RemoveStoreUser(storeId, userId);
StatusMessage = "User removed successfully";
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
}
[HttpGet]
@@ -403,15 +383,17 @@ namespace BTCPayServer.Controllers
return View(model);
}
model.Label = model.Label ?? String.Empty;
if (storeId == null) // Permissions are not checked by Policy if the storeId is not passed by url
storeId = model.StoreId ?? storeId;
var userId = GetUserId();
if (userId == null)
return Unauthorized();
var store = await _Repo.FindStore(storeId, userId);
if (store == null)
return Unauthorized();
if (store.Role != StoreRoles.Owner)
{
storeId = model.StoreId;
var userId = GetUserId();
if (userId == null)
return Unauthorized();
var store = await _Repo.FindStore(storeId, userId);
if (store == null)
return Unauthorized();
StatusMessage = "Error: You need to be owner of this store to request pairing codes";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
var tokenRequest = new TokenRequest()
@@ -491,11 +473,13 @@ namespace BTCPayServer.Controllers
[Route("/api-access-request")]
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
{
if (pairingCode == null)
return NotFound();
var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
if (pairing == null)
{
StatusMessage = "Unknown pairing code";
return RedirectToAction(nameof(ListStores));
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
else
{
@@ -517,7 +501,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost]
[Route("api-access-request")]
[Route("/api-access-request")]
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
{
if (pairingCode == null)
@@ -527,6 +511,12 @@ namespace BTCPayServer.Controllers
if (store == null || pairing == null)
return NotFound();
if(store.Role != StoreRoles.Owner)
{
StatusMessage = "Error: You can't approve a pairing without being owner of the store";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
}
var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id);
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
{

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Controllers
{
[Route("stores")]
[Authorize(AuthenticationSchemes = "Identity.Application")]
[AutoValidateAntiforgeryToken]
public partial class UserStoresController : Controller
{
private StoreRepository _Repo;
private BTCPayNetworkProvider _NetworkProvider;
private UserManager<ApplicationUser> _UserManager;
private BTCPayWalletProvider _WalletProvider;
public UserStoresController(
UserManager<ApplicationUser> userManager,
BTCPayNetworkProvider networkProvider,
BTCPayWalletProvider walletProvider,
StoreRepository storeRepository)
{
_Repo = storeRepository;
_NetworkProvider = networkProvider;
_UserManager = userManager;
_WalletProvider = walletProvider;
}
[HttpGet]
[Route("{storeId}/delete")]
public async Task<IActionResult> DeleteStore(string storeId)
{
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
return View("Confirm", new ConfirmModel()
{
Title = "Delete store " + store.StoreName,
Description = "This store will still be accessible to users sharing it",
Action = "Delete"
});
}
[HttpGet]
[Route("create")]
public IActionResult CreateStore()
{
return View();
}
public string CreatedStoreId
{
get; set;
}
[HttpPost]
[Route("{storeId}/delete")]
public async Task<IActionResult> DeleteStorePost(string storeId)
{
var userId = GetUserId();
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
await _Repo.RemoveStore(storeId, userId);
StatusMessage = "Store removed successfully";
return RedirectToAction(nameof(ListStores));
}
[TempData]
public string StatusMessage { get; set; }
[HttpGet]
public async Task<IActionResult> ListStores()
{
StoresViewModel result = new StoresViewModel();
var stores = await _Repo.GetStoresByUserId(GetUserId());
var balances = stores
.Select(s => s.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationStrategy>()
.Select(d => ((Wallet: _WalletProvider.GetWallet(d.Network),
DerivationStrategy: d.DerivationStrategyBase)))
.Where(_ => _.Wallet != null)
.Select(async _ => (await GetBalanceString(_)) + " " + _.Wallet.Network.CryptoCode))
.ToArray();
await Task.WhenAll(balances.SelectMany(_ => _));
for (int i = 0; i < stores.Length; i++)
{
var store = stores[i];
result.Stores.Add(new StoresViewModel.StoreViewModel()
{
Id = store.Id,
Name = store.StoreName,
WebSite = store.StoreWebsite,
IsOwner = store.Role == StoreRoles.Owner,
Balances = store.Role == StoreRoles.Owner ? balances[i].Select(t => t.Result).ToArray() : Array.Empty<string>()
});
}
return View(result);
}
[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));
}
private static async Task<string> GetBalanceString((BTCPayWallet Wallet, DerivationStrategyBase DerivationStrategy) _)
{
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
try
{
return (await _.Wallet.GetBalance(_.DerivationStrategy, cts.Token)).ToString();
}
catch
{
return "--";
}
}
}
private string GetUserId()
{
return _UserManager.GetUserId(User);
}
}
}