Greenfield: Admins can create/delete API keys of any user (#4680)

* Greenfield: Admins can create/delete API keys of any user

* Greenfield: Improve doc for scoped apikey (Close #4673)

* Fix permissions hierarchy

* Update BTCPayServer.Client/Permissions.cs

* Fix tests

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
This commit is contained in:
Nicolas Dorier 2023-02-24 16:19:03 +09:00 committed by GitHub
parent d14dafc871
commit 4ae05272c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 401 additions and 169 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -22,6 +23,15 @@ namespace BTCPayServer.Client
return await HandleResponse<ApiKeyData>(response); return await HandleResponse<ApiKeyData>(response);
} }
public virtual async Task<ApiKeyData> CreateAPIKey(string userId, CreateApiKeyRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{userId}/api-keys",
bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<ApiKeyData>(response);
}
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default) public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
{ {
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token); var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
@ -35,5 +45,14 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token); var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token);
await HandleResponse(response); await HandleResponse(response);
} }
public virtual async Task RevokeAPIKey(string userId, string apikey, CancellationToken token = default)
{
if (apikey == null)
throw new ArgumentNullException(nameof(apikey));
if (userId is null)
throw new ArgumentNullException(nameof(userId));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/users/{userId}/api-keys/{apikey}", null, HttpMethod.Delete), token);
await HandleResponse(response);
}
} }
} }

View File

@ -28,6 +28,7 @@ namespace BTCPayServer.Client
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser"; public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
public const string CanViewUsers = "btcpay.server.canviewusers"; public const string CanViewUsers = "btcpay.server.canviewusers";
public const string CanCreateUser = "btcpay.server.cancreateuser"; public const string CanCreateUser = "btcpay.server.cancreateuser";
public const string CanManageUsers = "btcpay.server.canmanageusers";
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 CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
@ -73,6 +74,7 @@ namespace BTCPayServer.Client
yield return CanDepositToCustodianAccounts; yield return CanDepositToCustodianAccounts;
yield return CanWithdrawFromCustodianAccounts; yield return CanWithdrawFromCustodianAccounts;
yield return CanTradeCustodianAccount; yield return CanTradeCustodianAccount;
yield return CanManageUsers;
} }
} }
public static bool IsValidPolicy(string policy) public static bool IsValidPolicy(string policy)
@ -206,15 +208,23 @@ namespace BTCPayServer.Client
private static void Init() private static void Init()
{ {
PolicyHasChild(Policies.CanModifyStoreSettings, PolicyHasChild(Policies.CanModifyStoreSettings,
Policies.CanManageCustodianAccounts, Policies.CanManagePullPayments, Policies.CanModifyInvoices, Policies.CanViewStoreSettings, Policies.CanModifyStoreWebhooks, Policies.CanModifyPaymentRequests ); Policies.CanManageCustodianAccounts,
Policies.CanManagePullPayments,
Policies.CanModifyInvoices,
Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks,
Policies.CanModifyPaymentRequests);
PolicyHasChild(Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments ); PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments );
PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments ); PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments );
PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests ); PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests );
PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile ); PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile );
PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore ); PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore );
PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser ); PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser );
PolicyHasChild(Policies.CanModifyServerSettings, Policies.CanUseInternalLightningNode ); PolicyHasChild(Policies.CanModifyServerSettings,
Policies.CanUseInternalLightningNode,
Policies.CanManageUsers);
PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode,Policies.CanViewLightningInvoiceInternalNode ); PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode,Policies.CanViewLightningInvoiceInternalNode );
PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts ); PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts );
PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice ); PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice );

View File

@ -190,6 +190,41 @@ namespace BTCPayServer.Tests
await unrestricted.RevokeAPIKey(apiKey.ApiKey); await unrestricted.RevokeAPIKey(apiKey.ApiKey);
await AssertAPIError("apikey-not-found", () => unrestricted.RevokeAPIKey(apiKey.ApiKey)); await AssertAPIError("apikey-not-found", () => unrestricted.RevokeAPIKey(apiKey.ApiKey));
// Admin create API key to new user
acc = tester.NewAccount();
await acc.GrantAccessAsync(isAdmin: true);
unrestricted = await acc.CreateClient();
var newUser = await unrestricted.CreateUser(new CreateApplicationUserRequest() { Email = Utils.GenerateEmail(), Password = "Kitten0@" });
var newUserAPIKey = await unrestricted.CreateAPIKey(newUser.Id, new CreateApiKeyRequest()
{
Label = "Hello world",
Permissions = new Permission[] { Permission.Create(Policies.CanViewProfile) }
});
var newUserClient = acc.CreateClientFromAPIKey(newUserAPIKey.ApiKey);
Assert.Equal(newUser.Id, (await newUserClient.GetCurrentUser()).Id);
// Admin delete it
await unrestricted.RevokeAPIKey(newUser.Id, newUserAPIKey.ApiKey);
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetCurrentUser());
// Admin create store
var store = await unrestricted.CreateStore(new CreateStoreRequest() { Name = "Pouet lol" });
// Grant right to another user
newUserAPIKey = await unrestricted.CreateAPIKey(newUser.Id, new CreateApiKeyRequest()
{
Label = "Hello world",
Permissions = new Permission[] { Permission.Create(Policies.CanViewInvoices, store.Id) },
});
// Despite the grant, the user shouldn't be able to get the invoices!
newUserClient = acc.CreateClientFromAPIKey(newUserAPIKey.ApiKey);
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
// if user is a guest or owner, then it should be ok
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { UserId = newUser.Id, Role = "Guest" });
await newUserClient.GetInvoices(store.Id);
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]

View File

@ -219,7 +219,7 @@ namespace BTCPayServer.Tests
var account = parent.PayTester.GetController<UIAccountController>(); var account = parent.PayTester.GetController<UIAccountController>();
RegisterDetails = new RegisterViewModel() RegisterDetails = new RegisterViewModel()
{ {
Email = Guid.NewGuid() + "@toto.com", Email = Utils.GenerateEmail(),
ConfirmPassword = "Kitten0@", ConfirmPassword = "Kitten0@",
Password = "Kitten0@", Password = "Kitten0@",
IsAdmin = isAdmin IsAdmin = isAdmin

View File

@ -160,25 +160,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync(); await tester.StartAsync();
var acc = tester.NewAccount(); var acc = tester.NewAccount();
var description = var description = UtilitiesTests.GetSecuritySchemeDescription();
"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#OTHERPERMISSIONS#\n\nThe following permissions are available if the user is an administrator:\n\n#SERVERPERMISSIONS#\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#STOREPERMISSIONS#\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";
var storePolicies =
UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
Policies.IsStorePolicy(pair.Key) && !pair.Key.EndsWith(":", StringComparison.InvariantCulture));
var serverPolicies =
UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
Policies.IsServerPolicy(pair.Key));
var otherPolicies =
UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
description = description.Replace("#OTHERPERMISSIONS#",
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
.Replace("#SERVERPERMISSIONS#",
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
.Replace("#STOREPERMISSIONS#",
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")));
TestLogs.LogInformation(description); TestLogs.LogInformation(description);
var sresp = Assert var sresp = Assert
@ -187,7 +169,11 @@ namespace BTCPayServer.Tests
JObject json = JObject.Parse(sresp); JObject json = JObject.Parse(sresp);
Assert.Equal(description, json["components"]["securitySchemes"]["API_Key"]["description"].Value<string>()); // If this test fail, run `UpdateSwagger` once.
if (description != json["components"]["securitySchemes"]["API_Key"]["description"].Value<string>())
{
Assert.False(true, "Please run manually the test `UpdateSwagger` once");
}
} }
[Fact] [Fact]

View File

@ -1,15 +1,11 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Amazon.Runtime.Internal; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Controllers;
using Microsoft.Extensions.Configuration;
using Microsoft.WindowsAzure.Storage.RetryPolicies;
using NBitcoin.DataEncoders;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
@ -20,6 +16,39 @@ namespace BTCPayServer.Tests
/// </summary> /// </summary>
public class UtilitiesTests public class UtilitiesTests
{ {
internal static string GetSecuritySchemeDescription()
{
var 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#OTHERPERMISSIONS#\n\nThe following permissions are available if the user is an administrator:\n\n#SERVERPERMISSIONS#\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#STOREPERMISSIONS#\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";
var storePolicies =
UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
Policies.IsStorePolicy(pair.Key) && !pair.Key.EndsWith(":", StringComparison.InvariantCulture));
var serverPolicies =
UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
Policies.IsServerPolicy(pair.Key));
var otherPolicies =
UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
description = description.Replace("#OTHERPERMISSIONS#",
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
.Replace("#SERVERPERMISSIONS#",
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
.Replace("#STOREPERMISSIONS#",
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")));
return description;
}
[Trait("Utilities", "Utilities")]
[Fact]
public void UpdateSwagger()
{
var filePath = Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "wwwroot", "swagger", "v1", "swagger.template.json");
var o = JObject.Parse(File.ReadAllText(filePath));
o["components"]["securitySchemes"]["API_Key"]["description"] = GetSecuritySchemeDescription();
File.WriteAllText(filePath, o.ToString(Newtonsoft.Json.Formatting.Indented));
}
/// <summary> /// <summary>
/// Download transifex transactions and put them in BTCPayServer\wwwroot\locales /// Download transifex transactions and put them in BTCPayServer\wwwroot\locales
/// </summary> /// </summary>

View File

@ -77,5 +77,10 @@ namespace BTCPayServer.Tests
} }
// depending on your use case, consider throwing an exception here // depending on your use case, consider throwing an exception here
} }
public static string GenerateEmail()
{
return Guid.NewGuid() + "@toto.com";
}
} }
} }

View File

@ -44,7 +44,14 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpPost("~/api/v1/api-keys")] [HttpPost("~/api/v1/api-keys")]
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CreateKey(CreateApiKeyRequest request) public Task<IActionResult> CreateAPIKey(CreateApiKeyRequest request)
{
return CreateUserAPIKey(_userManager.GetUserId(User), request);
}
[HttpPost("~/api/v1/users/{userId}/api-keys")]
[Authorize(Policy = Policies.CanManageUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CreateUserAPIKey(string userId, CreateApiKeyRequest request)
{ {
request ??= new CreateApiKeyRequest(); request ??= new CreateApiKeyRequest();
request.Permissions ??= System.Array.Empty<Permission>(); request.Permissions ??= System.Array.Empty<Permission>();
@ -52,7 +59,7 @@ namespace BTCPayServer.Controllers.Greenfield
{ {
Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)), Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)),
Type = APIKeyType.Permanent, Type = APIKeyType.Permanent,
UserId = _userManager.GetUserId(User), UserId = userId,
Label = request.Label Label = request.Label
}; };
key.SetBlob(new APIKeyBlob() key.SetBlob(new APIKeyBlob()
@ -72,19 +79,27 @@ namespace BTCPayServer.Controllers.Greenfield
// Should be impossible (we force apikey auth) // Should be impossible (we force apikey auth)
return Task.FromResult<IActionResult>(BadRequest()); return Task.FromResult<IActionResult>(BadRequest());
} }
return RevokeKey(apiKey); return RevokeAPIKey(apiKey);
} }
[HttpDelete("~/api/v1/api-keys/{apikey}", Order = 1)] [HttpDelete("~/api/v1/api-keys/{apikey}", Order = 1)]
[Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.Unrestricted, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> RevokeKey(string apikey) public Task<IActionResult> RevokeAPIKey(string apikey)
{
return RevokeAPIKey(_userManager.GetUserId(User), apikey);
}
[HttpDelete("~/api/v1/users/{userId}/api-keys/{apikey}", Order = 1)]
[Authorize(Policy = Policies.CanManageUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> RevokeAPIKey(string userId, string apikey)
{ {
if (!string.IsNullOrEmpty(apikey) && if (!string.IsNullOrEmpty(apikey) &&
await _apiKeyRepository.Remove(apikey, _userManager.GetUserId(User))) await _apiKeyRepository.Remove(apikey, userId))
return Ok(); return Ok();
else else
return this.CreateAPIError("apikey-not-found", "This apikey does not exists"); return this.CreateAPIError("apikey-not-found", "This apikey does not exists");
} }
private static ApiKeyData FromModel(APIKeyData data) private static ApiKeyData FromModel(APIKeyData data)
{ {
return new ApiKeyData() return new ApiKeyData()

View File

@ -670,7 +670,7 @@ namespace BTCPayServer.Controllers.Greenfield
public override async Task<ApiKeyData> CreateAPIKey(CreateApiKeyRequest request, public override async Task<ApiKeyData> CreateAPIKey(CreateApiKeyRequest request,
CancellationToken token = default) CancellationToken token = default)
{ {
return GetFromActionResult<ApiKeyData>(await GetController<GreenfieldApiKeysController>().CreateKey(request)); return GetFromActionResult<ApiKeyData>(await GetController<GreenfieldApiKeysController>().CreateAPIKey(request));
} }
public override async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default) public override async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
@ -680,7 +680,7 @@ namespace BTCPayServer.Controllers.Greenfield
public override async Task RevokeAPIKey(string apikey, CancellationToken token = default) public override async Task RevokeAPIKey(string apikey, CancellationToken token = default)
{ {
HandleActionResult(await GetController<GreenfieldApiKeysController>().RevokeKey(apikey)); HandleActionResult(await GetController<GreenfieldApiKeysController>().RevokeAPIKey(apikey));
} }
public override async Task<IEnumerable<NotificationData>> GetNotifications(bool? seen = null, public override async Task<IEnumerable<NotificationData>> GetNotifications(bool? seen = null,

View File

@ -509,6 +509,7 @@ namespace BTCPayServer.Controllers
{Policies.Unrestricted, ("Unrestricted access", "The app will have unrestricted access to your account.")}, {Policies.Unrestricted, ("Unrestricted access", "The app will have unrestricted access to your account.")},
{Policies.CanViewUsers, ("View users", "The app will be able to see all users on this server.")}, {Policies.CanViewUsers, ("View users", "The app will be able to see all users on this server.")},
{Policies.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")}, {Policies.CanCreateUser, ("Create new users", "The app will be able to create new users on this server.")},
{Policies.CanManageUsers, ("Manage users", "The app will be able to create/delete API keys for users.")},
{Policies.CanDeleteUser, ("Delete user", "The app will be able to delete the user to whom it is assigned. Admin users can delete any user without this permission.")}, {Policies.CanDeleteUser, ("Delete user", "The app will be able to delete the user to whom it is assigned. Admin users can delete any user without this permission.")},
{Policies.CanModifyStoreSettings, ("Modify your stores", "The app will be able to manage invoices on all your stores and modify their settings.")}, {Policies.CanModifyStoreSettings, ("Modify your stores", "The app will be able to manage invoices on all your stores and modify their settings.")},
{$"{Policies.CanModifyStoreSettings}:", ("Manage selected stores", "The app will be able to manage invoices on the selected stores and modify their settings.")}, {$"{Policies.CanModifyStoreSettings}:", ("Manage selected stores", "The app will be able to manage invoices on the selected stores and modify their settings.")},

View File

@ -47,7 +47,6 @@ namespace BTCPayServer.Security.Greenfield
{ {
throw new InvalidOperationException("cannot save a bitpay legacy api key with this repository"); throw new InvalidOperationException("cannot save a bitpay legacy api key with this repository");
} }
using var context = _applicationDbContextFactory.CreateContext(); using var context = _applicationDbContextFactory.CreateContext();
await context.ApiKeys.AddAsync(key); await context.ApiKeys.AddAsync(key);
await context.SaveChangesAsync(); await context.SaveChangesAsync();

View File

@ -27,7 +27,47 @@
}, },
"security": [ "security": [
{ {
"API_Key": ["unrestricted"], "API_Key": [ "unrestricted" ],
"Basic": []
}
]
}
},
"/api/v1/users/{userId}/api-keys/{apikey}": {
"delete": {
"operationId": "ApiKeys_DeleteUserApiKey",
"tags": [
"API Keys"
],
"summary": "Revoke an API Key of target user",
"description": "Revoke the API key of a target user so that it cannot be used anymore",
"parameters": [
{
"name": "userId",
"in": "path",
"required": true,
"description": "The target user",
"schema": { "type": "string" }
},
{
"name": "apikey",
"in": "path",
"required": true,
"description": "The API Key to revoke",
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "The key has been deleted"
},
"404": {
"description": "The key is not found for this user"
}
},
"security": [
{
"API_Key": [ "unrestricted" ],
"Basic": [] "Basic": []
} }
] ]
@ -65,7 +105,8 @@
}, },
"security": [ "security": [
{ {
"API_Key": [] "API_Key": [ "btcpay.server.canmanageusers" ],
"Basic": []
} }
] ]
}, },
@ -178,6 +219,89 @@
} }
] ]
} }
},
"/api/v1/users/{userId}/api-keys": {
"post": {
"operationId": "ApiKeys_CreateUserApiKey",
"tags": [
"API Keys"
],
"summary": "Create a new API Key for a user",
"description": "Create a new API Key for a user",
"parameters": [
{
"name": "userId",
"in": "path",
"required": true,
"description": "The target user",
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "Information about the new api key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiKeyData"
}
}
}
},
"401": {
"description": "Missing authorization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
}
},
"requestBody": {
"x-name": "request",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"label": {
"type": "string",
"description": "The label of the new API Key",
"nullable": true
},
"permissions": {
"type": "array",
"description": "The permissions granted to this API Key (See API Key Authentication)",
"nullable": true,
"items": {
"type": "string"
}
}
}
}
}
}
},
"security": [
{
"API_Key": [ "btcpay.server.canmanageusers" ],
"Basic": []
}
]
}
} }
}, },
"components": { "components": {
@ -198,11 +322,12 @@
}, },
"permissions": { "permissions": {
"type": "array", "type": "array",
"description": "The permissions associated to this API Key", "description": "The permissions associated to this API Key (can be scoped to a specific store)",
"nullable": false, "nullable": false,
"items": { "items": {
"type": "string" "type": "string"
} },
"example": [ "btcpay.server.canmanageusers", "btcpay.server.canmanageusers:2KxSpc9V5zDWfUbvgYiZuAfka4wUhGF96F75Ao8y4zHP" ]
} }
} }
} }

View File

@ -1,132 +1,140 @@
{ {
"openapi": "3.0.0", "openapi": "3.0.0",
"info": { "info": {
"title": "BTCPay Greenfield API", "title": "BTCPay Greenfield API",
"version": "v1", "version": "v1",
"description": "A full API to use your BTCPay Server", "description": "A full API to use your BTCPay Server",
"contact": { "contact": {
"name": "BTCPay Server", "name": "BTCPay Server",
"url": "https://btcpayserver.org" "url": "https://btcpayserver.org"
},
"license": {
"name": "MIT",
"url": "https://github.com/btcpayserver/btcpayserver/blob/master/LICENSE"
}
}, },
"servers": [ "license": {
{ "name": "MIT",
"url": "/", "url": "https://github.com/btcpayserver/btcpayserver/blob/master/LICENSE"
"description": "BTCPay Server Greenfield API" }
} },
], "servers": [
"components": { {
"schemas": { "url": "/",
"ValidationProblemDetails": { "description": "BTCPay Server Greenfield API"
"type": "array", }
"description": "An array of validation errors of the request", ],
"items": { "components": {
"type": "object", "schemas": {
"description": "A specific validation error on a json property", "ValidationProblemDetails": {
"properties": { "type": "array",
"path": { "description": "An array of validation errors of the request",
"type": "string", "items": {
"nullable": false, "type": "object",
"description": "The json path of the property which failed validation" "description": "A specific validation error on a json property",
}, "properties": {
"message": { "path": {
"type": "string", "type": "string",
"nullable": false, "nullable": false,
"description": "User friendly error message about the validation" "description": "The json path of the property which failed validation"
}
}
}
}, },
"ProblemDetails": { "message": {
"type": "object", "type": "string",
"description": "Description of an error happening during processing of the request", "nullable": false,
"properties": { "description": "User friendly error message about the validation"
"code": {
"type": "string",
"nullable": false,
"description": "An error code describing the error"
},
"message": {
"type": "string",
"nullable": false,
"description": "User friendly error message about the error"
}
}
},
"UnixTimestamp": {
"type": "number",
"format": "int32",
"example": 1592312018,
"description": "A unix timestamp in seconds"
},
"SpeedPolicy": {
"type": "string",
"description": "`\"HighSpeed\"`: 0 confirmations (1 confirmation if RBF enabled in transaction) \n`\"MediumSpeed\"`: 1 confirmation \n`\"LowMediumSpeed\"`: 2 confirmations \n`\"LowSpeed\"`: 6 confirmations\n",
"x-enumNames": [
"HighSpeed",
"MediumSpeed",
"LowSpeed",
"LowMediumSpeed"
],
"enum": [
"HighSpeed",
"MediumSpeed",
"LowSpeed",
"LowMediumSpeed"
]
},
"CheckoutType": {
"type": "string",
"description": "`\"V1\"`: The original checkout form \n`\"V2\"`: The new experimental checkout form",
"nullable": true,
"default": "V1",
"x-enumNames": [
"V1",
"V2"
],
"enum": [
"V1",
"V2"
]
},
"TimeSpan": {
"type": "number",
"format": "int32",
"example": 90
},
"TimeSpanSeconds": {
"allOf": [ { "$ref": "#/components/schemas/TimeSpan" } ],
"format": "seconds",
"description": "A span of times in seconds"
},
"TimeSpanMinutes": {
"allOf": [ { "$ref": "#/components/schemas/TimeSpan" } ],
"format": "minutes",
"description": "A span of times in minutes"
}
},
"securitySchemes": {
"API_Key": {
"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.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",
"in": "header"
},
"Basic": {
"type": "http",
"description": "BTCPay Server supports authenticating and authorizing users through the Basic HTTP authentication scheme. Send the user and password encoded in base64 with the format `Basic {base64(username:password)}`. Using this authentication method implicitly provides you with the `unrestricted` permission",
"scheme": "Basic"
} }
}
} }
},
"ProblemDetails": {
"type": "object",
"description": "Description of an error happening during processing of the request",
"properties": {
"code": {
"type": "string",
"nullable": false,
"description": "An error code describing the error"
},
"message": {
"type": "string",
"nullable": false,
"description": "User friendly error message about the error"
}
}
},
"UnixTimestamp": {
"type": "number",
"format": "int32",
"example": 1592312018,
"description": "A unix timestamp in seconds"
},
"SpeedPolicy": {
"type": "string",
"description": "`\"HighSpeed\"`: 0 confirmations (1 confirmation if RBF enabled in transaction) \n`\"MediumSpeed\"`: 1 confirmation \n`\"LowMediumSpeed\"`: 2 confirmations \n`\"LowSpeed\"`: 6 confirmations\n",
"x-enumNames": [
"HighSpeed",
"MediumSpeed",
"LowSpeed",
"LowMediumSpeed"
],
"enum": [
"HighSpeed",
"MediumSpeed",
"LowSpeed",
"LowMediumSpeed"
]
},
"CheckoutType": {
"type": "string",
"description": "`\"V1\"`: The original checkout form \n`\"V2\"`: The new experimental checkout form",
"nullable": true,
"default": "V1",
"x-enumNames": [
"V1",
"V2"
],
"enum": [
"V1",
"V2"
]
},
"TimeSpan": {
"type": "number",
"format": "int32",
"example": 90
},
"TimeSpanSeconds": {
"allOf": [
{
"$ref": "#/components/schemas/TimeSpan"
}
],
"format": "seconds",
"description": "A span of times in seconds"
},
"TimeSpanMinutes": {
"allOf": [
{
"$ref": "#/components/schemas/TimeSpan"
}
],
"format": "minutes",
"description": "A span of times in minutes"
}
}, },
"security": [ "securitySchemes": {
{ "API_Key": {
"API_Key": [], "type": "apiKey",
"Basic": [] "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.canmanageusers`: Manage 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",
] "in": "header"
} },
"Basic": {
"type": "http",
"description": "BTCPay Server supports authenticating and authorizing users through the Basic HTTP authentication scheme. Send the user and password encoded in base64 with the format `Basic {base64(username:password)}`. Using this authentication method implicitly provides you with the `unrestricted` permission",
"scheme": "Basic"
}
}
},
"security": [
{
"API_Key": [],
"Basic": []
}
]
}