From eac4c91820e8fbf13d2e06e184c0916385b239b7 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 19 Oct 2019 00:54:20 +0900 Subject: [PATCH] Move Bitpay authentication class in BTCPayServer.Security --- BTCPayServer.Tests/AuthenticationTests.cs | 1 - BTCPayServer.Tests/UnitTest1.cs | 2 +- BTCPayServer/Authentication/BitToken.cs | 45 ---- .../Authentication/PairingCodeEntity.cs | 45 ---- .../Authentication/TokenRepository.cs | 247 ------------------ BTCPayServer/BTCPayServer.csproj | 3 +- .../Controllers/AccessTokenController.cs | 4 +- BTCPayServer/Controllers/ManageController.cs | 4 - BTCPayServer/Controllers/RateController.cs | 2 +- .../Controllers/RestApi/TestController.cs | 1 - BTCPayServer/Controllers/StoresController.cs | 2 +- BTCPayServer/Data/StoreDataExtensions.cs | 1 - BTCPayServer/Extensions.cs | 3 +- BTCPayServer/Hosting/BTCPayServerServices.cs | 1 - BTCPayServer/Hosting/BTCpayMiddleware.cs | 1 - BTCPayServer/Hosting/Startup.cs | 1 - BTCPayServer/Models/GetTokensResponse.cs | 2 +- .../Bitpay/BitpayAuthenticationHandler.cs | 1 - .../Bitpay/BitpayAuthorizationHandler.cs | 1 - .../Security/CookieAuthorizationHandler.cs | 1 - .../Security/OpenIdAuthorizationHandler.cs | 1 - BTCPayServer/Security/Policies.cs | 1 - BTCPayServer/Views/Stores/ShowToken.cshtml | 2 +- 23 files changed, 10 insertions(+), 362 deletions(-) delete mode 100644 BTCPayServer/Authentication/BitToken.cs delete mode 100644 BTCPayServer/Authentication/PairingCodeEntity.cs delete mode 100644 BTCPayServer/Authentication/TokenRepository.cs diff --git a/BTCPayServer.Tests/AuthenticationTests.cs b/BTCPayServer.Tests/AuthenticationTests.cs index c47ccb2a4..554730a6e 100644 --- a/BTCPayServer.Tests/AuthenticationTests.cs +++ b/BTCPayServer.Tests/AuthenticationTests.cs @@ -12,7 +12,6 @@ using Xunit; using Xunit.Abstractions; using System.Net.Http; using System.Net.Http.Headers; -using BTCPayServer.Authentication; using BTCPayServer.Data; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 0ed084050..e9dc22143 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -15,7 +15,6 @@ using System.IO; using Newtonsoft.Json.Linq; using BTCPayServer.Controllers; using Microsoft.AspNetCore.Mvc; -using BTCPayServer.Authentication; using System.Diagnostics; using BTCPayServer.Data; using Microsoft.EntityFrameworkCore; @@ -60,6 +59,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using NBXplorer.DerivationStrategy; using BTCPayServer.U2F.Models; +using BTCPayServer.Security.Bitpay; namespace BTCPayServer.Tests { diff --git a/BTCPayServer/Authentication/BitToken.cs b/BTCPayServer/Authentication/BitToken.cs deleted file mode 100644 index 24b55189f..000000000 --- a/BTCPayServer/Authentication/BitToken.cs +++ /dev/null @@ -1,45 +0,0 @@ -using NBitcoin; -using System; -using System.Collections.Generic; -using System.Text; -using NBitpayClient; - -namespace BTCPayServer.Authentication -{ - public class BitTokenEntity - { - public string Value - { - get; set; - } - public string StoreId - { - get; set; - } - public string Label - { - get; set; - } - public DateTimeOffset PairingTime - { - get; set; - } - public string SIN - { - get; - set; - } - - public BitTokenEntity Clone(Facade facade) - { - return new BitTokenEntity() - { - Label = Label, - StoreId = StoreId, - PairingTime = PairingTime, - SIN = SIN, - Value = Value - }; - } - } -} diff --git a/BTCPayServer/Authentication/PairingCodeEntity.cs b/BTCPayServer/Authentication/PairingCodeEntity.cs deleted file mode 100644 index 746b77a79..000000000 --- a/BTCPayServer/Authentication/PairingCodeEntity.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace BTCPayServer.Authentication -{ - public class PairingCodeEntity - { - public string Id - { - get; - set; - } - public string Label - { - get; - set; - } - public string SIN - { - get; - set; - } - public DateTimeOffset CreatedTime - { - get; - set; - } - public DateTimeOffset Expiration - { - get; - set; - } - public string TokenValue - { - get; - set; - } - - public bool IsExpired() - { - return DateTimeOffset.UtcNow > Expiration; - } - } -} diff --git a/BTCPayServer/Authentication/TokenRepository.cs b/BTCPayServer/Authentication/TokenRepository.cs deleted file mode 100644 index 4effa2c2f..000000000 --- a/BTCPayServer/Authentication/TokenRepository.cs +++ /dev/null @@ -1,247 +0,0 @@ -using BTCPayServer.Data; -using DBriize; -using NBitcoin; -using NBitcoin.DataEncoders; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query; -using System.Linq; - -namespace BTCPayServer.Authentication -{ - public enum PairingResult - { - Partial, - Complete, - ReusedKey, - Expired - } - - public class TokenRepository - { - ApplicationDbContextFactory _Factory; - public TokenRepository(ApplicationDbContextFactory dbFactory) - { - if (dbFactory == null) - throw new ArgumentNullException(nameof(dbFactory)); - _Factory = dbFactory; - } - - public async Task GetTokens(string sin) - { - if (sin == null) - return Array.Empty(); - using (var ctx = _Factory.CreateContext()) - { - return (await ctx.PairedSINData.Where(p => p.SIN == sin) - .ToArrayAsync()) - .Select(p => CreateTokenEntity(p)) - .ToArray(); - } - } - - public async Task GetStoreIdFromAPIKey(string apiKey) - { - using (var ctx = _Factory.CreateContext()) - { - return await ctx.ApiKeys.Where(o => o.Id == apiKey).Select(o => o.StoreId).FirstOrDefaultAsync(); - } - } - - public async Task GenerateLegacyAPIKey(string storeId) - { - // It is legacy support and Bitpay generate string of unknown format, trying to replicate them - // as good as possible. The string below got generated for me. - var chars = "ERo0vkBMOYhyU0ZHvirCplbLDIGWPdi1ok77VnW7QdE"; - var rand = new Random(Math.Abs(RandomUtils.GetInt32())); - var generated = new char[chars.Length]; - for (int i = 0; i < generated.Length; i++) - { - generated[i] = chars[rand.Next(0, generated.Length)]; - } - - using (var ctx = _Factory.CreateContext()) - { - var existing = await ctx.ApiKeys.Where(o => o.StoreId == storeId).FirstOrDefaultAsync(); - if (existing != null) - { - ctx.ApiKeys.Remove(existing); - } - ctx.ApiKeys.Add(new APIKeyData() { Id = new string(generated), StoreId = storeId }); - await ctx.SaveChangesAsync().ConfigureAwait(false); - } - } - - public async Task GetLegacyAPIKeys(string storeId) - { - using (var ctx = _Factory.CreateContext()) - { - return await ctx.ApiKeys.Where(o => o.StoreId == storeId).Select(c => c.Id).ToArrayAsync(); - } - } - - private BitTokenEntity CreateTokenEntity(PairedSINData data) - { - return new BitTokenEntity() - { - Label = data.Label, - Value = data.Id, - SIN = data.SIN, - PairingTime = data.PairingTime, - StoreId = data.StoreDataId - }; - } - - public async Task CreatePairingCodeAsync() - { - string pairingCodeId = null; - while (true) - { - pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6)); - if (pairingCodeId.Length == 7) // woocommerce plugin check for exactly 7 digits - break; - } - using (var ctx = _Factory.CreateContext()) - { - var now = DateTime.UtcNow; - var expiration = DateTime.UtcNow + TimeSpan.FromMinutes(15); - ctx.PairingCodes.Add(new PairingCodeData() - { - Id = pairingCodeId, - DateCreated = now, - Expiration = expiration, - TokenValue = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)) - }); - await ctx.SaveChangesAsync(); - } - return pairingCodeId; - } - - public async Task UpdatePairingCode(PairingCodeEntity pairingCodeEntity) - { - using (var ctx = _Factory.CreateContext()) - { - var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id); - pairingCode.Label = pairingCodeEntity.Label; - await ctx.SaveChangesAsync(); - return CreatePairingCodeEntity(pairingCode); - } - } - - 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 PairingResult.Expired; - pairingCode.StoreDataId = storeId; - var result = await ActivateIfComplete(ctx, pairingCode); - await ctx.SaveChangesAsync(); - return result; - } - } - - 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 PairingResult.Expired; - pairingCode.SIN = sin; - var result = await ActivateIfComplete(ctx, pairingCode); - await ctx.SaveChangesAsync(); - return result; - } - } - - - 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, - PairingTime = DateTime.UtcNow, - Label = pairingCode.Label, - StoreDataId = pairingCode.StoreDataId, - SIN = pairingCode.SIN - }); - return PairingResult.Complete; - } - return PairingResult.Partial; - } - - - public async Task 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 GetPairingAsync(string pairingCode) - { - using (var ctx = _Factory.CreateContext()) - { - return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode)); - } - } - - private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data) - { - if (data == null) - return null; - return new PairingCodeEntity() - { - Id = data.Id, - Label = data.Label, - Expiration = data.Expiration, - CreatedTime = data.DateCreated, - TokenValue = data.TokenValue, - SIN = data.SIN - }; - } - - - public async Task 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 GetToken(string tokenId) - { - using (var ctx = _Factory.CreateContext()) - { - var token = await ctx.PairedSINData.FindAsync(tokenId); - if (token == null) - return null; - return CreateTokenEntity(token); - } - } - - } -} diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 8360acd92..565335762 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -64,7 +64,7 @@ - + @@ -129,6 +129,7 @@ + diff --git a/BTCPayServer/Controllers/AccessTokenController.cs b/BTCPayServer/Controllers/AccessTokenController.cs index aa42f7808..1c4ab4016 100644 --- a/BTCPayServer/Controllers/AccessTokenController.cs +++ b/BTCPayServer/Controllers/AccessTokenController.cs @@ -1,7 +1,7 @@ -using BTCPayServer.Authentication; -using BTCPayServer.Filters; +using BTCPayServer.Filters; using BTCPayServer.Models; using BTCPayServer.Security; +using BTCPayServer.Security.Bitpay; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; diff --git a/BTCPayServer/Controllers/ManageController.cs b/BTCPayServer/Controllers/ManageController.cs index 72fe9d1a2..69eb3d94c 100644 --- a/BTCPayServer/Controllers/ManageController.cs +++ b/BTCPayServer/Controllers/ManageController.cs @@ -11,7 +11,6 @@ using Microsoft.Extensions.Logging; using BTCPayServer.Models; using BTCPayServer.Models.ManageViewModels; using BTCPayServer.Services; -using BTCPayServer.Authentication; using Microsoft.AspNetCore.Hosting; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; @@ -35,7 +34,6 @@ namespace BTCPayServer.Controllers private readonly EmailSenderFactory _EmailSenderFactory; private readonly ILogger _logger; private readonly UrlEncoder _urlEncoder; - TokenRepository _TokenRepository; IWebHostEnvironment _Env; private readonly U2FService _u2FService; private readonly BTCPayServerEnvironment _btcPayServerEnvironment; @@ -49,7 +47,6 @@ namespace BTCPayServer.Controllers EmailSenderFactory emailSenderFactory, ILogger logger, UrlEncoder urlEncoder, - TokenRepository tokenRepository, BTCPayWalletProvider walletProvider, StoreRepository storeRepository, IWebHostEnvironment env, @@ -61,7 +58,6 @@ namespace BTCPayServer.Controllers _EmailSenderFactory = emailSenderFactory; _logger = logger; _urlEncoder = urlEncoder; - _TokenRepository = tokenRepository; _Env = env; _u2FService = u2FService; _btcPayServerEnvironment = btcPayServerEnvironment; diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index 8c09e61bc..28ed5cc5c 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -11,11 +11,11 @@ using BTCPayServer.Services.Stores; using BTCPayServer.Rating; using Newtonsoft.Json; using Microsoft.AspNetCore.Authorization; -using BTCPayServer.Authentication; using Microsoft.AspNetCore.Cors; using System.Threading; using BTCPayServer.Data; using BTCPayServer.Security; +using BTCPayServer.Security.Bitpay; namespace BTCPayServer.Controllers { diff --git a/BTCPayServer/Controllers/RestApi/TestController.cs b/BTCPayServer/Controllers/RestApi/TestController.cs index 1e020704f..4ab4d7dbe 100644 --- a/BTCPayServer/Controllers/RestApi/TestController.cs +++ b/BTCPayServer/Controllers/RestApi/TestController.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using BTCPayServer.Authentication; using BTCPayServer.Data; using BTCPayServer.Models; using BTCPayServer.Security; diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 43b0374b0..0111b5c05 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Authentication; using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.HostedServices; @@ -17,6 +16,7 @@ using BTCPayServer.Payments.Changelly; using BTCPayServer.Payments.Lightning; using BTCPayServer.Rating; using BTCPayServer.Security; +using BTCPayServer.Security.Bitpay; using BTCPayServer.Services; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Rates; diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index 48ae7e992..dfa425f92 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; -using BTCPayServer.Authentication; using BTCPayServer.Payments; using BTCPayServer.Security; using BTCPayServer.Services.Rates; diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index e5e9d3e57..2599f5392 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -1,5 +1,4 @@ -using BTCPayServer.Authentication; -using BTCPayServer.Configuration; +using BTCPayServer.Configuration; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 7d56ca028..ebd642669 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -25,7 +25,6 @@ using BTCPayServer.Controllers; using BTCPayServer.Services.Mails; using System.Threading; using BTCPayServer.Services.Wallets; -using BTCPayServer.Authentication; using BTCPayServer.Logging; using BTCPayServer.HostedServices; using BTCPayServer.PaymentRequest; diff --git a/BTCPayServer/Hosting/BTCpayMiddleware.cs b/BTCPayServer/Hosting/BTCpayMiddleware.cs index 9d122137d..d58c2953d 100644 --- a/BTCPayServer/Hosting/BTCpayMiddleware.cs +++ b/BTCPayServer/Hosting/BTCpayMiddleware.cs @@ -7,7 +7,6 @@ using System.Text; using System.Linq; using System.Threading.Tasks; using System.IO; -using BTCPayServer.Authentication; using BTCPayServer.Logging; using Newtonsoft.Json; using BTCPayServer.Models; diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index ed6b37572..6f2cbd9f8 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -24,7 +24,6 @@ using BTCPayServer.Security; using Microsoft.AspNetCore.Server.Kestrel.Core; using OpenIddict.EntityFrameworkCore.Models; using System.Net; -using BTCPayServer.Authentication; using BTCPayServer.Security.OpenId; using BTCPayServer.PaymentRequest; using BTCPayServer.Services.Apps; diff --git a/BTCPayServer/Models/GetTokensResponse.cs b/BTCPayServer/Models/GetTokensResponse.cs index 5836806cc..a837740f2 100644 --- a/BTCPayServer/Models/GetTokensResponse.cs +++ b/BTCPayServer/Models/GetTokensResponse.cs @@ -6,10 +6,10 @@ using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; -using BTCPayServer.Authentication; using NBitcoin.DataEncoders; using Microsoft.AspNetCore.Http; using System.IO; +using BTCPayServer.Security.Bitpay; namespace BTCPayServer.Models { diff --git a/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs b/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs index 74425455d..d923e4259 100644 --- a/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs +++ b/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; -using BTCPayServer.Authentication; using BTCPayServer.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Http; diff --git a/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs b/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs index b13f4a627..42bfefa3d 100644 --- a/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs +++ b/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs @@ -9,7 +9,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authentication; -using BTCPayServer.Authentication; using BTCPayServer.Services; using BTCPayServer.Security.Bitpay; diff --git a/BTCPayServer/Security/CookieAuthorizationHandler.cs b/BTCPayServer/Security/CookieAuthorizationHandler.cs index 1859fa632..c844b2c46 100644 --- a/BTCPayServer/Security/CookieAuthorizationHandler.cs +++ b/BTCPayServer/Security/CookieAuthorizationHandler.cs @@ -9,7 +9,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authentication; -using BTCPayServer.Authentication; using Microsoft.Extensions.Primitives; namespace BTCPayServer.Security diff --git a/BTCPayServer/Security/OpenIdAuthorizationHandler.cs b/BTCPayServer/Security/OpenIdAuthorizationHandler.cs index 3b0abf02d..a7413e41f 100644 --- a/BTCPayServer/Security/OpenIdAuthorizationHandler.cs +++ b/BTCPayServer/Security/OpenIdAuthorizationHandler.cs @@ -9,7 +9,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authentication; -using BTCPayServer.Authentication; using Microsoft.Extensions.Primitives; using static BTCPayServer.Security.OpenId.RestAPIPolicies; using OpenIddict.Abstractions; diff --git a/BTCPayServer/Security/Policies.cs b/BTCPayServer/Security/Policies.cs index 8ea09d01d..ad2a711f0 100644 --- a/BTCPayServer/Security/Policies.cs +++ b/BTCPayServer/Security/Policies.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using BTCPayServer.Authentication; using Microsoft.AspNetCore.Authorization; namespace BTCPayServer.Security diff --git a/BTCPayServer/Views/Stores/ShowToken.cshtml b/BTCPayServer/Views/Stores/ShowToken.cshtml index df7bcda1d..0c7b18e0b 100644 --- a/BTCPayServer/Views/Stores/ShowToken.cshtml +++ b/BTCPayServer/Views/Stores/ShowToken.cshtml @@ -1,4 +1,4 @@ -@model BTCPayServer.Authentication.BitTokenEntity +@model BTCPayServer.Security.Bitpay.BitTokenEntity @{ Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePageAndTitle(StoreNavPages.Tokens, "Access Tokens");