first commit
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
namespace SequenceAuth.Lib;
|
||||
|
||||
public record SequenceValidationResult(ValidationOutcome Outcome, Option<string> NextSequence, Option<string> UserId, Option<int> RequestsRemaining);
|
||||
|
||||
public class SequenceManager(ISequenceStore store)
|
||||
{
|
||||
public async Task<SequenceValidationResult> ValidateAndRotateAsync(string currentSequenceId)
|
||||
{
|
||||
var sequenceOpt = await store.GetSequenceAsync(currentSequenceId);
|
||||
|
||||
return sequenceOpt.State switch
|
||||
{
|
||||
OptionState.None => new SequenceValidationResult(ValidationOutcome.SequenceNotFound, Option<string>.None(), Option<string>.None(), Option<int>.None()),
|
||||
OptionState.Some => await ProcessSequenceAsync(currentSequenceId, sequenceOpt.Value!),
|
||||
_ => new SequenceValidationResult(ValidationOutcome.InternalError, Option<string>.None(), Option<string>.None(), Option<int>.None())
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<SequenceValidationResult> ProcessSequenceAsync(string currentSequenceId, SequenceData data)
|
||||
{
|
||||
return data.State switch
|
||||
{
|
||||
SequenceState.Compromised => new SequenceValidationResult(ValidationOutcome.CompromisedSequenceDetected, Option<string>.None(), Option<string>.None(), Option<int>.None()),
|
||||
SequenceState.Rotated => await HandleRotatedSequenceAsync(data.UserId),
|
||||
SequenceState.Active or SequenceState.Initialized => await HandleActiveSequenceAsync(currentSequenceId, data),
|
||||
_ => new SequenceValidationResult(ValidationOutcome.InternalError, Option<string>.None(), Option<string>.None(), Option<int>.None())
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<SequenceValidationResult> HandleRotatedSequenceAsync(string userId)
|
||||
{
|
||||
await store.InvalidateUserSessionsAsync(userId);
|
||||
return new SequenceValidationResult(ValidationOutcome.CompromisedSequenceDetected, Option<string>.None(), Option<string>.None(), Option<int>.None());
|
||||
}
|
||||
|
||||
private async Task<SequenceValidationResult> HandleActiveSequenceAsync(string currentSequenceId, SequenceData data)
|
||||
{
|
||||
var checkLimit = CheckLimit(data.RequestsRemaining);
|
||||
return checkLimit switch
|
||||
{
|
||||
ValidationOutcome.LimitExceeded => new SequenceValidationResult(ValidationOutcome.LimitExceeded, Option<string>.None(), Option<string>.None(), Option<int>.None()),
|
||||
ValidationOutcome.Success => await RotateSequenceAsync(currentSequenceId, data),
|
||||
_ => new SequenceValidationResult(ValidationOutcome.InternalError, Option<string>.None(), Option<string>.None(), Option<int>.None())
|
||||
};
|
||||
}
|
||||
|
||||
private ValidationOutcome CheckLimit(int requestsRemaining)
|
||||
{
|
||||
return requestsRemaining switch
|
||||
{
|
||||
<= 0 => ValidationOutcome.LimitExceeded,
|
||||
> 0 => ValidationOutcome.Success
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<SequenceValidationResult> RotateSequenceAsync(string currentSequenceId, SequenceData data)
|
||||
{
|
||||
var futureSequenceId = Guid.NewGuid().ToString("N");
|
||||
var oldDataRotated = data with { State = SequenceState.Rotated };
|
||||
await store.SaveSequenceAsync(currentSequenceId, oldDataRotated);
|
||||
|
||||
var newData = new SequenceData(
|
||||
UserId: data.UserId,
|
||||
RequestsRemaining: data.RequestsRemaining - 1,
|
||||
State: SequenceState.Active,
|
||||
NextSequenceId: futureSequenceId);
|
||||
|
||||
await store.SaveSequenceAsync(futureSequenceId, newData);
|
||||
|
||||
return new SequenceValidationResult(ValidationOutcome.Success, Option<string>.Some(futureSequenceId), Option<string>.Some(data.UserId), Option<int>.Some(data.RequestsRemaining - 1));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user