Add HTTP header authorization middleware
This commit is contained in:
72
src/Electricity.Api/AllowedAccessMiddleware.cs
Normal file
72
src/Electricity.Api/AllowedAccessMiddleware.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.Net;
|
||||
using Electricity.Api.Options;
|
||||
|
||||
namespace Electricity.Api;
|
||||
|
||||
internal class AllowedAccessMiddleware(
|
||||
RequestDelegate next,
|
||||
ILogger<AllowedAccessMiddleware> logger,
|
||||
AllowedAccessOptions options
|
||||
)
|
||||
{
|
||||
private readonly RequestDelegate next = next;
|
||||
private readonly ILogger<AllowedAccessMiddleware> logger = logger;
|
||||
private readonly AllowedAccessOptions options = options;
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
if (
|
||||
!context.Request.Headers.TryGetValue(
|
||||
options.HttpIpAddressHeaderName,
|
||||
out var headerValue
|
||||
)
|
||||
)
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Rejecting request missing the expected HTTP header {HeaderName}",
|
||||
options.HttpIpAddressHeaderName
|
||||
);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
if (headerValue.Count != 1)
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Rejecting request with malformed HTTP header {HeaderName}",
|
||||
options.HttpIpAddressHeaderName
|
||||
);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(headerValue[0]))
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Rejecting request with malformed value {HeaderValue} in HTTP header {HeaderName}",
|
||||
headerValue[0],
|
||||
options.HttpIpAddressHeaderName
|
||||
);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.HttpIpAddressHeaderValues.Contains(headerValue[0]))
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Rejecting request with disallowed header value {HeaderValue} in HTTP header {HeaderName}",
|
||||
headerValue[0],
|
||||
options.HttpIpAddressHeaderName
|
||||
);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogDebug(
|
||||
"Accepting request with allowed value {HeaderValue} in HTTP header {HeaderName}",
|
||||
headerValue[0],
|
||||
options.HttpIpAddressHeaderName
|
||||
);
|
||||
await next.Invoke(context);
|
||||
}
|
||||
}
|
||||
47
src/Electricity.Api/Options/AllowedAccessOptions.cs
Normal file
47
src/Electricity.Api/Options/AllowedAccessOptions.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace Electricity.Api.Options;
|
||||
|
||||
internal class AllowedAccessOptions(IConfiguration configuration)
|
||||
{
|
||||
public string HttpIpAddressHeaderName { get; } = ResolveHttpIpAddressHeaderName(configuration);
|
||||
public string[] HttpIpAddressHeaderValues { get; } =
|
||||
ResolveHttpIpAddressHeaderValues(configuration);
|
||||
|
||||
private static string ResolveHttpIpAddressHeaderName(IConfiguration configuration)
|
||||
{
|
||||
const string configurationKey = "AllowedAccess:HttpIpAddressHeaderName";
|
||||
var value = configuration["AllowedAccess:HttpIpAddressHeaderName"];
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new NotSupportedException($"The app setting {configurationKey} cannot be empty");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string[] ResolveHttpIpAddressHeaderValues(IConfiguration configuration)
|
||||
{
|
||||
const string configurationKey = "AllowedAccess:HttpIpAddressHeaderValues";
|
||||
var values = configuration.GetSection(configurationKey).Get<List<string>>();
|
||||
if (values == null || values.Count == 0)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"The app setting {configurationKey} must be an string array with at least 1 value"
|
||||
);
|
||||
}
|
||||
|
||||
List<string> validatedValues = [];
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"The value \"{value}\" from app setting {configurationKey} cannot be empty"
|
||||
);
|
||||
}
|
||||
|
||||
validatedValues.Add(value);
|
||||
}
|
||||
|
||||
return validatedValues.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.CommandLine;
|
||||
using Electricity.Api;
|
||||
using Electricity.Api.Options;
|
||||
using Electricity.Api.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -67,6 +68,11 @@ internal class Program
|
||||
config.JsonSerializerOptions.WriteIndented = true;
|
||||
});
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddSingleton(sp => new AllowedAccessOptions(
|
||||
sp.GetRequiredService<IConfiguration>()
|
||||
));
|
||||
|
||||
builder.Services.AddScoped<ElectricityService>();
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -79,6 +85,9 @@ internal class Program
|
||||
app.UseCors("default");
|
||||
}
|
||||
|
||||
app.UseMiddleware<AllowedAccessMiddleware>(
|
||||
app.Services.GetRequiredService<AllowedAccessOptions>()
|
||||
);
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"AllowedAccess": {
|
||||
"HttpIpAddressHeaderName": "Host",
|
||||
"HttpIpAddressHeaderValues": ["localhost:7290"]
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Default": "Trace",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"AllowedHosts": "*",
|
||||
"AllowedAccess": {
|
||||
"HttpIpAddressHeaderName": "X-Real-IP",
|
||||
"HttpIpAddressHeaderValues": ["213.233.220.64", "86.83.136.215"]
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
Reference in New Issue
Block a user