Files
Vitalii Litvinchuk 23958e8e2c first commit
2026-06-13 23:23:50 +03:00

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));
}
}