73 lines
3.6 KiB
C#
73 lines
3.6 KiB
C#
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));
|
|
}
|
|
}
|