diff --git a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs similarity index 54% rename from Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs rename to Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs index 34e37d4d..ea17f347 100644 --- a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs +++ b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs @@ -1,34 +1,45 @@ using FluentAssertions; using Vonage.Common.Failures; +using Vonage.SimSwap.Authenticate; using Vonage.Test.Common.Extensions; using Xunit; namespace Vonage.Test.SimSwap.Authenticate; [Trait("Category", "Request")] -public class AuthenticateRequest +public class AuthenticateRequestTest { + private const string ValidScope = "scope=test"; + [Theory] [InlineData("")] [InlineData(" ")] [InlineData(null)] public void Parse_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value) => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Should() + AuthenticateRequest.Parse(value, ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace.")); + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void Parse_ShouldReturnFailure_GivenScopeIsNullOrWhitespace(string value) => + AuthenticateRequest.Parse("1234567", value).Should() + .BeParsingFailure("Scope cannot be null or whitespace."); + [Fact] public void Parse_ShouldReturnFailure_GivenNumberContainsNonDigits() => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("1234567abc123").Should() + AuthenticateRequest.Parse("1234567abc123", ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number can only contain digits.")); [Fact] public void Parse_ShouldReturnFailure_GivenNumberLengthIsHigherThan7() => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456").Should() + AuthenticateRequest.Parse("123456", ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be lower than 7.")); [Fact] public void Parse_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("1234567890123456").Should() + AuthenticateRequest.Parse("1234567890123456", ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be higher than 15.")); [Theory] @@ -37,14 +48,23 @@ public void Parse_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() => [InlineData("+1234567890", "1234567890")] [InlineData("+123456789012345", "123456789012345")] [InlineData("+++1234567890", "1234567890")] - public void Parse_ShouldReturnSuccess_GivenNumberIsValid(string value, string expected) => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Map(number => number.PhoneNumber.Number).Should() + public void Parse_ShouldSetNumber(string value, string expected) => + AuthenticateRequest.Parse(value, ValidScope) + .Map(request => request.PhoneNumber.Number) + .Should() .BeSuccess(expected); + [Fact] + public void Parse_ShouldSetScope() => + AuthenticateRequest.Parse("1234567", ValidScope) + .Map(request => request.Scope) + .Should() + .BeSuccess(ValidScope); + [Fact] public void BuildAuthorizeRequest() { - var request = Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789").GetSuccessUnsafe(); + var request = AuthenticateRequest.Parse("123456789", ValidScope).GetSuccessUnsafe(); request.BuildAuthorizeRequest().Number.Should().Be(request.PhoneNumber); } } \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs b/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs index f8becb43..633cddc8 100644 --- a/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs +++ b/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs @@ -1,4 +1,5 @@ -using Vonage.Test.Common.Extensions; +using Vonage.SimSwap.Authenticate; +using Vonage.Test.Common.Extensions; using Xunit; namespace Vonage.Test.SimSwap.Authenticate; @@ -8,7 +9,7 @@ public class AuthorizeRequestTest { [Fact] public void GetEndpointPath_ShouldReturnApiEndpoint() => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789") + AuthenticateRequest.Parse("123456789", "scope") .Map(request => request.BuildAuthorizeRequest()) .Map(r => r.GetEndpointPath()) .Should().BeSuccess("oauth2/bc-authorize"); diff --git a/Vonage.Test/SimSwap/Authenticate/E2ETest.cs b/Vonage.Test/SimSwap/Authenticate/E2ETest.cs index 7ab8df50..d580bac1 100644 --- a/Vonage.Test/SimSwap/Authenticate/E2ETest.cs +++ b/Vonage.Test/SimSwap/Authenticate/E2ETest.cs @@ -33,7 +33,8 @@ public async Task Authenticate() .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAccessToken)))); await this.Helper.VonageClient.SimSwapClient - .AuthenticateAsync(Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("447700900000")) + .AuthenticateAsync(AuthenticateRequest.Parse("447700900000", + "scope=openid dpv:FraudPreventionAndDetection#check-sim-swap")) .Should() .BeSuccessAsync(new AuthenticateResponse("ABCDEFG")); } diff --git a/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs b/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs index 240a42fc..ae682eed 100644 --- a/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs +++ b/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs @@ -1,5 +1,6 @@ using Vonage.Common; using Vonage.Common.Monads; +using Vonage.Common.Validation; namespace Vonage.SimSwap.Authenticate; @@ -12,17 +13,29 @@ public readonly struct AuthenticateRequest /// Parses the input into an AuthenticateRequest. /// /// The phone number. + /// The authorization scope for the token. /// Success if the input matches all requirements. Failure otherwise. - public static Result Parse(string number) => + public static Result Parse(string number, string tokenScope) => PhoneNumber.Parse(number).Map(phoneNumber => new AuthenticateRequest - { - PhoneNumber = phoneNumber, - }); + { + PhoneNumber = phoneNumber, + Scope = tokenScope, + }) + .Map(InputEvaluation.Evaluate) + .Bind(evaluation => evaluation.WithRules(VerifyScope)); /// /// Subscriber number in E.164 format (starting with country code). Optionally prefixed with '+'. /// public PhoneNumber PhoneNumber { get; private init; } + /// + /// The authorization scope for the token. + /// + public string Scope { get; private init; } + + private static Result VerifyScope(AuthenticateRequest request) => + InputValidation.VerifyNotEmpty(request, request.Scope, nameof(request.Scope)); + internal AuthorizeRequest BuildAuthorizeRequest() => new AuthorizeRequest(this.PhoneNumber); } \ No newline at end of file diff --git a/Vonage/SimSwap/Check/CheckRequest.cs b/Vonage/SimSwap/Check/CheckRequest.cs index c0efdc2c..aee019c1 100644 --- a/Vonage/SimSwap/Check/CheckRequest.cs +++ b/Vonage/SimSwap/Check/CheckRequest.cs @@ -3,8 +3,10 @@ using System.Text.Json.Serialization; using Vonage.Common; using Vonage.Common.Client; +using Vonage.Common.Monads; using Vonage.Common.Serialization; using Vonage.Serialization; +using Vonage.SimSwap.Authenticate; namespace Vonage.SimSwap.Check; @@ -42,6 +44,15 @@ private StringContent GetRequestContent() => [JsonPropertyName("maxAge")] public int Period { get; internal init; } + /// + /// The authorization scope for the token. + /// + [JsonIgnore] + public string Scope => "scope=openid dpv:FraudPreventionAndDetection#check-sim-swap"; + + internal Result BuildAuthenticationRequest() => + AuthenticateRequest.Parse(this.PhoneNumber.NumberWithInternationalIndicator, this.Scope); + /// /// Initializes a builder. /// diff --git a/Vonage/SimSwap/SimSwapClient.cs b/Vonage/SimSwap/SimSwapClient.cs index a2329c36..d0adb260 100644 --- a/Vonage/SimSwap/SimSwapClient.cs +++ b/Vonage/SimSwap/SimSwapClient.cs @@ -37,8 +37,8 @@ private VonageHttpClient BuildClientWithAuthenticationHeader(AuthenticationHeade private static AuthenticationHeaderValue BuildAuthenticationHeader(AuthenticateResponse authentication) => authentication.BuildAuthenticationHeader(); - private Task> AuthenticateCheckRequest(CheckRequest r) => - this.AuthenticateAsync(AuthenticateRequest.Parse(r.PhoneNumber.NumberWithInternationalIndicator)); + private Task> AuthenticateCheckRequest(CheckRequest request) => + this.AuthenticateAsync(request.BuildAuthenticationRequest()); private static AuthenticateResponse BuildAuthenticateResponse(GetTokenResponse response) => new AuthenticateResponse(response.AccessToken);