mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-01 20:34:38 +01:00
Merge pull request #1341 from btcpayserver/swagger
Add Swagger and Redoc
This commit is contained in:
@@ -26,6 +26,7 @@ namespace BTCPayServer.Data
|
||||
|
||||
public StoreData StoreData { get; set; }
|
||||
public ApplicationUser User { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string[] GetPermissions() { return Permissions?.Split(';') ?? new string[0]; }
|
||||
|
||||
public void SetPermissions(IEnumerable<string> permissions)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20200224134444_Remove_OpenIddict")]
|
||||
public partial class Remove_OpenIddict : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20200225133433_AddApiKeyLabel")]
|
||||
public partial class AddApiKeyLabel : Migration
|
||||
{
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Label",
|
||||
table: "ApiKeys",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Label",
|
||||
table: "ApiKeys");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ namespace BTCPayServer.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("Label")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Permissions")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="13.2.2" />
|
||||
<PackageReference Include="Serilog" Version="2.9.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
|
||||
@@ -4,13 +4,13 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hosting.OpenApi;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.APIKeys;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using NSwag.Annotations;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@@ -28,9 +28,33 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
||||
|
||||
[HttpGet("api-keys/{id}/delete")]
|
||||
public async Task<IActionResult> RemoveAPIKey(string id)
|
||||
{
|
||||
var key = await _apiKeyRepository.GetKey(id);
|
||||
if (key == null || key.UserId != _userManager.GetUserId(User))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete API Key "+ ( string.IsNullOrEmpty(key.Label)? string.Empty: key.Label) + "("+key.Id+")",
|
||||
Description = "Any application using this api key will immediately lose access",
|
||||
Action = "Delete",
|
||||
ActionUrl = Request.GetCurrentUrl().Replace("RemoveAPIKey", "RemoveAPIKeyPost")
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("api-keys/{id}/delete")]
|
||||
public async Task<IActionResult> RemoveAPIKeyPost(string id)
|
||||
{
|
||||
var key = await _apiKeyRepository.GetKey(id);
|
||||
if (key == null || key.UserId != _userManager.GetUserId(User))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
await _apiKeyRepository.Remove(id, _userManager.GetUserId(User));
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@@ -56,8 +80,16 @@ namespace BTCPayServer.Controllers
|
||||
return View("AddApiKey", await SetViewModelValues(new AddApiKeyViewModel()));
|
||||
}
|
||||
|
||||
/// <param name="permissions">The permissions to request. Current permissions available: ServerManagement, StoreManagement</param>
|
||||
/// <param name="applicationName">The name of your application</param>
|
||||
/// <param name="strict">If permissions are specified, and strict is set to false, it will allow the user to reject some of permissions the application is requesting.</param>
|
||||
/// <param name="selectiveStores">If the application is requesting the CanModifyStoreSettings permission and selectiveStores is set to true, this allows the user to only grant permissions to selected stores under the user's control.</param>
|
||||
[HttpGet("~/api-keys/authorize")]
|
||||
public async Task<IActionResult> AuthorizeAPIKey( string[] permissions, string applicationName = null,
|
||||
[OpenApiTags("Authorization")]
|
||||
[OpenApiOperation("Authorize User",
|
||||
"Redirect the browser to this endpoint to request the user to generate an api-key with specific permissions")]
|
||||
[IncludeInOpenApiDocs]
|
||||
public async Task<IActionResult> AuthorizeAPIKey(string[] permissions, string applicationName = null,
|
||||
bool strict = true, bool selectiveStores = false)
|
||||
{
|
||||
if (!_btcPayServerEnvironment.IsSecure)
|
||||
@@ -74,6 +106,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var vm = await SetViewModelValues(new AuthorizeApiKeysViewModel()
|
||||
{
|
||||
Label = applicationName,
|
||||
ServerManagementPermission = permissions.Contains(APIKeyConstants.Permissions.ServerManagement),
|
||||
StoreManagementPermission = permissions.Contains(APIKeyConstants.Permissions.StoreManagement),
|
||||
PermissionsFormatted = permissions,
|
||||
@@ -214,7 +247,10 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var key = new APIKeyData()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(), Type = APIKeyType.Permanent, UserId = _userManager.GetUserId(User)
|
||||
Id = Guid.NewGuid().ToString().Replace("-", string.Empty),
|
||||
Type = APIKeyType.Permanent,
|
||||
UserId = _userManager.GetUserId(User),
|
||||
Label = viewModel.Label
|
||||
};
|
||||
key.SetPermissions(GetPermissionsFromViewModel(viewModel));
|
||||
await _apiKeyRepository.CreateKey(key);
|
||||
@@ -251,6 +287,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
public class AddApiKeyViewModel
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public StoreData[] Stores { get; set; }
|
||||
public ApiKeyStoreMode StoreMode { get; set; }
|
||||
public List<string> SpecificStores { get; set; } = new List<string>();
|
||||
@@ -277,7 +314,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
get
|
||||
{
|
||||
return Permissions?.Split(";", StringSplitOptions.RemoveEmptyEntries);
|
||||
return Permissions?.Split(";", StringSplitOptions.RemoveEmptyEntries)?? Array.Empty<string>();
|
||||
}
|
||||
set
|
||||
{
|
||||
|
||||
@@ -141,8 +141,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, DerivationSchemeViewModel vm,
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, [FromForm] DerivationSchemeViewModel vm,
|
||||
string cryptoCode)
|
||||
{
|
||||
vm.CryptoCode = cryptoCode;
|
||||
|
||||
@@ -26,6 +26,7 @@ using System.Threading;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting.OpenApi;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
@@ -262,7 +263,7 @@ namespace BTCPayServer.Hosting
|
||||
}
|
||||
return rateLimits;
|
||||
});
|
||||
|
||||
services.AddBTCPayOpenApi();
|
||||
|
||||
services.AddLogging(logBuilder =>
|
||||
{
|
||||
@@ -291,6 +292,7 @@ namespace BTCPayServer.Hosting
|
||||
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseMiddleware<BTCPayMiddleware>();
|
||||
app.UseBTCPayOpenApi();
|
||||
return app;
|
||||
}
|
||||
public static IApplicationBuilder UseHeadersOverride(this IApplicationBuilder app)
|
||||
|
||||
9
BTCPayServer/Hosting/OpenApi/IncludeInOpenApiDocs.cs
Normal file
9
BTCPayServer/Hosting/OpenApi/IncludeInOpenApiDocs.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
|
||||
namespace BTCPayServer.Hosting.OpenApi
|
||||
{
|
||||
public class IncludeInOpenApiDocs : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
96
BTCPayServer/Hosting/OpenApi/OpenApiExtensions.cs
Normal file
96
BTCPayServer/Hosting/OpenApi/OpenApiExtensions.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Generation.TypeMappers;
|
||||
using NSwag;
|
||||
using NSwag.Generation.Processors.Security;
|
||||
|
||||
namespace BTCPayServer.Hosting.OpenApi
|
||||
{
|
||||
public static class OpenApiExtensions
|
||||
{
|
||||
public static IServiceCollection AddBTCPayOpenApi(this IServiceCollection serviceCollection)
|
||||
{
|
||||
|
||||
return serviceCollection.AddOpenApiDocument(config =>
|
||||
{
|
||||
config.PostProcess = document =>
|
||||
{
|
||||
document.Info.Version = "v1";
|
||||
document.Info.Title = "BTCPay Greenfield API";
|
||||
document.Info.Description = "A full API to use your BTCPay Server";
|
||||
document.Info.TermsOfService = null;
|
||||
document.Info.Contact = new NSwag.OpenApiContact
|
||||
{
|
||||
Name = "BTCPay Server", Email = string.Empty, Url = "https://btcpayserver.org"
|
||||
};
|
||||
};
|
||||
config.AddOperationFilter(context =>
|
||||
{
|
||||
var methodInfo = context.MethodInfo;
|
||||
if (methodInfo != null)
|
||||
{
|
||||
return methodInfo.CustomAttributes.Any(data =>
|
||||
data.AttributeType == typeof(IncludeInOpenApiDocs)) ||
|
||||
methodInfo.DeclaringType.CustomAttributes.Any(data =>
|
||||
data.AttributeType == typeof(IncludeInOpenApiDocs));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
config.AddSecurity("APIKey", Enumerable.Empty<string>(),
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Type = OpenApiSecuritySchemeType.ApiKey,
|
||||
Name = "Authorization",
|
||||
In = OpenApiSecurityApiKeyLocation.Header,
|
||||
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."
|
||||
});
|
||||
|
||||
config.OperationProcessors.Add(
|
||||
new BTCPayPolicyOperationProcessor("APIKey", AuthenticationSchemes.ApiKey));
|
||||
|
||||
config.TypeMappers.Add(
|
||||
new PrimitiveTypeMapper(typeof(PaymentType), s => s.Type = JsonObjectType.String));
|
||||
config.TypeMappers.Add(new PrimitiveTypeMapper(typeof(PaymentMethodId),
|
||||
s => s.Type = JsonObjectType.String));
|
||||
});
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseBTCPayOpenApi(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseOpenApi()
|
||||
.UseReDoc(settings => settings.Path = "/docs");
|
||||
}
|
||||
|
||||
|
||||
class BTCPayPolicyOperationProcessor : AspNetCoreOperationSecurityScopeProcessor
|
||||
{
|
||||
private readonly string _authScheme;
|
||||
|
||||
public BTCPayPolicyOperationProcessor(string x, string authScheme) : base(x)
|
||||
{
|
||||
_authScheme = authScheme;
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetScopes(IEnumerable<AuthorizeAttribute> authorizeAttributes)
|
||||
{
|
||||
var result = authorizeAttributes
|
||||
.Where(attribute => attribute?.AuthenticationSchemes != null && attribute.Policy != null &&
|
||||
attribute.AuthenticationSchemes.Equals(_authScheme,
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(attribute => attribute.Policy);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,5 +29,6 @@ namespace BTCPayServer.Models
|
||||
get; set;
|
||||
}
|
||||
public string ButtonClass { get; set; } = "btn-danger";
|
||||
public string ActionUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
<table class="table table-lg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th >Key</th>
|
||||
<th >Permissions</th>
|
||||
<th>Label</th>
|
||||
<th>Key</th>
|
||||
<th>Permissions</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -17,6 +18,7 @@
|
||||
@foreach (var keyData in Model.ApiKeyDatas)
|
||||
{
|
||||
<tr>
|
||||
<td>@keyData.Label</td>
|
||||
<td>@keyData.Id</td>
|
||||
<td>
|
||||
@if (string.IsNullOrEmpty(keyData.Permissions))
|
||||
@@ -29,20 +31,20 @@
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a asp-action="RemoveAPIKey" asp-route-id="@keyData.Id">Remove</a>
|
||||
<a asp-action="RemoveAPIKey" asp-route-id="@keyData.Id" asp-controller="Manage">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (!Model.ApiKeyDatas.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="2" class="text-center h5 py-2">
|
||||
<td colspan="4" class="text-center h5 py-2">
|
||||
No API keys
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="bg-gray">
|
||||
<td colspan="3">
|
||||
<td colspan="4">
|
||||
<a class="btn btn-primary" asp-action="AddApiKey" id="AddApiKey">Generate new key</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
|
||||
<input type="hidden" asp-for="StoreMode" value="@Model.StoreMode"/>
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
|
||||
<div class="list-group-item ">
|
||||
<div class="form-group">
|
||||
<label asp-for="Label"></label>
|
||||
<input asp-for="Label" class="form-control"/>
|
||||
<span asp-validation-for="Label" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.IsServerAdmin)
|
||||
{
|
||||
<div class="list-group-item form-group">
|
||||
|
||||
@@ -35,6 +35,19 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 list-group px-2">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="list-group-item ">
|
||||
<div class="form-group">
|
||||
<label asp-for="Label"></label>
|
||||
<input asp-for="Label" class="form-control"/>
|
||||
<span asp-validation-for="Label" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
@if (!Model.PermissionsFormatted.Any())
|
||||
{
|
||||
<div class="list-group-item form-group">
|
||||
<p >There are no associated permissions to the API key being requested here. The application cannot do anything with your BTCPay account other than validating your account exists.</p>
|
||||
</div>
|
||||
}
|
||||
@if (Model.PermissionsFormatted.Contains(APIKeyConstants.Permissions.ServerManagement) && (Model.IsServerAdmin || Model.Strict))
|
||||
{
|
||||
<div class="list-group-item form-group">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<form method="post">
|
||||
<form method="post" action="@Model.ActionUrl">
|
||||
<button id="continue" type="submit" class="btn @Model.ButtonClass w-25">@Model.Action</button>
|
||||
<button type="submit" class="btn btn-secondary w-25" onclick="history.back(); return false;">Go back</button>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user