diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index f7fd971..d155ce3 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -10,13 +10,12 @@ concurrency: cancel-in-progress: true jobs: - build: - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -28,6 +27,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha + - name: Login to Docker Hub uses: docker/login-action@v3 with: @@ -36,6 +36,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -44,3 +45,7 @@ jobs: with: push: true tags: ${{ steps.meta.outputs.tags }} + build-args: | + GIT_VERSION_TAG=${{ env.RELEASE_VERSION }} + GIT_COMMIT_MESSAGE=${{ github.event.head_commit.message }} + GIT_VERSION_HASH=${{ github.sha }} diff --git a/Dockerfile b/Dockerfile index 0c953db..812e8de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,7 @@ +ARG GIT_VERSION_TAG +ARG GIT_COMMIT_MESSAGE=unspecified +ARG GIT_VERSION_HASH=unspecified + FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base USER $APP_UID WORKDIR /app @@ -25,7 +29,7 @@ RUN dotnet build "./Proxarr.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build # This stage is used to publish the service project to be copied to the final stage FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./Proxarr.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish "./Proxarr.Api.csproj" -c $BUILD_CONFIGURATION -p:Version=${GIT_VERSION_TAG:-1.0.0} -o /app/publish /p:UseAppHost=false # This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) FROM base AS final diff --git a/src/Proxarr.Api/Core/BasicAuthenticationHandler.cs b/src/Proxarr.Api/Core/Http/BasicAuthenticationHandler.cs similarity index 97% rename from src/Proxarr.Api/Core/BasicAuthenticationHandler.cs rename to src/Proxarr.Api/Core/Http/BasicAuthenticationHandler.cs index 29494c9..e621f63 100644 --- a/src/Proxarr.Api/Core/BasicAuthenticationHandler.cs +++ b/src/Proxarr.Api/Core/Http/BasicAuthenticationHandler.cs @@ -8,7 +8,7 @@ using Proxarr.Api.Configuration; using System.Diagnostics.CodeAnalysis; -namespace Proxarr.Api.Core +namespace Proxarr.Api.Core.Http { [ExcludeFromCodeCoverage] public class BasicAuthenticationDefaults @@ -40,7 +40,7 @@ public class BasicAuthenticationHandler : AuthenticationHandler appConfig, IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + public BasicAuthenticationHandler(IOptions appConfig, IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) { _appConfig = appConfig?.Value; diff --git a/src/Proxarr.Api/Core/Http/BasicSecuritySchemeTransformer.cs b/src/Proxarr.Api/Core/Http/BasicSecuritySchemeTransformer.cs new file mode 100644 index 0000000..fc23281 --- /dev/null +++ b/src/Proxarr.Api/Core/Http/BasicSecuritySchemeTransformer.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; +using System.Diagnostics.CodeAnalysis; + +namespace Proxarr.Api.Core.Http +{ + [ExcludeFromCodeCoverage] + internal sealed class BasicSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer + { + public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) + { + var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync(); + if (authenticationSchemes.Any(authScheme => authScheme.Name == BasicAuthenticationDefaults.AuthenticationScheme)) + { + var requirements = new Dictionary + { + ["Basic"] = new OpenApiSecurityScheme + { + Type = SecuritySchemeType.Http, + Scheme = BasicAuthenticationDefaults.AuthenticationScheme, + In = ParameterLocation.Header, + } + }; + document.Components ??= new OpenApiComponents(); + document.Components.SecuritySchemes = requirements; + + // Apply it as a requirement for all operations + foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations)) + { + operation.Value.Security.Add(new OpenApiSecurityRequirement + { + [new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = BasicAuthenticationDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }] = Array.Empty() + }); + } + } + } + } +} diff --git a/src/Proxarr.Api/Core/ExceptionToProblemDetailsHandler.cs b/src/Proxarr.Api/Core/Http/ExceptionToProblemDetailsHandler.cs similarity index 97% rename from src/Proxarr.Api/Core/ExceptionToProblemDetailsHandler.cs rename to src/Proxarr.Api/Core/Http/ExceptionToProblemDetailsHandler.cs index 806cb8c..3db64be 100644 --- a/src/Proxarr.Api/Core/ExceptionToProblemDetailsHandler.cs +++ b/src/Proxarr.Api/Core/Http/ExceptionToProblemDetailsHandler.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Proxarr.Api.Core +namespace Proxarr.Api.Core.Http { [ExcludeFromCodeCoverage] public class ExceptionToProblemDetailsHandler : Microsoft.AspNetCore.Diagnostics.IExceptionHandler diff --git a/src/Proxarr.Api/Program.cs b/src/Proxarr.Api/Program.cs index f8a8d72..3c06693 100644 --- a/src/Proxarr.Api/Program.cs +++ b/src/Proxarr.Api/Program.cs @@ -4,6 +4,7 @@ using Polly; using Proxarr.Api.Configuration; using Proxarr.Api.Core; +using Proxarr.Api.Core.Http; using Proxarr.Api.HostedServices; using Proxarr.Api.Services; using Radarr.Http.Client; @@ -13,10 +14,11 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net; +using System.Reflection; using TMDbLib.Client; using TMDbLib.Objects.Exceptions; - +var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "v1.0.0"; var builder = WebApplication.CreateBuilder(args); var configFileName = "config.yml"; @@ -48,15 +50,14 @@ builder.Services.AddControllers().AddJsonOptions(options => { - options.JsonSerializerOptions.Converters.Add( - new MediaAddedJsonConverter()); + options.JsonSerializerOptions.Converters.Add(new MediaAddedJsonConverter()); }); // Add basic authentication var appConfig = new AppConfiguration(); builder.Configuration.GetRequiredSection(AppConfiguration.SECTION_NAME).Bind(appConfig); -if (!string.IsNullOrEmpty(appConfig?.Authentication?.Password) || !string.IsNullOrEmpty(appConfig?.Authentication?.Username)) +if (!string.IsNullOrEmpty(appConfig?.Authentication?.Password) && !string.IsNullOrEmpty(appConfig?.Authentication?.Username)) { builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, options => @@ -84,7 +85,21 @@ }).AddExceptionHandler(); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); +builder.Services.AddOpenApi(options => +{ + options + .AddDocumentTransformer() + .AddDocumentTransformer((document, context, cancellationToken) => + { + document.Info = new() + { + Title = "Proxarr Api", + Version = "v1", + Description = $"Proxarr Api {assemblyVersion}" + }; + return Task.CompletedTask; + }); +}); builder.Services.AddSingleton(x => new TMDbClient(appConfig?.TmdbApiKey)); builder.Services.AddTransient(); @@ -131,8 +146,20 @@ app.MapOpenApi(); app.MapScalarApiReference(opt => { - opt.Title = "Proxarr API"; - opt.Theme = ScalarTheme.Mars; + opt + .WithTitle("Proxarr Api") + .WithTheme(ScalarTheme.Mars); + + if (!string.IsNullOrEmpty(appConfig?.Authentication?.Password) && !string.IsNullOrEmpty(appConfig?.Authentication?.Username)) + { + opt + .WithPreferredScheme(BasicAuthenticationDefaults.AuthenticationScheme) + .WithHttpBasicAuthentication(basic => + { + basic.Username = appConfig.Authentication.Username; + basic.Password = appConfig.Authentication.Password; + }); + } }); } @@ -152,7 +179,6 @@ Radarr.Http.Client.ApiException e when (e.StatusCode == StatusCodes.Status404Not Sonarr.Http.Client.ApiException e when (e.StatusCode == StatusCodes.Status404NotFound) => StatusCodes.Status404NotFound, _ => StatusCodes.Status500InternalServerError }, - }); await app.RunAsync(); diff --git a/src/Proxarr.sln b/src/Proxarr.sln index b087f36..97cd303 100644 --- a/src/Proxarr.sln +++ b/src/Proxarr.sln @@ -16,7 +16,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.gitignore = ..\.gitignore ..\CONTRIBUTING.md = ..\CONTRIBUTING.md ..\docker-compose.yml = ..\docker-compose.yml - ..\.github\workflows\docker-image.yml = ..\.github\workflows\docker-image.yml ..\Dockerfile = ..\Dockerfile ..\README.md = ..\README.md EndProjectSection