first commit
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user