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 System.CommandLine;
|
||||||
using Electricity.Api;
|
using Electricity.Api;
|
||||||
|
using Electricity.Api.Options;
|
||||||
using Electricity.Api.Services;
|
using Electricity.Api.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
@@ -67,6 +68,11 @@ internal class Program
|
|||||||
config.JsonSerializerOptions.WriteIndented = true;
|
config.JsonSerializerOptions.WriteIndented = true;
|
||||||
});
|
});
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(sp => new AllowedAccessOptions(
|
||||||
|
sp.GetRequiredService<IConfiguration>()
|
||||||
|
));
|
||||||
|
|
||||||
builder.Services.AddScoped<ElectricityService>();
|
builder.Services.AddScoped<ElectricityService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
@@ -79,6 +85,9 @@ internal class Program
|
|||||||
app.UseCors("default");
|
app.UseCors("default");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseMiddleware<AllowedAccessMiddleware>(
|
||||||
|
app.Services.GetRequiredService<AllowedAccessOptions>()
|
||||||
|
);
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"AllowedAccess": {
|
||||||
|
"HttpIpAddressHeaderName": "Host",
|
||||||
|
"HttpIpAddressHeaderValues": ["localhost:7290"]
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Trace",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
|
"AllowedAccess": {
|
||||||
|
"HttpIpAddressHeaderName": "X-Real-IP",
|
||||||
|
"HttpIpAddressHeaderValues": ["213.233.220.64", "86.83.136.215"]
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
Reference in New Issue
Block a user