Refactor token handling, support server-initiated pairing

This commit is contained in:
nicolas.dorier
2017-10-11 12:20:44 +09:00
parent 7d8c3c1c81
commit 6d3ea65e03
24 changed files with 985 additions and 228 deletions

View File

@@ -51,7 +51,7 @@ namespace BTCPayServer.Tests
return String.IsNullOrEmpty(var) ? defaultValue : var; return String.IsNullOrEmpty(var) ? defaultValue : var;
} }
public TestAccount CreateAccount() public TestAccount NewAccount()
{ {
return new TestAccount(this); return new TestAccount(this);
} }

View File

@@ -26,10 +26,45 @@ namespace BTCPayServer.Tests
{ {
GrantAccessAsync().GetAwaiter().GetResult(); GrantAccessAsync().GetAwaiter().GetResult();
} }
public void Register()
{
RegisterAsync().GetAwaiter().GetResult();
}
public BitcoinExtKey ExtKey
{
get; set;
}
public async Task GrantAccessAsync() public async Task GrantAccessAsync()
{ {
var extKey = new ExtKey().GetWif(parent.Network); await RegisterAsync();
var store = await CreateStoreAsync();
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant); var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId);
}
public StoresController CreateStore()
{
return CreateStoreAsync().GetAwaiter().GetResult();
}
public async Task<StoresController> CreateStoreAsync()
{
ExtKey = new ExtKey().GetWif(parent.Network);
var store = parent.PayTester.GetController<StoresController>(UserId);
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
StoreId = store.CreatedStoreId;
await store.UpdateStore(StoreId, new StoreViewModel()
{
DerivationScheme = ExtKey.Neuter().ToString() + "-[legacy]",
SpeedPolicy = SpeedPolicy.MediumSpeed
}, "Save");
return store;
}
private async Task RegisterAsync()
{
var account = parent.PayTester.GetController<AccountController>(); var account = parent.PayTester.GetController<AccountController>();
await account.Register(new RegisterViewModel() await account.Register(new RegisterViewModel()
{ {
@@ -38,18 +73,6 @@ namespace BTCPayServer.Tests
Password = "Kitten0@", Password = "Kitten0@",
}); });
UserId = account.RegisteredUserId; UserId = account.RegisteredUserId;
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()
{
DerivationScheme = extKey.Neuter().ToString() + "-[legacy]",
SpeedPolicy = SpeedPolicy.MediumSpeed
}, "Save");
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId);
} }
public Bitpay BitPay public Bitpay BitPay

View File

@@ -13,6 +13,8 @@ using BTCPayServer.Servcices.Invoices;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.IO; using System.IO;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using BTCPayServer.Controllers;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Tests namespace BTCPayServer.Tests
{ {
@@ -63,7 +65,7 @@ namespace BTCPayServer.Tests
using(var tester = ServerTester.Create()) using(var tester = ServerTester.Create())
{ {
tester.Start(); tester.Start();
var user = tester.CreateAccount(); var user = tester.NewAccount();
user.GrantAccess(); user.GrantAccess();
var invoice = user.BitPay.CreateInvoice(new Invoice() var invoice = user.BitPay.CreateInvoice(new Invoice()
{ {
@@ -104,6 +106,31 @@ namespace BTCPayServer.Tests
} }
} }
[Fact]
public void CanUseServerInitiatedPairingCode()
{
using(var tester = ServerTester.Create())
{
tester.Start();
var acc = tester.NewAccount();
acc.Register();
acc.CreateStore();
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
{
Facade = Facade.Merchant.ToString(),
Label = "bla",
PublicKey = null
}).GetAwaiter().GetResult();
var pairingCode = (string)token.RouteValues["pairingCode"];
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
}
}
[Fact] [Fact]
public void CanSendIPN() public void CanSendIPN()
{ {
@@ -112,7 +139,7 @@ namespace BTCPayServer.Tests
using(var tester = ServerTester.Create()) using(var tester = ServerTester.Create())
{ {
tester.Start(); tester.Start();
var acc = tester.CreateAccount(); var acc = tester.NewAccount();
acc.GrantAccess(); acc.GrantAccess();
var invoice = acc.BitPay.CreateInvoice(new Invoice() var invoice = acc.BitPay.CreateInvoice(new Invoice()
{ {
@@ -143,7 +170,7 @@ namespace BTCPayServer.Tests
using(var tester = ServerTester.Create()) using(var tester = ServerTester.Create())
{ {
tester.Start(); tester.Start();
var user = tester.CreateAccount(); var user = tester.NewAccount();
Assert.False(user.BitPay.TestAccess(Facade.Merchant)); Assert.False(user.BitPay.TestAccess(Facade.Merchant));
user.GrantAccess(); user.GrantAccess();
Assert.True(user.BitPay.TestAccess(Facade.Merchant)); Assert.True(user.BitPay.TestAccess(Facade.Merchant));

View File

@@ -8,7 +8,7 @@ namespace BTCPayServer.Authentication
{ {
public class BitTokenEntity public class BitTokenEntity
{ {
public string Name public string Facade
{ {
get; set; get; set;
} }
@@ -16,15 +16,7 @@ namespace BTCPayServer.Authentication
{ {
get; set; get; set;
} }
public DateTimeOffset DateCreated public string StoreId
{
get; set;
}
public bool Active
{
get; set;
}
public string PairedId
{ {
get; set; get; set;
} }
@@ -46,11 +38,9 @@ namespace BTCPayServer.Authentication
{ {
return new BitTokenEntity() return new BitTokenEntity()
{ {
Active = Active,
DateCreated = DateCreated,
Label = Label, Label = Label,
Name = Name, Facade = Facade,
PairedId = PairedId, StoreId = StoreId,
PairingTime = PairingTime, PairingTime = PairingTime,
SIN = SIN, SIN = SIN,
Value = Value Value = Value

View File

@@ -26,17 +26,17 @@ namespace BTCPayServer.Authentication
get; get;
set; set;
} }
public DateTimeOffset PairingTime public DateTimeOffset CreatedTime
{ {
get; get;
set; set;
} }
public DateTimeOffset PairingExpiration public DateTimeOffset Expiration
{ {
get; get;
set; set;
} }
public string Token public string TokenValue
{ {
get; get;
set; set;
@@ -44,7 +44,7 @@ namespace BTCPayServer.Authentication
public bool IsExpired() public bool IsExpired()
{ {
return DateTimeOffset.UtcNow > PairingExpiration; return DateTimeOffset.UtcNow > Expiration;
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using DBreeze; using BTCPayServer.Data;
using DBreeze;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -6,175 +7,177 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using System.Linq;
namespace BTCPayServer.Authentication namespace BTCPayServer.Authentication
{ {
public class TokenRepository public class TokenRepository
{ {
public TokenRepository(DBreezeEngine engine) ApplicationDbContextFactory _Factory;
public TokenRepository(ApplicationDbContextFactory dbFactory)
{ {
_Engine = engine; if(dbFactory == null)
throw new ArgumentNullException(nameof(dbFactory));
_Factory = dbFactory;
} }
public async Task<BitTokenEntity[]> GetTokens(string sin)
private readonly DBreezeEngine _Engine;
public DBreezeEngine Engine
{ {
get using(var ctx = _Factory.CreateContext())
{ {
return _Engine; return (await ctx.PairedSINData
.Where(p => p.SIN == sin)
.ToListAsync())
.Select(p => CreateTokenEntity(p))
.ToArray();
} }
} }
public Task<BitTokenEntity[]> GetTokens(string sin) private BitTokenEntity CreateTokenEntity(PairedSINData data)
{ {
List<BitTokenEntity> tokens = new List<BitTokenEntity>(); return new BitTokenEntity()
using(var tx = _Engine.GetTransaction())
{ {
tx.ValuesLazyLoadingIsOn = false; Label = data.Label,
foreach(var row in tx.SelectForward<string, byte[]>($"T_{sin}")) Facade = data.Facade,
{ Value = data.Id,
var token = ToObject<BitTokenEntity>(row.Value); SIN = data.SIN,
tokens.Add(token); PairingTime = data.PairingTime,
} StoreId = data.StoreDataId
}
return Task.FromResult(tokens.ToArray());
}
public Task<BitTokenEntity> CreateToken(string sin, string tokenName)
{
var token = new BitTokenEntity
{
Name = tokenName,
Value = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)),
DateCreated = DateTimeOffset.UtcNow
}; };
using(var tx = _Engine.GetTransaction())
{
tx.Insert<string, byte[]>($"T_{sin}", token.Name, ToBytes(token));
tx.Commit();
}
return Task.FromResult(token);
} }
public Task<bool> PairWithAsync(string pairingCode, string pairedId) public async Task<string> CreatePairingCodeAsync()
{ {
if(pairedId == null) string pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
throw new ArgumentNullException(nameof(pairedId)); using(var ctx = _Factory.CreateContext())
using(var tx = _Engine.GetTransaction())
{ {
var row = tx.Select<string, byte[]>("PairingCodes", pairingCode); var now = DateTime.UtcNow;
if(row == null || !row.Exists) var expiration = DateTime.UtcNow + TimeSpan.FromMinutes(15);
return Task.FromResult(false); await ctx.PairingCodes.AddAsync(new PairingCodeData()
tx.RemoveKey<string>("PairingCodes", pairingCode);
try
{ {
var pairingEntity = ToObject<PairingCodeEntity>(row.Value); Id = pairingCodeId,
if(pairingEntity.IsExpired()) DateCreated = now,
return Task.FromResult(false); Expiration = expiration,
row = tx.Select<string, byte[]>($"T_{pairingEntity.SIN}", pairingEntity.Facade); TokenValue = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32))
if(row == null || !row.Exists) });
return Task.FromResult(false); await ctx.SaveChangesAsync();
var token = ToObject<BitTokenEntity>(row.Value);
if(token.Active)
return Task.FromResult(false);
token.Active = true;
token.PairedId = pairedId;
token.SIN = pairingEntity.SIN;
token.Label = pairingEntity.Label;
token.PairingTime = DateTimeOffset.UtcNow;
tx.Insert($"TbP_{pairedId}", token.Value, ToBytes(token));
tx.Insert($"T_{pairingEntity.SIN}", pairingEntity.Facade, ToBytes(token));
}
finally
{
tx.Commit();
}
} }
return Task.FromResult(true); return pairingCodeId;
} }
public Task<BitTokenEntity[]> GetTokensByPairedIdAsync(string pairedId) public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity)
{ {
List<BitTokenEntity> tokens = new List<BitTokenEntity>(); using(var ctx = _Factory.CreateContext())
using(var tx = _Engine.GetTransaction())
{ {
tx.ValuesLazyLoadingIsOn = false; var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id);
foreach(var row in tx.SelectForward<string, byte[]>($"TbP_{pairedId}")) pairingCode.Label = pairingCodeEntity.Label;
{ pairingCode.Facade = pairingCodeEntity.Facade;
tokens.Add(ToObject<BitTokenEntity>(row.Value)); await ctx.SaveChangesAsync();
} return CreatePairingCodeEntity(pairingCode);
}
return Task.FromResult(tokens.ToArray());
}
public Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
{
using(var tx = _Engine.GetTransaction())
{
var row = tx.Select<string, byte[]>("PairingCodes", pairingCode);
if(row == null || !row.Exists)
return Task.FromResult<PairingCodeEntity>(null);
var pairingEntity = ToObject<PairingCodeEntity>(row.Value);
if(pairingEntity.IsExpired())
return Task.FromResult<PairingCodeEntity>(null);
return Task.FromResult(pairingEntity);
} }
} }
public Task<PairingCodeEntity> AddPairingCodeAsync(PairingCodeEntity pairingCodeEntity)
public async Task<bool> PairWithStoreAsync(string pairingCodeId, string storeId)
{ {
pairingCodeEntity = Clone(pairingCodeEntity); using(var ctx = _Factory.CreateContext())
pairingCodeEntity.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
using(var tx = _Engine.GetTransaction())
{ {
tx.Insert("PairingCodes", pairingCodeEntity.Id, ToBytes(pairingCodeEntity)); var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
tx.Commit(); if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
} return false;
return Task.FromResult(pairingCodeEntity); pairingCode.StoreDataId = storeId;
} await ActivateIfComplete(ctx, pairingCode);
await ctx.SaveChangesAsync();
private byte[] ToBytes<T>(T obj)
{
return ZipUtils.Zip(JsonConvert.SerializeObject(obj));
}
private T ToObject<T>(byte[] value)
{
return JsonConvert.DeserializeObject<T>(ZipUtils.Unzip(value));
}
private T Clone<T>(T obj)
{
return ToObject<T>(ToBytes(obj));
}
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);
if(token.PairedId != null)
tx.RemoveKey<string>($"TbP_" + token.PairedId, token.Value);
tx.Commit();
} }
return true; return true;
} }
private Task<BitTokenEntity> GetToken(string sin, string tokenName) public async Task<bool> PairWithSINAsync(string pairingCodeId, string sin)
{ {
using(var tx = _Engine.GetTransaction()) using(var ctx = _Factory.CreateContext())
{ {
tx.ValuesLazyLoadingIsOn = true; var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
var row = tx.Select<string, byte[]>($"T_{sin}", tokenName); if(pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
if(row == null || !row.Exists) return false;
return Task.FromResult<BitTokenEntity>(null); pairingCode.SIN = sin;
var token = ToObject<BitTokenEntity>(row.Value); await ActivateIfComplete(ctx, pairingCode);
if(!token.Active) await ctx.SaveChangesAsync();
return Task.FromResult<BitTokenEntity>(null); }
return Task.FromResult(token); return true;
}
private async Task ActivateIfComplete(ApplicationDbContext ctx, PairingCodeData pairingCode)
{
if(!string.IsNullOrEmpty(pairingCode.SIN) && !string.IsNullOrEmpty(pairingCode.StoreDataId))
{
ctx.PairingCodes.Remove(pairingCode);
await ctx.PairedSINData.AddAsync(new PairedSINData()
{
Id = pairingCode.TokenValue,
PairingTime = DateTime.UtcNow,
Facade = pairingCode.Facade,
Label = pairingCode.Label,
StoreDataId = pairingCode.StoreDataId,
SIN = pairingCode.SIN
});
}
}
public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId)
{
using(var ctx = _Factory.CreateContext())
{
return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync())
.Select(c => CreateTokenEntity(c))
.ToArray();
}
}
public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
{
using(var ctx = _Factory.CreateContext())
{
return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
}
}
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
{
return new PairingCodeEntity()
{
Facade = data.Facade,
Id = data.Id,
Label = data.Label,
Expiration = data.Expiration,
CreatedTime = data.DateCreated,
TokenValue = data.TokenValue,
SIN = data.SIN
};
}
public async Task<bool> DeleteToken(string tokenId)
{
using(var ctx = _Factory.CreateContext())
{
var token = await ctx.PairedSINData.FindAsync(tokenId);
if(token == null)
return false;
ctx.PairedSINData.Remove(token);
await ctx.SaveChangesAsync();
return true;
}
}
public async Task<BitTokenEntity> GetToken(string tokenId)
{
using(var ctx = _Factory.CreateContext())
{
var token = await ctx.PairedSINData.FindAsync(tokenId);
return CreateTokenEntity(token);
} }
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.0.5</Version> <Version>1.0.0.6</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Build\dockerfiles\**" /> <Compile Remove="Build\dockerfiles\**" />
@@ -22,7 +22,7 @@
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" /> <PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="NBitcoin" Version="4.0.0.38" /> <PackageReference Include="NBitcoin" Version="4.0.0.38" />
<PackageReference Include="NBitpayClient" Version="1.0.0.9" /> <PackageReference Include="NBitpayClient" Version="1.0.0.10" />
<PackageReference Include="DBreeze" Version="1.87.0" /> <PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.16" /> <PackageReference Include="NBXplorer.Client" Version="1.0.0.16" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" /> <PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />

View File

@@ -51,7 +51,6 @@ namespace BTCPayServer.Configuration
} }
DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB")); DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB"));
_Resources.Add(db); _Resources.Add(db);
TokenRepository = new TokenRepository(db);
db = new DBreezeEngine(CreateDBPath(opts, "InvoiceDB")); db = new DBreezeEngine(CreateDBPath(opts, "InvoiceDB"));
_Resources.Add(db); _Resources.Add(db);
@@ -99,10 +98,6 @@ namespace BTCPayServer.Configuration
get; get;
private set; private set;
} }
public TokenRepository TokenRepository
{
get; set;
}
public InvoiceRepository InvoiceRepository public InvoiceRepository InvoiceRepository
{ {
get; get;

View File

@@ -21,7 +21,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet] [HttpGet]
[Route("tokens")] [Route("tokens")]
public async Task<GetTokensResponse> GetTokens() public async Task<GetTokensResponse> Tokens()
{ {
var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN); var tokens = await _TokenRepository.GetTokens(this.GetBitIdentity().SIN);
return new GetTokensResponse(tokens); return new GetTokensResponse(tokens);
@@ -29,33 +29,51 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("tokens")] [Route("tokens")]
public async Task<DataWrapper<List<PairingCodeResponse>>> GetPairingCode([FromBody] PairingCodeRequest token) public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
{ {
var now = DateTimeOffset.UtcNow; PairingCodeEntity pairingEntity = null;
var pairingEntity = new PairingCodeEntity() if(string.IsNullOrEmpty(request.PairingCode))
{ {
Facade = token.Facade, if(string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
Label = token.Label, throw new BitpayHttpException(400, "'id' property is required");
SIN = token.Id, if(string.IsNullOrEmpty(request.Facade))
PairingTime = now, throw new BitpayHttpException(400, "'facade' property is required");
PairingExpiration = now + TimeSpan.FromMinutes(15)
}; var pairingCode = await _TokenRepository.CreatePairingCodeAsync();
var grantedToken = await _TokenRepository.CreateToken(token.Id, token.Facade); await _TokenRepository.PairWithSINAsync(pairingCode, request.Id);
pairingEntity.Token = grantedToken.Name; pairingEntity = await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
pairingEntity = await _TokenRepository.AddPairingCodeAsync(pairingEntity); {
Id = pairingCode,
Facade = request.Facade,
Label = request.Label
});
}
else
{
var sin = this.GetBitIdentity(false)?.SIN ?? request.Id;
if(string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
pairingEntity.SIN = sin;
if(!await _TokenRepository.PairWithSINAsync(request.PairingCode, sin))
throw new BitpayHttpException(400, "Unknown pairing code");
}
var pairingCodes = new List<PairingCodeResponse> var pairingCodes = new List<PairingCodeResponse>
{
new PairingCodeResponse()
{ {
PairingCode = pairingEntity.Id, new PairingCodeResponse()
PairingExpiration = pairingEntity.PairingExpiration, {
DateCreated = pairingEntity.PairingTime, PairingCode = pairingEntity.Id,
Facade = grantedToken.Name, PairingExpiration = pairingEntity.Expiration,
Token = grantedToken.Value, DateCreated = pairingEntity.CreatedTime,
Label = pairingEntity.Label Facade = pairingEntity.Facade,
} Token = pairingEntity.TokenValue,
}; Label = pairingEntity.Label
}
};
return DataWrapper.Create(pairingCodes); return DataWrapper.Create(pairingCodes);
} }
} }

View File

@@ -83,26 +83,26 @@ namespace BTCPayServer.Controllers
if(facade == null) if(facade == null)
throw new ArgumentNullException(nameof(facade)); throw new ArgumentNullException(nameof(facade));
var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).Where(t => t.Active).ToArray(); var actualTokens = (await _TokenRepository.GetTokens(this.GetBitIdentity().SIN)).ToArray();
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray(); actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal)); var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
if(expectedToken == null || actualToken == null) if(expectedToken == null || actualToken == null)
{ {
Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.GetBitIdentity().SIN}"); Logs.PayServer.LogDebug($"No token found for facade {facade} for SIN {this.GetBitIdentity().SIN}");
throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Name).Concat(new[] { "user" }).FirstOrDefault()}` facade"); throw new BitpayHttpException(401, $"This endpoint does not support the `{actualTokens.Select(a => a.Facade).Concat(new[] { "user" }).FirstOrDefault()}` facade");
} }
return actualToken; return actualToken;
} }
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token) private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
{ {
if(token.Name == Facade.Merchant.ToString()) if(token.Facade == Facade.Merchant.ToString())
{ {
yield return token.Clone(Facade.User); yield return token.Clone(Facade.User);
yield return token.Clone(Facade.PointOfSale); yield return token.Clone(Facade.PointOfSale);
} }
if(token.Name == Facade.PointOfSale.ToString()) if(token.Facade == Facade.PointOfSale.ToString())
{ {
yield return token.Clone(Facade.User); yield return token.Clone(Facade.User);
} }
@@ -111,7 +111,7 @@ namespace BTCPayServer.Controllers
private async Task<StoreData> FindStore(BitTokenEntity bitToken) private async Task<StoreData> FindStore(BitTokenEntity bitToken)
{ {
var store = await _StoreRepository.FindStore(bitToken.PairedId); var store = await _StoreRepository.FindStore(bitToken.StoreId);
if(store == null) if(store == null)
throw new BitpayHttpException(401, "Unknown store"); throw new BitpayHttpException(401, "Unknown store");
return store; return store;

View File

@@ -33,6 +33,8 @@ using BTCPayServer.Servcices.Invoices;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using BTCPayServer.Validations; using BTCPayServer.Validations;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Routing;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers

View File

@@ -197,11 +197,11 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> ListTokens(string storeId) public async Task<IActionResult> ListTokens(string storeId)
{ {
var model = new TokensViewModel(); var model = new TokensViewModel();
var tokens = await _TokenRepository.GetTokensByPairedIdAsync(storeId); var tokens = await _TokenRepository.GetTokensByStoreIdAsync(storeId);
model.StatusMessage = StatusMessage; model.StatusMessage = StatusMessage;
model.Tokens = tokens.Select(t => new TokenViewModel() model.Tokens = tokens.Select(t => new TokenViewModel()
{ {
Facade = t.Name, Facade = t.Facade,
Label = t.Label, Label = t.Label,
SIN = t.SIN, SIN = t.SIN,
Id = t.Value Id = t.Value
@@ -219,16 +219,34 @@ namespace BTCPayServer.Controllers
return View(model); return View(model);
} }
var pairingCode = await _TokenController.GetPairingCode(new PairingCodeRequest() var tokenRequest = new TokenRequest()
{ {
Facade = model.Facade, Facade = model.Facade,
Label = model.Label, Label = model.Label,
Id = NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey)) Id = model.PublicKey == null ? null : NBitpayClient.Extensions.BitIdExtensions.GetBitIDSIN(new PubKey(model.PublicKey))
}); };
string pairingCode = null;
if(model.PublicKey == null)
{
tokenRequest.PairingCode = await _TokenRepository.CreatePairingCodeAsync();
await _TokenRepository.UpdatePairingCode(new PairingCodeEntity()
{
Id = tokenRequest.PairingCode,
Facade = model.Facade,
Label = model.Label,
});
await _TokenRepository.PairWithStoreAsync(tokenRequest.PairingCode, storeId);
pairingCode = tokenRequest.PairingCode;
}
else
{
pairingCode = ((DataWrapper<List<PairingCodeResponse>>)await _TokenController.Tokens(tokenRequest)).Data[0].PairingCode;
}
return RedirectToAction(nameof(RequestPairing), new return RedirectToAction(nameof(RequestPairing), new
{ {
pairingCode = pairingCode.Data[0].PairingCode, pairingCode = pairingCode,
selectedStore = storeId selectedStore = storeId
}); });
} }
@@ -239,22 +257,21 @@ namespace BTCPayServer.Controllers
{ {
var model = new CreateTokenViewModel(); var model = new CreateTokenViewModel();
model.Facade = "merchant"; model.Facade = "merchant";
if(_Env.IsDevelopment())
{
model.PublicKey = new Key().PubKey.ToHex();
}
return View(model); return View(model);
} }
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
[Route("{storeId}/Tokens/Delete")] [Route("{storeId}/Tokens/Delete")]
public async Task<IActionResult> DeleteToken(string storeId, string name, string sin) public async Task<IActionResult> DeleteToken(string storeId, string tokenId)
{ {
if(await _TokenRepository.DeleteToken(sin, name, storeId)) var token = await _TokenRepository.GetToken(tokenId);
StatusMessage = "Token revoked"; if(token == null ||
else token.StoreId != storeId ||
!await _TokenRepository.DeleteToken(tokenId))
StatusMessage = "Failure to revoke this token"; StatusMessage = "Failure to revoke this token";
else
StatusMessage = "Token revoked";
return RedirectToAction(nameof(ListTokens)); return RedirectToAction(nameof(ListTokens));
} }
@@ -277,7 +294,7 @@ namespace BTCPayServer.Controllers
Id = pairing.Id, Id = pairing.Id,
Facade = pairing.Facade, Facade = pairing.Facade,
Label = pairing.Label, Label = pairing.Label,
SIN = pairing.SIN, SIN = pairing.SIN ?? "Server-Initiated Pairing",
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id, SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
Stores = stores.Select(s => new PairingModel.StoreViewModel() Stores = stores.Select(s => new PairingModel.StoreViewModel()
{ {
@@ -294,11 +311,14 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Pair(string pairingCode, string selectedStore) public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
{ {
var store = await _Repo.FindStore(selectedStore, GetUserId()); var store = await _Repo.FindStore(selectedStore, GetUserId());
if(store == null) var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
if(store == null || pairing == null)
return NotFound(); return NotFound();
if(pairingCode != null && await _TokenRepository.PairWithAsync(pairingCode, store.Id)) if(pairingCode != null && await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id))
{ {
StatusMessage = "Pairing is successfull"; StatusMessage = "Pairing is successfull";
if(pairing.SIN == null)
StatusMessage = "Server initiated pairing code: " + pairingCode;
return RedirectToAction(nameof(ListTokens), new return RedirectToAction(nameof(ListTokens), new
{ {
storeId = store.Id storeId = store.Id

View File

@@ -56,6 +56,17 @@ namespace BTCPayServer.Data
get; set; get; set;
} }
public DbSet<PairingCodeData> PairingCodes
{
get; set;
}
public DbSet<PairedSINData> PairedSINData
{
get; set;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any(); var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
@@ -94,6 +105,15 @@ namespace BTCPayServer.Data
builder.Entity<AddressInvoiceData>() builder.Entity<AddressInvoiceData>()
.HasKey(o => o.Address); .HasKey(o => o.Address);
builder.Entity<PairingCodeData>()
.HasKey(o => o.Id);
builder.Entity<PairedSINData>(b =>
{
b.HasIndex(o => o.SIN);
b.HasIndex(o => o.StoreDataId);
});
} }
} }
} }

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class PairedSINData
{
public string Id
{
get; set;
}
public string Facade
{
get; set;
}
public string StoreDataId
{
get; set;
}
public string Label
{
get;
set;
}
public DateTimeOffset PairingTime
{
get;
set;
}
public string SIN
{
get; set;
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class PairingCodeData
{
public string Id
{
get; set;
}
public string Facade
{
get; set;
}
public string StoreDataId
{
get; set;
}
public DateTimeOffset Expiration
{
get;
set;
}
public string Label
{
get;
set;
}
public string SIN
{
get;
set;
}
public DateTime DateCreated
{
get;
set;
}
public string TokenValue
{
get;
set;
}
}
}

View File

@@ -38,10 +38,10 @@ namespace BTCPayServer
} }
public static BitIdentity GetBitIdentity(this Controller controller) public static BitIdentity GetBitIdentity(this Controller controller, bool throws = true)
{ {
if(!(controller.User.Identity is BitIdentity)) if(!(controller.User.Identity is BitIdentity))
throw new UnauthorizedAccessException("no-bitid"); return throws ? throw new UnauthorizedAccessException("no-bitid") : (BitIdentity)null;
return (BitIdentity)controller.User.Identity; return (BitIdentity)controller.User.Identity;
} }
} }

View File

@@ -32,6 +32,7 @@ using BTCPayServer.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using BTCPayServer.Authentication;
namespace BTCPayServer.Hosting namespace BTCPayServer.Hosting
{ {
@@ -107,7 +108,7 @@ namespace BTCPayServer.Hosting
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>()); runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
return runtime; return runtime;
}); });
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().TokenRepository); services.TryAddSingleton<TokenRepository>();
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository); services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network); services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory); services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);

View File

@@ -0,0 +1,444 @@
// <auto-generated />
using BTCPayServer.Data;
using BTCPayServer.Servcices.Invoices;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20171010082424_Tokens")]
partial class Tokens
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.ValueGeneratedOnAdd();
b.Property<string>("InvoiceDataId");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<DateTimeOffset>("Created");
b.Property<string>("CustomerEmail");
b.Property<string>("ExceptionStatus");
b.Property<string>("ItemCode");
b.Property<string>("OrderId");
b.Property<string>("Status");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("StoreDataId");
b.ToTable("Invoices");
});
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<DateTimeOffset>("PairingTime");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("SIN");
b.HasIndex("StoreDataId");
b.ToTable("PairedSINData");
});
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("DateCreated");
b.Property<DateTimeOffset>("Expiration");
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.Property<string>("TokenValue");
b.HasKey("Id");
b.ToTable("PairingCodes");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("Payments");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("RefundAddresses");
});
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("DerivationStrategy");
b.Property<int>("SpeedPolicy");
b.Property<byte[]>("StoreCertificate");
b.Property<string>("StoreName");
b.Property<string>("StoreWebsite");
b.HasKey("Id");
b.ToTable("Stores");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.Property<string>("ApplicationUserId");
b.Property<string>("StoreDataId");
b.Property<string>("Role");
b.HasKey("ApplicationUserId", "StoreDataId");
b.HasIndex("StoreDataId");
b.ToTable("UserStore");
});
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<bool>("RequiresEmailConfirmation");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany()
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany()
.HasForeignKey("StoreDataId");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("Payments")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("RefundAddresses")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
.WithMany("UserStores")
.HasForeignKey("ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany("UserStores")
.HasForeignKey("StoreDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,67 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace BTCPayServer.Migrations
{
public partial class Tokens : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PairedSINData",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Facade = table.Column<string>(type: "TEXT", nullable: true),
Label = table.Column<string>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true),
PairingTime = table.Column<DateTimeOffset>(nullable: false),
SIN = table.Column<string>(type: "TEXT", nullable: true),
StoreDataId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PairedSINData", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PairingCodes",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
DateCreated = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTimeOffset>(nullable: false),
Facade = table.Column<string>(type: "TEXT", nullable: true),
Label = table.Column<string>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true),
SIN = table.Column<string>(type: "TEXT", nullable: true),
StoreDataId = table.Column<string>(type: "TEXT", nullable: true),
TokenValue = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PairingCodes", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_PairedSINData_SIN",
table: "PairedSINData",
column: "SIN");
migrationBuilder.CreateIndex(
name: "IX_PairedSINData_StoreDataId",
table: "PairedSINData",
column: "StoreDataId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PairedSINData");
migrationBuilder.DropTable(
name: "PairingCodes");
}
}
}

View File

@@ -62,6 +62,58 @@ namespace BTCPayServer.Migrations
b.ToTable("Invoices"); b.ToTable("Invoices");
}); });
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<DateTimeOffset>("PairingTime");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("SIN");
b.HasIndex("StoreDataId");
b.ToTable("PairedSINData");
});
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("DateCreated");
b.Property<DateTimeOffset>("Expiration");
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("Name");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.Property<string>("TokenValue");
b.HasKey("Id");
b.ToTable("PairingCodes");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")

View File

@@ -39,7 +39,7 @@ namespace BTCPayServer.Models
{ {
JObject item = new JObject(); JObject item = new JObject();
jarray.Add(item); jarray.Add(item);
JProperty jProp = new JProperty(token.Name); JProperty jProp = new JProperty(token.Facade);
item.Add(jProp); item.Add(jProp);
jProp.Value = token.Value; jProp.Value = token.Value;
} }

View File

@@ -6,7 +6,7 @@ using NBitcoin;
namespace BTCPayServer.Models namespace BTCPayServer.Models
{ {
public class PairingCodeRequest public class TokenRequest
{ {
[JsonProperty(PropertyName = "id")] [JsonProperty(PropertyName = "id")]
public string Id public string Id
@@ -34,6 +34,12 @@ namespace BTCPayServer.Models
{ {
get; set; get; set;
} }
[JsonProperty(PropertyName = "pairingCode")]
public string PairingCode
{
get; set;
}
} }
public class PairingCodeResponse public class PairingCodeResponse

View File

@@ -17,6 +17,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="PublicKey"></label> <label asp-for="PublicKey"></label>
<small class="text-muted">Keep empty for server-initiated pairing</small>
<input asp-for="PublicKey" class="form-control" /> <input asp-for="PublicKey" class="form-control" />
<span asp-validation-for="PublicKey" class="text-danger"></span> <span asp-validation-for="PublicKey" class="text-danger"></span>
</div> </div>

View File

@@ -27,8 +27,7 @@
<td>@token.Facade</td> <td>@token.Facade</td>
<td> <td>
<form asp-action="DeleteToken" method="post"> <form asp-action="DeleteToken" method="post">
<input type="hidden" name="name" value="@token.Facade"> <input type="hidden" name="tokenId" value="@token.Id">
<input type="hidden" name="sin" value="@token.SIN">
<button type="submit" class="btn btn-danger" role="button">Revoke</button> <button type="submit" class="btn btn-danger" role="button">Revoke</button>
</form> </form>
</td> </td>