From 300425ea0373164f21591c98c319d9ee8f1b6aa7 Mon Sep 17 00:00:00 2001 From: arumie Date: Sat, 2 Mar 2024 18:37:32 +0100 Subject: [PATCH] Added Authentication --- .github/workflows/google-cloudrun-docker.yml | 1 + .../Authorization/ApiKeyMiddleware.cs | 42 +++++++++++++++++++ .../Authorization/ApiKeyValidation.cs | 15 +++++++ AiTestimonials/Authorization/AuthConstants.cs | 7 ++++ .../Authorization/IApiKeyValidation.cs | 6 +++ AiTestimonials/Program.cs | 38 ++++++++++++++++- AiTestimonials/appsettings.Development.json | 3 +- 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 AiTestimonials/Authorization/ApiKeyMiddleware.cs create mode 100644 AiTestimonials/Authorization/ApiKeyValidation.cs create mode 100644 AiTestimonials/Authorization/AuthConstants.cs create mode 100644 AiTestimonials/Authorization/IApiKeyValidation.cs diff --git a/.github/workflows/google-cloudrun-docker.yml b/.github/workflows/google-cloudrun-docker.yml index 5fdfd6a..e6f66c5 100644 --- a/.github/workflows/google-cloudrun-docker.yml +++ b/.github/workflows/google-cloudrun-docker.yml @@ -111,6 +111,7 @@ jobs: image: ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{env.GAR_REPO}}/${{ env.SERVICE }}:${{ github.sha }} secrets: | AiTestimonials__PostgresConnectionString=vercel-postgres-secret:latest + API_KEY=dzachdev-api-key-secret:latest # If required, use the Cloud Run url output in later steps - name: Show Output diff --git a/AiTestimonials/Authorization/ApiKeyMiddleware.cs b/AiTestimonials/Authorization/ApiKeyMiddleware.cs new file mode 100644 index 0000000..1cd4a2a --- /dev/null +++ b/AiTestimonials/Authorization/ApiKeyMiddleware.cs @@ -0,0 +1,42 @@ +using System.Net; + +namespace AiTestimonials.Authorization; + +public class ApiKeyMiddleware +{ + private readonly RequestDelegate _next; + private readonly IApiKeyValidation _apiKeyValidation; + + private readonly List _utilityEndpoints = + [ + "/health/ready", + "/health/live" + ]; + + public ApiKeyMiddleware(RequestDelegate next, IApiKeyValidation apiKeyValidation) + { + _next = next; + _apiKeyValidation = apiKeyValidation; + } + public async Task InvokeAsync(HttpContext context) + { + if (_utilityEndpoints.Contains(context.Request.Path)) + { + await _next(context); + return; + } + + if (string.IsNullOrWhiteSpace(context.Request.Headers[AuthConstants.ApiKeyHeaderName])) + { + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + string? userApiKey = context.Request.Headers[AuthConstants.ApiKeyHeaderName]; + if (!_apiKeyValidation.IsValidApiKey(userApiKey!)) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + await _next(context); + } +} diff --git a/AiTestimonials/Authorization/ApiKeyValidation.cs b/AiTestimonials/Authorization/ApiKeyValidation.cs new file mode 100644 index 0000000..a22f7ee --- /dev/null +++ b/AiTestimonials/Authorization/ApiKeyValidation.cs @@ -0,0 +1,15 @@ +namespace AiTestimonials.Authorization; + +internal sealed class ApiKeyValidation(IConfiguration configuration) : IApiKeyValidation +{ + public bool IsValidApiKey(string userApiKey) + { + if (string.IsNullOrWhiteSpace(userApiKey)) + { + return false; + } + + var apiKey = configuration.GetValue(AuthConstants.ApiKeyName); + return apiKey != null && apiKey == userApiKey; + } +} diff --git a/AiTestimonials/Authorization/AuthConstants.cs b/AiTestimonials/Authorization/AuthConstants.cs new file mode 100644 index 0000000..0c88d1a --- /dev/null +++ b/AiTestimonials/Authorization/AuthConstants.cs @@ -0,0 +1,7 @@ +namespace AiTestimonials.Authorization; + +public static class AuthConstants +{ + public const string ApiKeyHeaderName = "x-api-key"; + public const string ApiKeyName = "API_KEY"; +} diff --git a/AiTestimonials/Authorization/IApiKeyValidation.cs b/AiTestimonials/Authorization/IApiKeyValidation.cs new file mode 100644 index 0000000..4acf863 --- /dev/null +++ b/AiTestimonials/Authorization/IApiKeyValidation.cs @@ -0,0 +1,6 @@ +namespace AiTestimonials.Authorization; + +public interface IApiKeyValidation +{ + bool IsValidApiKey(string userApiKey); +} diff --git a/AiTestimonials/Program.cs b/AiTestimonials/Program.cs index 15c6949..b06d287 100644 --- a/AiTestimonials/Program.cs +++ b/AiTestimonials/Program.cs @@ -2,6 +2,9 @@ using AiTestimonials.Options; using AiTestimonials.Repository; using AiTestimonials.Services; +using AiTestimonials.Authorization; +using Swashbuckle.AspNetCore.Swagger; +using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -13,9 +16,39 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddAuthorization(); +builder.Services.AddAuthentication(); +builder.Services.AddSwaggerGen(c => +{ + c.AddSecurityDefinition(AuthConstants.ApiKeyName, new OpenApiSecurityScheme + { + Description = "ApiKey must appear in header", + Type = SecuritySchemeType.ApiKey, + Name = AuthConstants.ApiKeyHeaderName, + In = ParameterLocation.Header, + Scheme = "ApiKeyScheme" + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme() + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = AuthConstants.ApiKeyName + }, + In = ParameterLocation.Header + }, + new List() + } + }); +}); + var app = builder.Build(); app.RegisterAiTestimonialsEndpoints(); @@ -23,10 +56,13 @@ // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); + app.UseSwagger(new SwaggerOptions() { SerializeAsV2 = true, RouteTemplate = "swagger/{documentName}/swaggerV2.{json|yaml}" }); + app.UseSwagger(new SwaggerOptions() ); app.UseSwaggerUI(); } app.UseHttpsRedirection(); +app.UseMiddleware(); +app.UseAuthorization(); app.Run(); diff --git a/AiTestimonials/appsettings.Development.json b/AiTestimonials/appsettings.Development.json index 2b9a90e..ec4cde5 100644 --- a/AiTestimonials/appsettings.Development.json +++ b/AiTestimonials/appsettings.Development.json @@ -11,5 +11,6 @@ "PostgresDatabase": "", "PostgresUser": "", "PostgresHost": "" - } + }, + "API_KEY": "developer" }