namespace SequenceAuth.Lib; public record SequenceValidationResult(ValidationOutcome Outcome, Option NextSequence, Option UserId, Option RequestsRemaining); public class SequenceManager(ISequenceStore store) { public async Task ValidateAndRotateAsync(string currentSequenceId) { var sequenceOpt = await store.GetSequenceAsync(currentSequenceId); return sequenceOpt.State switch { OptionState.None => new SequenceValidationResult(ValidationOutcome.SequenceNotFound, Option.None(), Option.None(), Option.None()), OptionState.Some => await ProcessSequenceAsync(currentSequenceId, sequenceOpt.Value!), _ => new SequenceValidationResult(ValidationOutcome.InternalError, Option.None(), Option.None(), Option.None()) }; } private async Task ProcessSequenceAsync(string currentSequenceId, SequenceData data) { return data.State switch { SequenceState.Compromised => new SequenceValidationResult(ValidationOutcome.CompromisedSequenceDetected, Option.None(), Option.None(), Option.None()), SequenceState.Rotated => await HandleRotatedSequenceAsync(data.UserId), SequenceState.Active or SequenceState.Initialized => await HandleActiveSequenceAsync(currentSequenceId, data), _ => new SequenceValidationResult(ValidationOutcome.InternalError, Option.None(), Option.None(), Option.None()) }; } private async Task HandleRotatedSequenceAsync(string userId) { await store.InvalidateUserSessionsAsync(userId); return new SequenceValidationResult(ValidationOutcome.CompromisedSequenceDetected, Option.None(), Option.None(), Option.None()); } private async Task HandleActiveSequenceAsync(string currentSequenceId, SequenceData data) { var checkLimit = CheckLimit(data.RequestsRemaining); return checkLimit switch { ValidationOutcome.LimitExceeded => new SequenceValidationResult(ValidationOutcome.LimitExceeded, Option.None(), Option.None(), Option.None()), ValidationOutcome.Success => await RotateSequenceAsync(currentSequenceId, data), _ => new SequenceValidationResult(ValidationOutcome.InternalError, Option.None(), Option.None(), Option.None()) }; } private ValidationOutcome CheckLimit(int requestsRemaining) { return requestsRemaining switch { <= 0 => ValidationOutcome.LimitExceeded, > 0 => ValidationOutcome.Success }; } private async Task 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.Some(futureSequenceId), Option.Some(data.UserId), Option.Some(data.RequestsRemaining - 1)); } }