mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Can't pair same SIN to different store
This commit is contained in:
parent
15e73e1cad
commit
a7e10c0fb9
4 changed files with 55 additions and 14 deletions
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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})");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue