From a7e10c0fb9cdd271c3c603f89212b91de716f620 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Fri, 13 Oct 2017 18:06:46 +0900 Subject: [PATCH] Can't pair same SIN to different store --- BTCPayServer.Tests/UnitTest1.cs | 20 +++++++++++ .../Authentication/TokenRepository.cs | 33 ++++++++++++++----- .../Controllers/AccessTokenController.cs | 6 ++-- BTCPayServer/Controllers/StoresController.cs | 10 ++++-- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 0e016e37d..a629012fc 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -15,6 +15,7 @@ using System.IO; using Newtonsoft.Json.Linq; using BTCPayServer.Controllers; using Microsoft.AspNetCore.Mvc; +using BTCPayServer.Authentication; namespace BTCPayServer.Tests { @@ -168,6 +169,25 @@ namespace BTCPayServer.Tests } } + [Fact] + public void CantPairTwiceWithSamePubkey() + { + using(var tester = ServerTester.Create()) + { + tester.Start(); + var acc = tester.NewAccount(); + acc.Register(); + var store = acc.CreateStore(); + var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant); + Assert.IsType(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult()); + + pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant); + var store2 = acc.CreateStore(); + store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult(); + Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage); + } + } + [Fact] public void InvoiceFlowThroughDifferentStatesCorrectly() { diff --git a/BTCPayServer/Authentication/TokenRepository.cs b/BTCPayServer/Authentication/TokenRepository.cs index b06696d52..642dbc2b5 100644 --- a/BTCPayServer/Authentication/TokenRepository.cs +++ b/BTCPayServer/Authentication/TokenRepository.cs @@ -13,6 +13,14 @@ using System.Linq; namespace BTCPayServer.Authentication { + public enum PairingResult + { + Partial, + Complete, + ReusedKey, + Expired + } + public class TokenRepository { ApplicationDbContextFactory _Factory; @@ -79,40 +87,45 @@ namespace BTCPayServer.Authentication } } - public async Task PairWithStoreAsync(string pairingCodeId, string storeId) + public async Task PairWithStoreAsync(string pairingCodeId, string storeId) { using(var ctx = _Factory.CreateContext()) { var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId); if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow) - return false; + return PairingResult.Expired; pairingCode.StoreDataId = storeId; - await ActivateIfComplete(ctx, pairingCode); + var result = await ActivateIfComplete(ctx, pairingCode); await ctx.SaveChangesAsync(); + return result; } - return true; } - public async Task PairWithSINAsync(string pairingCodeId, string sin) + public async Task PairWithSINAsync(string pairingCodeId, string sin) { using(var ctx = _Factory.CreateContext()) { var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId); if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow) - return false; + return PairingResult.Expired; pairingCode.SIN = sin; - await ActivateIfComplete(ctx, pairingCode); + var result = await ActivateIfComplete(ctx, pairingCode); await ctx.SaveChangesAsync(); + return result; } - return true; } - private async Task ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode) + private async Task ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode) { if(!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId)) { ctx.PairingCodes.Remove(pairingCode); + + // Can have concurrency issues... but no harm can be done + var alreadyUsed = await ctx.PairedSINData.Where(p => p.SIN == pairingCode.SIN && p.StoreDataId != pairingCode.StoreDataId).AnyAsync(); + if(alreadyUsed) + return PairingResult.ReusedKey; await ctx.PairedSINData.AddAsync(new PairedSINData() { Id = pairingCode.TokenValue, @@ -122,7 +135,9 @@ namespace BTCPayServer.Authentication StoreDataId = pairingCode.StoreDataId, SIN = pairingCode.SIN }); + return PairingResult.Complete; } + return PairingResult.Partial; } diff --git a/BTCPayServer/Controllers/AccessTokenController.cs b/BTCPayServer/Controllers/AccessTokenController.cs index 2afa5389e..4e8601d92 100644 --- a/BTCPayServer/Controllers/AccessTokenController.cs +++ b/BTCPayServer/Controllers/AccessTokenController.cs @@ -57,8 +57,10 @@ namespace BTCPayServer.Controllers pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode); pairingEntity.SIN = sin; - if(!await _TokenRepository.PairWithSINAsync(request.PairingCode, sin)) - throw new BitpayHttpException(400, "Unknown pairing code"); + + var result = await _TokenRepository.PairWithSINAsync(request.PairingCode, sin); + if(result != PairingResult.Complete && result != PairingResult.Partial) + throw new BitpayHttpException(400, $"Error while pairing ({result})"); } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index a9cd7bcf4..14e0fcacd 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -320,14 +320,18 @@ namespace BTCPayServer.Controllers [Route("api-access-request")] public async Task Pair(string pairingCode, string selectedStore) { + if(pairingCode == null) + return NotFound(); var store = await _Repo.FindStore(selectedStore, GetUserId()); var pairing = await _TokenRepository.GetPairingAsync(pairingCode); if(store == null || pairing == null) return NotFound(); - if(pairingCode != null && await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id)) + + var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id); + if(pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial) { StatusMessage = "Pairing is successfull"; - if(pairing.SIN == null) + if(pairingResult == PairingResult.Partial) StatusMessage = "Server initiated pairing code: " + pairingCode; return RedirectToAction(nameof(ListTokens), new { @@ -336,7 +340,7 @@ namespace BTCPayServer.Controllers } else { - StatusMessage = "Pairing failed"; + StatusMessage = $"Pairing failed ({pairingResult})"; return RedirectToAction(nameof(ListTokens), new { storeId = store.Id