Add additional permission for pull payments (#4539)

* Add additional permission for pull payments

* Apply suggestions from code review
This commit is contained in:
Andrew Camilleri 2023-01-26 01:46:05 +01:00 committed by GitHub
parent 69e90b7ff1
commit f46443a5e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 34 deletions

View file

@ -30,6 +30,8 @@ namespace BTCPayServer.Client
public const string CanCreateUser = "btcpay.server.cancreateuser"; public const string CanCreateUser = "btcpay.server.cancreateuser";
public const string CanDeleteUser = "btcpay.user.candeleteuser"; public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments"; public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts"; public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts"; public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts";
public const string CanDepositToCustodianAccounts = "btcpay.store.candeposittocustodianaccount"; public const string CanDepositToCustodianAccounts = "btcpay.store.candeposittocustodianaccount";
@ -64,6 +66,8 @@ namespace BTCPayServer.Client
yield return CanViewLightningInvoiceInStore; yield return CanViewLightningInvoiceInStore;
yield return CanCreateLightningInvoiceInStore; yield return CanCreateLightningInvoiceInStore;
yield return CanManagePullPayments; yield return CanManagePullPayments;
yield return CanCreatePullPayments;
yield return CanCreateNonApprovedPullPayments;
yield return CanViewCustodianAccounts; yield return CanViewCustodianAccounts;
yield return CanManageCustodianAccounts; yield return CanManageCustodianAccounts;
yield return CanDepositToCustodianAccounts; yield return CanDepositToCustodianAccounts;
@ -95,6 +99,11 @@ namespace BTCPayServer.Client
} }
public class Permission public class Permission
{ {
static Permission()
{
Init();
}
public static Permission Create(string policy, string scope = null) public static Permission Create(string policy, string scope = null)
{ {
if (TryCreatePermission(policy, scope, out var r)) if (TryCreatePermission(policy, scope, out var r))
@ -179,39 +188,55 @@ namespace BTCPayServer.Client
private bool ContainsPolicy(string subpolicy) private bool ContainsPolicy(string subpolicy)
{ {
if (this.Policy == Policies.Unrestricted) return ContainsPolicy(Policy, subpolicy);
return true; }
if (this.Policy == subpolicy)
return true; private static bool ContainsPolicy(string policy, string subpolicy)
switch (subpolicy)
{ {
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyStoreSettings: if (policy == Policies.Unrestricted)
case Policies.CanViewInvoices when this.Policy == Policies.CanModifyInvoices:
case Policies.CanModifyStoreWebhooks when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewInvoices when this.Policy == Policies.CanViewStoreSettings:
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanModifyInvoices when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewProfile when this.Policy == Policies.CanModifyProfile:
case Policies.CanModifyPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanManagePullPayments when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyPaymentRequests:
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
case Policies.CanViewLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
case Policies.CanViewLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
case Policies.CanViewNotificationsForUser when this.Policy == Policies.CanManageNotificationsForUser:
case Policies.CanUseInternalLightningNode when this.Policy == Policies.CanModifyServerSettings:
case Policies.CanViewCustodianAccounts when this.Policy == Policies.CanManageCustodianAccounts:
case Policies.CanViewCustodianAccounts when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanManageCustodianAccounts when this.Policy == Policies.CanModifyStoreSettings:
return true; return true;
default: if (policy == subpolicy)
return false; return true;
if (!PolicyMap.TryGetValue(policy, out var subPolicies)) return false;
return subPolicies.Contains(subpolicy) || subPolicies.Any(s => ContainsPolicy(s, subpolicy));
}
private static Dictionary<string, HashSet<string>> PolicyMap = new();
private static void Init()
{
PolicyHasChild(Policies.CanModifyStoreSettings,
Policies.CanManageCustodianAccounts, Policies.CanManagePullPayments, Policies.CanModifyInvoices, Policies.CanViewStoreSettings, Policies.CanModifyStoreWebhooks, Policies.CanModifyPaymentRequests );
PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments );
PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments );
PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests );
PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile );
PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore );
PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser );
PolicyHasChild(Policies.CanModifyServerSettings, Policies.CanUseInternalLightningNode );
PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode,Policies.CanViewLightningInvoiceInternalNode );
PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts );
PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice );
PolicyHasChild(Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests );
}
private static void PolicyHasChild(string policy, params string[] subPolicies)
{
if (PolicyMap.TryGetValue(policy, out var existingSubPolicies))
{
foreach (string subPolicy in subPolicies)
{
existingSubPolicies.Add(subPolicy);
} }
} }
else
{
PolicyMap.Add(policy,subPolicies.ToHashSet());
}
}
public string Scope { get; } public string Scope { get; }
public string Policy { get; } public string Policy { get; }

View file

@ -906,6 +906,49 @@ namespace BTCPayServer.Tests
payout = (await client.GetPayouts(payout.PullPaymentId)).First(data => data.Id == payout.Id); payout = (await client.GetPayouts(payout.PullPaymentId)).First(data => data.Id == payout.Id);
Assert.Equal(PayoutState.Completed, payout.State); Assert.Equal(PayoutState.Completed, payout.State);
await AssertAPIError("invalid-state", async () => await client.MarkPayoutPaid(storeId, payout.Id)); await AssertAPIError("invalid-state", async () => await client.MarkPayoutPaid(storeId, payout.Id));
//permission test around auto approved pps and payouts
var nonApproved = await acc.CreateClient(Policies.CanCreateNonApprovedPullPayments);
var approved = await acc.CreateClient(Policies.CanCreatePullPayments);
await AssertPermissionError(Policies.CanCreatePullPayments, async () =>
{
var pullPayment = await nonApproved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
{
Amount = 100,
Currency = "USD",
Name = "pull payment",
PaymentMethods = new[] { "BTC" },
AutoApproveClaims = true
});
});
await AssertPermissionError(Policies.CanCreatePullPayments, async () =>
{
var pullPayment = await nonApproved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 100,
PaymentMethod = "BTC",
Approved = true,
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
});
});
var pullPayment = await approved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
{
Amount = 100,
Currency = "USD",
Name = "pull payment",
PaymentMethods = new[] { "BTC" },
AutoApproveClaims = true
});
var p = await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 100,
PaymentMethod = "BTC",
Approved = true,
Destination = new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, Network.RegTest).ToString()
});
} }
[Fact] [Fact]

View file

@ -11,6 +11,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Security;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -33,13 +34,15 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly CurrencyNameTable _currencyNameTable; private readonly CurrencyNameTable _currencyNameTable;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
private readonly IEnumerable<IPayoutHandler> _payoutHandlers; private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
private readonly IAuthorizationService _authorizationService;
public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService, public GreenfieldPullPaymentController(PullPaymentHostedService pullPaymentService,
LinkGenerator linkGenerator, LinkGenerator linkGenerator,
ApplicationDbContextFactory dbContextFactory, ApplicationDbContextFactory dbContextFactory,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
Services.BTCPayNetworkJsonSerializerSettings serializerSettings, Services.BTCPayNetworkJsonSerializerSettings serializerSettings,
IEnumerable<IPayoutHandler> payoutHandlers) IEnumerable<IPayoutHandler> payoutHandlers,
IAuthorizationService authorizationService)
{ {
_pullPaymentService = pullPaymentService; _pullPaymentService = pullPaymentService;
_linkGenerator = linkGenerator; _linkGenerator = linkGenerator;
@ -47,6 +50,7 @@ namespace BTCPayServer.Controllers.Greenfield
_currencyNameTable = currencyNameTable; _currencyNameTable = currencyNameTable;
_serializerSettings = serializerSettings; _serializerSettings = serializerSettings;
_payoutHandlers = payoutHandlers; _payoutHandlers = payoutHandlers;
_authorizationService = authorizationService;
} }
[HttpGet("~/api/v1/stores/{storeId}/pull-payments")] [HttpGet("~/api/v1/stores/{storeId}/pull-payments")]
@ -62,7 +66,7 @@ namespace BTCPayServer.Controllers.Greenfield
} }
[HttpPost("~/api/v1/stores/{storeId}/pull-payments")] [HttpPost("~/api/v1/stores/{storeId}/pull-payments")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CreatePullPayment(string storeId, CreatePullPaymentRequest request) public async Task<IActionResult> CreatePullPayment(string storeId, CreatePullPaymentRequest request)
{ {
if (request is null) if (request is null)
@ -70,6 +74,16 @@ namespace BTCPayServer.Controllers.Greenfield
ModelState.AddModelError(string.Empty, "Missing body"); ModelState.AddModelError(string.Empty, "Missing body");
return this.CreateValidationError(ModelState); return this.CreateValidationError(ModelState);
} }
if (request.AutoApproveClaims)
{
if (!(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanCreatePullPayments))).Succeeded)
{
return this.CreateAPIPermissionError(Policies.CanCreatePullPayments);
}
}
if (request.Amount <= 0.0m) if (request.Amount <= 0.0m)
{ {
ModelState.AddModelError(nameof(request.Amount), "The amount should more than 0."); ModelState.AddModelError(nameof(request.Amount), "The amount should more than 0.");
@ -304,9 +318,18 @@ namespace BTCPayServer.Controllers.Greenfield
} }
[HttpPost("~/api/v1/stores/{storeId}/payouts")] [HttpPost("~/api/v1/stores/{storeId}/payouts")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CreatePayoutThroughStore(string storeId, CreatePayoutThroughStoreRequest request) public async Task<IActionResult> CreatePayoutThroughStore(string storeId, CreatePayoutThroughStoreRequest request)
{ {
if (request.Approved is true)
{
if (!(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanCreatePullPayments))).Succeeded)
{
return this.CreateAPIPermissionError(Policies.CanCreatePullPayments);
}
}
if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId)) if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
{ {
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");

View file

@ -542,6 +542,10 @@ namespace BTCPayServer.Controllers
{$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "The app will be able to view the selected stores' payment requests.")}, {$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "The app will be able to view the selected stores' payment requests.")},
{Policies.CanManagePullPayments, ("Manage your pull payments", "The app will be able to view, modify, delete and create pull payments on all your stores.")}, {Policies.CanManagePullPayments, ("Manage your pull payments", "The app will be able to view, modify, delete and create pull payments on all your stores.")},
{$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "The app will be able to view, modify, delete and create new pull payments on the selected stores.")}, {$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "The app will be able to view, modify, delete and create new pull payments on the selected stores.")},
{Policies.CanCreatePullPayments, ("Create pull payments", "The app will be able to create pull payments on all your stores.")},
{$"{Policies.CanCreatePullPayments}:", ("Create pull payments in selected stores", "The app will be able to create new pull payments on the selected stores.")},
{Policies.CanCreateNonApprovedPullPayments, ("Create non-approved pull payments", "The app will be able to create pull payments without automatic approval on all your stores.")},
{$"{Policies.CanCreateNonApprovedPullPayments}:", ("Create non-approved pull payments in selected stores", "The app will be able to view, modify, delete and create pull payments without automatic approval on the selected stores.")},
{Policies.CanUseInternalLightningNode, ("Use the internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")}, {Policies.CanUseInternalLightningNode, ("Use the internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},
{Policies.CanViewLightningInvoiceInternalNode, ("View invoices from internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to view BOLT11 invoices.")}, {Policies.CanViewLightningInvoiceInternalNode, ("View invoices from internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to view BOLT11 invoices.")},
{Policies.CanCreateLightningInvoiceInternalNode, ("Create invoices with internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices.")}, {Policies.CanCreateLightningInvoiceInternalNode, ("Create invoices with internal lightning node", "The app will be able to use the internal BTCPay Server lightning node to create BOLT11 invoices.")},

View file

@ -112,7 +112,7 @@
"securitySchemes": { "securitySchemes": {
"API_Key": { "API_Key": {
"type": "apiKey", "type": "apiKey",
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.canviewlightninginvoiceinternalnode`: View invoices from internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.canviewcustodianaccounts`: View exchange accounts linked to your stores\n* `btcpay.store.canmanagecustodianaccounts`: Manage exchange accounts linked to your stores\n* `btcpay.store.candeposittocustodianaccount`: Deposit funds to exchange accounts linked to your stores\n* `btcpay.store.canwithdrawfromcustodianaccount`: Withdraw funds from exchange accounts to your store\n* `btcpay.store.cantradecustodianaccount`: Trade funds on your store's exchange accounts\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canmanagepullpayments`: Manage your pull payments\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.canviewlightninginvoice`: View the lightning invoices associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices from the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n", "description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.canviewlightninginvoiceinternalnode`: View invoices from internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.canviewcustodianaccounts`: View exchange accounts linked to your stores\n* `btcpay.store.canmanagecustodianaccounts`: Manage exchange accounts linked to your stores\n* `btcpay.store.candeposittocustodianaccount`: Deposit funds to exchange accounts linked to your stores\n* `btcpay.store.canwithdrawfromcustodianaccount`: Withdraw funds from exchange accounts to your store\n* `btcpay.store.cantradecustodianaccount`: Trade funds on your store's exchange accounts\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canmanagepullpayments`: Manage your pull payments\n* `btcpay.store.cancreatepullpayments`: Create pull payments\n* `btcpay.store.cancreatenonapprovedpullpayments`: Create non-approved pull payments\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.canviewlightninginvoice`: View the lightning invoices associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices from the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n",
"name": "Authorization", "name": "Authorization",
"in": "header" "in": "header"
}, },

View file

@ -159,7 +159,8 @@
"security": [ "security": [
{ {
"API_Key": [ "API_Key": [
"btcpay.store.canmanagepullpayments" "btcpay.store.cancreatepullpayments",
"btcpay.store.cancreatenonapprovedpullpayments"
], ],
"Basic": [] "Basic": []
} }
@ -470,7 +471,8 @@
"security": [ "security": [
{ {
"API_Key": [ "API_Key": [
"btcpay.store.canmanagepullpayments" "btcpay.store.cancreatepullpayments",
"btcpay.store.cancreatenonapprovedpullpayments"
], ],
"Basic": [] "Basic": []
} }