Can't pair same SIN to different store

This commit is contained in:
nicolas.dorier 2017-10-13 18:06:46 +09:00
parent 15e73e1cad
commit a7e10c0fb9
4 changed files with 55 additions and 14 deletions

View file

@ -15,6 +15,7 @@ using System.IO;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using BTCPayServer.Authentication;
namespace BTCPayServer.Tests 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<RedirectToActionResult>(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] [Fact]
public void InvoiceFlowThroughDifferentStatesCorrectly() public void InvoiceFlowThroughDifferentStatesCorrectly()
{ {

View file

@ -13,6 +13,14 @@ using System.Linq;
namespace BTCPayServer.Authentication namespace BTCPayServer.Authentication
{ {
public enum PairingResult
{
Partial,
Complete,
ReusedKey,
Expired
}
public class TokenRepository public class TokenRepository
{ {
ApplicationDbContextFactory _Factory; ApplicationDbContextFactory _Factory;
@ -79,40 +87,45 @@ namespace BTCPayServer.Authentication
} }
} }
public async Task<bool> PairWithStoreAsync(string pairingCodeId, string storeId) public async Task<PairingResult> PairWithStoreAsync(string pairingCodeId, string storeId)
{ {
using(var ctx = _Factory.CreateContext()) using(var ctx = _Factory.CreateContext())
{ {
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId); var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow) if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
return false; return PairingResult.Expired;
pairingCode.StoreDataId = storeId; pairingCode.StoreDataId = storeId;
await ActivateIfComplete(ctx, pairingCode); var result = await ActivateIfComplete(ctx, pairingCode);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
return result;
} }
return true;
} }
public async Task<bool> PairWithSINAsync(string pairingCodeId, string sin) public async Task<PairingResult> PairWithSINAsync(string pairingCodeId, string sin)
{ {
using(var ctx = _Factory.CreateContext()) using(var ctx = _Factory.CreateContext())
{ {
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId); var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow) if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
return false; return PairingResult.Expired;
pairingCode.SIN = sin; pairingCode.SIN = sin;
await ActivateIfComplete(ctx, pairingCode); var result = await ActivateIfComplete(ctx, pairingCode);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
return result;
} }
return true;
} }
private async Task ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode) private async Task<PairingResult> ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode)
{ {
if(!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId)) if(!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId))
{ {
ctx.PairingCodes.Remove(pairingCode); 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() await ctx.PairedSINData.AddAsync(new PairedSINData()
{ {
Id = pairingCode.TokenValue, Id = pairingCode.TokenValue,
@ -122,7 +135,9 @@ namespace BTCPayServer.Authentication
StoreDataId = pairingCode.StoreDataId, StoreDataId = pairingCode.StoreDataId,
SIN = pairingCode.SIN SIN = pairingCode.SIN
}); });
return PairingResult.Complete;
} }
return PairingResult.Partial;
} }

View file

@ -57,8 +57,10 @@ namespace BTCPayServer.Controllers
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode); pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
pairingEntity.SIN = sin; 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})");
} }

View file

@ -320,14 +320,18 @@ namespace BTCPayServer.Controllers
[Route("api-access-request")] [Route("api-access-request")]
public async Task<IActionResult> Pair(string pairingCode, string selectedStore) public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
{ {
if(pairingCode == null)
return NotFound();
var store = await _Repo.FindStore(selectedStore, GetUserId()); var store = await _Repo.FindStore(selectedStore, GetUserId());
var pairing = await _TokenRepository.GetPairingAsync(pairingCode); var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
if(store == null || pairing == null) if(store == null || pairing == null)
return NotFound(); 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"; StatusMessage = "Pairing is successfull";
if(pairing.SIN == null) if(pairingResult == PairingResult.Partial)
StatusMessage = "Server initiated pairing code: " + pairingCode; StatusMessage = "Server initiated pairing code: " + pairingCode;
return RedirectToAction(nameof(ListTokens), new return RedirectToAction(nameof(ListTokens), new
{ {
@ -336,7 +340,7 @@ namespace BTCPayServer.Controllers
} }
else else
{ {
StatusMessage = "Pairing failed"; StatusMessage = $"Pairing failed ({pairingResult})";
return RedirectToAction(nameof(ListTokens), new return RedirectToAction(nameof(ListTokens), new
{ {
storeId = store.Id storeId = store.Id