Files
sequence/services/csharp/lib/SequenceAuthMiddleware.cs
Vitalii Litvinchuk 23958e8e2c first commit
2026-06-13 23:23:50 +03:00

110 lines
3.6 KiB
C#

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace SequenceAuth.Lib;
public class SequenceAuthMiddleware(RequestDelegate next, IOptions<SequenceAuthOptions> options)
{
private readonly SequenceAuthOptions _options = options?.Value ?? throw new InvalidOperationException("SequenceAuthOptions must be registered in DI using AddSequenceAuth.");
public async Task InvokeAsync(HttpContext context, SequenceManager sequenceManager)
{
await (context.Request.Method switch
{
"OPTIONS" => next(context),
_ => ProcessPath(context, sequenceManager)
});
}
private async Task ProcessPath(HttpContext context, SequenceManager sequenceManager)
{
var path = context.Request.Path.Value?.ToLowerInvariant() ?? "";
var isIgnored = _options.IgnoredPaths.FirstOrDefault(x => path.StartsWith(x));
await (isIgnored switch
{
null => CheckHeader(context, sequenceManager),
_ => next(context)
});
}
private async Task CheckHeader(HttpContext context, SequenceManager sequenceManager)
{
var headerCount = context.Request.Headers[_options.AuthHeaderName].Count;
await (headerCount switch
{
0 => HandleMissingHeader(context),
> 0 => ProcessWithHeader(context, sequenceManager, context.Request.Headers[_options.AuthHeaderName].ToString()!),
_ => HandleMissingHeader(context)
});
}
private Task HandleMissingHeader(HttpContext context)
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
private async Task ProcessWithHeader(HttpContext context, SequenceManager sequenceManager, string sequenceId)
{
var result = await sequenceManager.ValidateAndRotateAsync(sequenceId);
await (result.Outcome switch
{
ValidationOutcome.Success => HandleSuccess(context, result.NextSequence, result.UserId, result.RequestsRemaining),
_ => HandleFailure(context, result.Outcome)
});
}
private async Task HandleSuccess(HttpContext context, Option<string> nextSequenceOpt, Option<string> userIdOpt, Option<int> requestsRemainingOpt)
{
_ = nextSequenceOpt.State switch
{
OptionState.Some => AddHeader(context, nextSequenceOpt.Value!),
OptionState.None => 0,
_ => 0
};
_ = userIdOpt.State switch
{
OptionState.Some => AddUserContext(context, userIdOpt.Value!),
OptionState.None => 0,
_ => 0
};
_ = requestsRemainingOpt.State switch
{
OptionState.Some => AddRemainingHeader(context, requestsRemainingOpt.Value),
OptionState.None => 0,
_ => 0
};
await next(context);
}
private int AddRemainingHeader(HttpContext context, int remaining)
{
context.Response.Headers.Append(_options.RequestsRemainingHeaderName, remaining.ToString());
return 1;
}
private int AddHeader(HttpContext context, string value)
{
context.Response.Headers.Append(_options.NextHeaderName, value);
return 1;
}
private int AddUserContext(HttpContext context, string userId)
{
context.Items[_options.UserIdItemKey] = userId;
return 1;
}
private Task HandleFailure(HttpContext context, ValidationOutcome outcome)
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
}