Mention the missing API permission in the response of a Greenfield request (#3195)

* Mention the missing API permission in the response header or body

* Fixes + Added a unit test. 1 TODO remains.

* Added MissingPermissionDescription to the error

* Update BTCPayServer.Tests/GreenfieldAPITests.cs

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>

* Fix tests

* [GreenField]: Make sure we are sending fully typed errors

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
This commit is contained in:
Wouter Samaey 2021-12-16 15:04:06 +01:00 committed by GitHub
parent 89a52703f6
commit 6de4f6a3ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 194 additions and 90 deletions

View file

@ -48,17 +48,24 @@ namespace BTCPayServer.Client
protected async Task HandleResponse(HttpResponseMessage message)
{
if (message.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity)
if (!message.IsSuccessStatusCode && message.Content?.Headers?.ContentType?.MediaType?.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) is true)
{
var err = JsonConvert.DeserializeObject<Models.GreenfieldValidationError[]>(await message.Content.ReadAsStringAsync());
;
throw new GreenFieldValidationException(err);
}
else if (!message.IsSuccessStatusCode && message.Content?.Headers?.ContentType?.MediaType?.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) is true)
{
var err = JsonConvert.DeserializeObject<Models.GreenfieldAPIError>(await message.Content.ReadAsStringAsync());
if (err.Code != null)
if (message.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity)
{
var err = JsonConvert.DeserializeObject<Models.GreenfieldValidationError[]>(await message.Content.ReadAsStringAsync());
throw new GreenFieldValidationException(err);
}
if (message.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
var err = JsonConvert.DeserializeObject<Models.GreenfieldPermissionAPIError>(await message.Content.ReadAsStringAsync());
throw new GreenFieldAPIException((int)message.StatusCode, err);
}
else
{
var err = JsonConvert.DeserializeObject<Models.GreenfieldAPIError>(await message.Content.ReadAsStringAsync());
if (err.Code != null)
throw new GreenFieldAPIException((int)message.StatusCode, err);
}
}
message.EnsureSuccessStatusCode();
}

View file

@ -0,0 +1,17 @@
using System;
namespace BTCPayServer.Client.Models
{
public class GreenfieldPermissionAPIError : GreenfieldAPIError
{
public GreenfieldPermissionAPIError(string missingPermission, string message = null) : base()
{
MissingPermission = missingPermission;
Code = "missing-permission";
Message = message ?? $"Insufficient API Permissions. Please use an API key with permission \"{MissingPermission}\". You can create an API key in your account's settings / Api Keys.";
}
public string MissingPermission { get; }
}
}

View file

@ -56,6 +56,23 @@ namespace BTCPayServer.Tests
var s = await client.GetStores();
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task MissingPermissionTest()
{
using (var tester = CreateServerTester())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var clientWithWrongPermissions = await user.CreateClient(Policies.CanViewProfile);
var e = await AssertAPIError("missing-permission", () => clientWithWrongPermissions.CreateStore(new CreateStoreRequest() { Name = "mystore" }));
Assert.Equal("missing-permission", e.APIError.Code);
Assert.NotNull(e.APIError.Message);
GreenfieldPermissionAPIError permissionError = Assert.IsType<GreenfieldPermissionAPIError>(e.APIError);
Assert.Equal(permissionError.MissingPermission, Policies.CanModifyStoreSettings);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
@ -161,7 +178,7 @@ namespace BTCPayServer.Tests
}));
await unrestricted.RevokeAPIKey(apiKey.ApiKey);
await AssertHttpError(404, async () => await unrestricted.RevokeAPIKey(apiKey.ApiKey));
await AssertAPIError("apikey-not-found", () => unrestricted.RevokeAPIKey(apiKey.ApiKey));
}
}
@ -251,10 +268,10 @@ namespace BTCPayServer.Tests
// Creating a new user without proper creds is now impossible (unauthorized)
// Because if registration are locked and that an admin exists, we don't accept unauthenticated connection
await AssertHttpError(401,
var ex = await AssertAPIError("unauthenticated",
async () => await unauthClient.CreateUser(
new CreateApplicationUserRequest() { Email = "test3@gmail.com", Password = "afewfoiewiou" }));
Assert.Equal("New user creation isn't authorized to users who are not admin", ex.APIError.Message);
// But should be ok with subscriptions unlocked
var settings = tester.PayTester.GetService<SettingsRepository>();
@ -263,7 +280,7 @@ namespace BTCPayServer.Tests
new CreateApplicationUserRequest() { Email = "test3@gmail.com", Password = "afewfoiewiou" });
// But it should be forbidden to create an admin without being authenticated
await AssertHttpError(403,
await AssertHttpError(401,
async () => await unauthClient.CreateUser(new CreateApplicationUserRequest()
{
Email = "admin2@gmail.com",
@ -281,7 +298,7 @@ namespace BTCPayServer.Tests
await AssertHttpError(403,
async () => await adminClient.CreateUser(
new CreateApplicationUserRequest() { Email = "test4@gmail.com", Password = "afewfoiewiou" }));
await AssertHttpError(403,
await AssertAPIError("missing-permission",
async () => await adminClient.CreateUser(new CreateApplicationUserRequest()
{
Email = "test4@gmail.com",
@ -394,9 +411,10 @@ namespace BTCPayServer.Tests
});
TestLogs.LogInformation("Can't archive without knowing the walletId");
await Assert.ThrowsAsync<HttpRequestException>(async () => await client.ArchivePullPayment("lol", result.Id));
var ex = await AssertAPIError("missing-permission", async () => await client.ArchivePullPayment("lol", result.Id));
Assert.Equal("btcpay.store.canmanagepullpayments", ((GreenfieldPermissionAPIError)ex.APIError).MissingPermission);
TestLogs.LogInformation("Can't archive without permission");
await Assert.ThrowsAsync<HttpRequestException>(async () => await unauthenticated.ArchivePullPayment(storeId, result.Id));
await AssertAPIError("unauthenticated", async () => await unauthenticated.ArchivePullPayment(storeId, result.Id));
await client.ArchivePullPayment(storeId, result.Id);
result = await unauthenticated.GetPullPayment(result.Id);
Assert.True(result.Archived);
@ -584,10 +602,11 @@ namespace BTCPayServer.Tests
return new DateTimeOffset(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day, dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Offset);
}
private async Task AssertAPIError(string expectedError, Func<Task> act)
private async Task<GreenFieldAPIException> AssertAPIError(string expectedError, Func<Task> act)
{
var err = await Assert.ThrowsAsync<GreenFieldAPIException>(async () => await act());
Assert.Equal(expectedError, err.APIError.Code);
return err;
}
[Fact(Timeout = TestTimeout)]
@ -662,15 +681,8 @@ namespace BTCPayServer.Tests
private async Task AssertHttpError(int code, Func<Task> act)
{
try
{
// Eventually all exception should be GreenFieldAPIException
var ex = await Assert.ThrowsAsync<HttpRequestException>(act);
Assert.Contains(code.ToString(), ex.Message);
}
catch (ThrowsException e) when (e.InnerException is GreenFieldAPIException ex && ex.HttpCode == code)
{
}
var ex = await Assert.ThrowsAsync<GreenFieldAPIException>(act);
Assert.Equal(code, ex.HttpCode);
}
[Fact(Timeout = TestTimeout)]
@ -696,12 +708,12 @@ namespace BTCPayServer.Tests
Assert.Equal(apiKeyProfileUserData.Email, user.RegisterDetails.Email);
Assert.Contains("ServerAdmin", apiKeyProfileUserData.Roles);
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
await AssertHttpError(403, async () => await clientInsufficient.GetCurrentUser());
await clientServer.GetCurrentUser();
await clientProfile.GetCurrentUser();
await clientBasic.GetCurrentUser();
await Assert.ThrowsAsync<HttpRequestException>(async () =>
await AssertHttpError(403, async () =>
await clientInsufficient.CreateUser(new CreateApplicationUserRequest()
{
Email = $"{Guid.NewGuid()}@g.com",
@ -1434,11 +1446,9 @@ namespace BTCPayServer.Tests
Assert.Single(info.NodeURIs);
Assert.NotEqual(0, info.BlockHeight);
var err = await Assert.ThrowsAsync<GreenFieldAPIException>(async () => await client.GetLightningNodeChannels("BTC"));
Assert.Equal(503, err.HttpCode);
await AssertAPIError("ligthning-node-unavailable", () => client.GetLightningNodeChannels("BTC"));
// Not permission for the store!
var err2 = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels(user.StoreId, "BTC"));
Assert.Contains("403", err2.Message);
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels(user.StoreId, "BTC"));
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
{
Amount = LightMoney.Satoshis(1000),
@ -1451,8 +1461,7 @@ namespace BTCPayServer.Tests
client = await user.CreateClient($"{Policies.CanUseLightningNodeInStore}:{user.StoreId}");
// Not permission for the server
err2 = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
Assert.Contains("403", err2.Message);
await AssertAPIError("missing-permission", () => client.GetLightningNodeChannels("BTC"));
var data = await client.GetLightningNodeChannels(user.StoreId, "BTC");
Assert.Equal(2, data.Count());
@ -1502,7 +1511,7 @@ namespace BTCPayServer.Tests
await client.GetLightningNodeInfo(user.StoreId, "BTC");
// But if not admin anymore, nope
await user.MakeAdmin(false);
await AssertAPIError("insufficient-api-permissions", () => client.GetLightningNodeInfo(user.StoreId, "BTC"));
await AssertAPIError("missing-permission", () => client.GetLightningNodeInfo(user.StoreId, "BTC"));
}
}

View file

@ -77,12 +77,11 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> RevokeKey(string apikey)
{
if (string.IsNullOrEmpty(apikey))
return NotFound();
if (await _apiKeyRepository.Remove(apikey, _userManager.GetUserId(User)))
if (!string.IsNullOrEmpty(apikey) &&
await _apiKeyRepository.Remove(apikey, _userManager.GetUserId(User)))
return Ok();
else
return NotFound();
return this.CreateAPIError("apikey-not-found", "This apikey does not exists");
}
private static ApiKeyData FromModel(APIKeyData data)

View file

@ -34,5 +34,9 @@ namespace BTCPayServer.Controllers.GreenField
{
return controller.StatusCode(httpCode, new GreenfieldAPIError(errorCode, errorMessage));
}
public static IActionResult CreateAPIPermissionError(this ControllerBase controller, string missingPermission, string message = null)
{
return controller.StatusCode(403, new GreenfieldPermissionAPIError(missingPermission, message));
}
}
}

View file

@ -269,7 +269,7 @@ namespace BTCPayServer.Controllers.GreenField
}
protected JsonHttpException ErrorShouldBeAdminForInternalNode()
{
return new JsonHttpException(this.CreateAPIError(403, "insufficient-api-permissions", "The user should be admin to use the internal lightning node"));
return new JsonHttpException(this.CreateAPIError(403, "missing-permission", "The user should be admin to use the internal lightning node"));
}
private LightningInvoiceData ToModel(LightningInvoice invoice)

View file

@ -80,30 +80,29 @@ namespace BTCPayServer.Controllers.GreenField
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
public ActionResult<LightningNetworkPaymentMethodData> GetLightningNetworkPaymentMethod(string storeId, string cryptoCode)
{
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
{
return NotFound();
}
AssertSupportLightning(cryptoCode);
var method = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, Store);
if (method is null)
{
return NotFound();
throw ErrorPaymentMethodNotConfigured();
}
return Ok(method);
}
protected JsonHttpException ErrorPaymentMethodNotConfigured()
{
return new JsonHttpException(this.CreateAPIError(404, "paymentmethod-not-configured", "The lightning payment method is not set up"));
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
public async Task<IActionResult> RemoveLightningNetworkPaymentMethod(
string storeId,
string cryptoCode)
{
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
{
return NotFound();
}
AssertSupportLightning(cryptoCode);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var store = Store;
@ -118,11 +117,7 @@ namespace BTCPayServer.Controllers.GreenField
[FromBody] UpdateLightningNetworkPaymentMethodRequest request)
{
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
if (!GetNetwork(cryptoCode, out var network))
{
return NotFound();
}
AssertSupportLightning(cryptoCode);
if (string.IsNullOrEmpty(request.ConnectionString))
{
@ -210,11 +205,14 @@ namespace BTCPayServer.Controllers.GreenField
paymentMethod.PaymentId.ToStringNormalized(), paymentMethod.DisableBOLT11PaymentOption);
}
private bool GetNetwork(string cryptoCode, [MaybeNullWhen(false)] out BTCPayNetwork network)
private BTCPayNetwork AssertSupportLightning(string cryptoCode)
{
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
network = network?.SupportLightning is true ? network : null;
return network != null;
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
if (network is null)
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code isn't set up in this BTCPay Server instance"));
if (!(network.SupportLightning is true))
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code doesn't support lightning"));
return network;
}
private async Task<bool> CanUseInternalLightning()

View file

@ -79,20 +79,21 @@ namespace BTCPayServer.Controllers.GreenField
string storeId,
string cryptoCode)
{
if (!GetCryptoCodeWallet(cryptoCode, out BTCPayNetwork _, out BTCPayWallet _))
{
return NotFound();
}
AssertCryptoCodeWallet(cryptoCode, out BTCPayNetwork _, out BTCPayWallet _);
var method = GetExistingBtcLikePaymentMethod(cryptoCode);
if (method is null)
{
return NotFound();
throw ErrorPaymentMethodNotConfigured();
}
return Ok(method);
}
protected JsonHttpException ErrorPaymentMethodNotConfigured()
{
return new JsonHttpException(this.CreateAPIError(404, "paymentmethod-not-configured", "The lightning node is not set up"));
}
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview")]
public IActionResult GetOnChainPaymentMethodPreview(
@ -100,15 +101,12 @@ namespace BTCPayServer.Controllers.GreenField
string cryptoCode,
int offset = 0, int amount = 10)
{
if (!GetCryptoCodeWallet(cryptoCode, out var network, out BTCPayWallet _))
{
return NotFound();
}
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
var paymentMethod = GetExistingBtcLikePaymentMethod(cryptoCode);
if (string.IsNullOrEmpty(paymentMethod?.DerivationScheme))
{
return NotFound();
throw ErrorPaymentMethodNotConfigured();
}
try
@ -149,10 +147,7 @@ namespace BTCPayServer.Controllers.GreenField
[FromBody] UpdateOnChainPaymentMethodRequest paymentMethodData,
int offset = 0, int amount = 10)
{
if (!GetCryptoCodeWallet(cryptoCode, out var network, out BTCPayWallet _))
{
return NotFound();
}
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
if (string.IsNullOrEmpty(paymentMethodData?.DerivationScheme))
{
@ -202,10 +197,7 @@ namespace BTCPayServer.Controllers.GreenField
string cryptoCode,
int offset = 0, int amount = 10)
{
if (!GetCryptoCodeWallet(cryptoCode, out BTCPayNetwork _, out BTCPayWallet _))
{
return NotFound();
}
AssertCryptoCodeWallet(cryptoCode, out _, out _);
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
var store = Store;
@ -222,11 +214,7 @@ namespace BTCPayServer.Controllers.GreenField
[FromBody] UpdateOnChainPaymentMethodRequest request)
{
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
if (!GetCryptoCodeWallet(cryptoCode, out var network, out var wallet))
{
return NotFound();
}
AssertCryptoCodeWallet(cryptoCode, out var network, out var wallet);
if (string.IsNullOrEmpty(request?.DerivationScheme))
{
@ -271,11 +259,15 @@ namespace BTCPayServer.Controllers.GreenField
}
}
private bool GetCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network, out BTCPayWallet wallet)
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network, out BTCPayWallet wallet)
{
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
wallet = network != null ? _walletProvider.GetWallet(network) : null;
return wallet != null;
if (network is null)
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code isn't set up in this BTCPay Server instance"));
wallet = _walletProvider.GetWallet(network);
if (wallet is null)
throw ErrorPaymentMethodNotConfigured();
}
private OnChainPaymentMethodData GetExistingBtcLikePaymentMethod(string cryptoCode, StoreData store = null)

View file

@ -102,11 +102,11 @@ namespace BTCPayServer.Controllers.GreenField
// If registration are locked and that an admin exists, don't accept unauthenticated connection
if (anyAdmin && policies.LockSubscription && !isAuth)
return Unauthorized();
return this.CreateAPIError(401, "unauthenticated", "New user creation isn't authorized to users who are not admin");
// Even if subscription are unlocked, it is forbidden to create admin unauthenticated
if (anyAdmin && request.IsAdministrator is true && !isAuth)
return Forbid(AuthenticationSchemes.GreenfieldBasic);
return this.CreateAPIError(401, "unauthenticated", "New admin creation isn't authorized to users who are not admin");
// You are de-facto admin if there is no other admin, else you need to be auth and pass policy requirements
bool isAdmin = anyAdmin ? (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded
&& (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.Unrestricted))).Succeeded
@ -114,14 +114,14 @@ namespace BTCPayServer.Controllers.GreenField
: true;
// You need to be admin to create an admin
if (request.IsAdministrator is true && !isAdmin)
return Forbid(AuthenticationSchemes.GreenfieldBasic);
return this.CreateAPIPermissionError(Policies.Unrestricted, $"Insufficient API Permissions. Please use an API key with permission: {Policies.Unrestricted} and be an admin.");
if (!isAdmin && (policies.LockSubscription || (await _settingsRepository.GetPolicies()).DisableNonAdminCreateUserApi))
{
// If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission
var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser))).Succeeded;
if (!isAuth || !canCreateUser)
return Forbid(AuthenticationSchemes.GreenfieldBasic);
return this.CreateAPIPermissionError(Policies.CanCreateUser);
}
var user = new ApplicationUser

View file

@ -472,6 +472,7 @@ namespace BTCPayServer.Hosting
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
{
app.UseMiddleware<GreenfieldMiddleware>();
app.UseMiddleware<BTCPayMiddleware>();
return app;
}

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Security.GreenField;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace BTCPayServer.Hosting
{
public class GreenfieldMiddleware
{
private readonly RequestDelegate _next;
private readonly IOptions<MvcNewtonsoftJsonOptions> _mvcOptions;
public GreenfieldMiddleware(RequestDelegate next, IOptions<MvcNewtonsoftJsonOptions> mvcOptions)
{
_next = next;
_mvcOptions = mvcOptions;
}
public async Task Invoke(HttpContext httpContext)
{
await _next(httpContext);
if (!httpContext.Response.HasStarted &&
!IsJson(httpContext.Response.ContentType) &&
!IsHtml(httpContext.Response.ContentType) &&
!httpContext.GetIsBitpayAPI() &&
(httpContext.Response.StatusCode == 401 || httpContext.Response.StatusCode == 403))
{
if (httpContext.Response.StatusCode == 403 &&
httpContext.Items.TryGetValue(GreenFieldAuthorizationHandler.RequestedPermissionKey, out var p) &&
p is string policy)
{
var outputObj = new GreenfieldPermissionAPIError(policy);
await WriteError(httpContext, outputObj);
}
if (httpContext.Response.StatusCode == 401)
{
var outputObj = new GreenfieldAPIError("unauthenticated", "Authentication is required for accessing this endpoint");
await WriteError(httpContext, outputObj);
}
}
}
private async Task WriteError(HttpContext httpContext, object outputObj)
{
string output = JsonConvert.SerializeObject(outputObj, _mvcOptions.Value.SerializerSettings);
var outputBytes = new UTF8Encoding(false).GetBytes(output);
httpContext.Response.Headers.Add("Content-Type", "application/json");
httpContext.Response.Headers.Add("Content-Length", outputBytes.Length.ToString(CultureInfo.InvariantCulture));
await httpContext.Response.Body.WriteAsync(outputBytes, 0, outputBytes.Length);
}
private bool IsHtml(string contentType)
{
return contentType?.StartsWith("text/html", StringComparison.OrdinalIgnoreCase) is true;
}
private bool IsJson(string contentType)
{
return contentType?.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) is true;
}
}
}

View file

@ -1,11 +1,17 @@
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Security.GreenField
{
@ -117,6 +123,8 @@ namespace BTCPayServer.Security.GreenField
{
context.Succeed(requirement);
}
_HttpContext.Items[RequestedPermissionKey] = policy;
}
public const string RequestedPermissionKey = nameof(RequestedPermissionKey);
}
}

View file

@ -81,7 +81,7 @@ else
ItemCode = item.Id
}, Context.Request.Scheme, Context.Request.Host.ToString()));
var lnUrl = LNURL.EncodeUri(lnurlEndpoint, "payRequest", supported.UseBech32Scheme);
<a href="@lnUrl"><vc:qr-code data="@lnUrl.ToString().ToUpperInvariant()" /></a>
<a href="@lnUrl" rel="noreferrer noopener"><vc:qr-code data="@lnUrl.ToString().ToUpperInvariant()" /></a>
}
}
</div>