diff --git a/Vonage.Test/NumberVerification/Authenticate/AuthenticateRequestTest.cs b/Vonage.Test/NumberVerification/Authenticate/AuthenticateRequestTest.cs new file mode 100644 index 00000000..5f13912a --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/AuthenticateRequestTest.cs @@ -0,0 +1,70 @@ +using FluentAssertions; +using Vonage.Common.Failures; +using Vonage.NumberVerification.Authenticate; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "Request")] +public class AuthenticateRequestTest +{ + private const string ValidScope = "scope=test"; + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void ParseFromPhoneNumber_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value) => + AuthenticateRequest.Parse(value, ValidScope).Should() + .BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace.")); + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void ParseFromPhoneNumber_ShouldReturnFailure_GivenScopeIsNullOrWhitespace(string value) => + AuthenticateRequest.Parse("1234567", value).Should() + .BeParsingFailure("Scope cannot be null or whitespace."); + + [Fact] + public void ParseFromPhoneNumber_ShouldReturnFailure_GivenNumberContainsNonDigits() => + AuthenticateRequest.Parse("1234567abc123", ValidScope).Should() + .BeFailure(ResultFailure.FromErrorMessage("Number can only contain digits.")); + + [Fact] + public void ParseFromPhoneNumber_ShouldReturnFailure_GivenNumberLengthIsLowerThan7() => + AuthenticateRequest.Parse("123456", ValidScope).Should() + .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be lower than 7.")); + + [Fact] + public void ParseFromPhoneNumber_ShouldReturnFailure_GivenNumberLengthIsHigherThan15() => + AuthenticateRequest.Parse("1234567890123456", ValidScope).Should() + .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be higher than 15.")); + + [Theory] + [InlineData("1234567", "1234567")] + [InlineData("123456789012345", "123456789012345")] + [InlineData("+1234567890", "1234567890")] + [InlineData("+123456789012345", "123456789012345")] + [InlineData("+++1234567890", "1234567890")] + public void ParseFromPhoneNumber_ShouldSetNumber(string value, string expected) => + AuthenticateRequest.Parse(value, ValidScope) + .Map(request => request.PhoneNumber.Number) + .Should() + .BeSuccess(expected); + + [Fact] + public void ParseFromPhoneNumber_ShouldSetScope() => + AuthenticateRequest.Parse("1234567", ValidScope) + .Map(request => request.Scope) + .Should() + .BeSuccess(ValidScope); + + [Fact] + public void BuildAuthorizeRequest() + { + 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/NumberVerification/Authenticate/AuthenticateResponseTest.cs b/Vonage.Test/NumberVerification/Authenticate/AuthenticateResponseTest.cs new file mode 100644 index 00000000..16003212 --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/AuthenticateResponseTest.cs @@ -0,0 +1,17 @@ +using System.Net.Http.Headers; +using FluentAssertions; +using Vonage.NumberVerification.Authenticate; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "Request")] +public class AuthenticateResponseTest +{ + [Fact] + public void BuildAuthenticationHeader_ShouldReturnBearerAuth() => + new AuthenticateResponse("123456789") + .BuildAuthenticationHeader() + .Should() + .Be(new AuthenticationHeaderValue("Bearer", "123456789")); +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/AuthorizeRequestTest.cs b/Vonage.Test/NumberVerification/Authenticate/AuthorizeRequestTest.cs new file mode 100644 index 00000000..5c103fa4 --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/AuthorizeRequestTest.cs @@ -0,0 +1,16 @@ +using Vonage.NumberVerification.Authenticate; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "Request")] +public class AuthorizeRequestTest +{ + [Fact] + public void GetEndpointPath_ShouldReturnApiEndpoint() => + AuthenticateRequest.Parse("123456789", "scope") + .Map(request => request.BuildAuthorizeRequest()) + .Map(r => r.GetEndpointPath()) + .Should().BeSuccess("oauth2/auth"); +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/AuthorizeResponseTest.cs b/Vonage.Test/NumberVerification/Authenticate/AuthorizeResponseTest.cs new file mode 100644 index 00000000..0924c20a --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/AuthorizeResponseTest.cs @@ -0,0 +1,15 @@ +using FluentAssertions; +using Vonage.NumberVerification.Authenticate; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "Request")] +public class AuthorizeResponseTest +{ + [Fact] + public void BuildGetTokenRequest() => + new AuthorizeResponse("123456", 0, 0) + .BuildGetTokenRequest() + .Should().Be(new GetTokenRequest("123456")); +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/Data/ShouldDeserializeAccessToken-response.json b/Vonage.Test/NumberVerification/Authenticate/Data/ShouldDeserializeAccessToken-response.json new file mode 100644 index 00000000..73ab58da --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/Data/ShouldDeserializeAccessToken-response.json @@ -0,0 +1,5 @@ +{ + "access_token": "ABCDEFG", + "token_type": "Bearer", + "expires_in": 3600 +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/Data/ShouldDeserializeAuthorize-response.json b/Vonage.Test/NumberVerification/Authenticate/Data/ShouldDeserializeAuthorize-response.json new file mode 100644 index 00000000..0d578eea --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/Data/ShouldDeserializeAuthorize-response.json @@ -0,0 +1,5 @@ +{ + "auth_req_id": "123456789", + "expires_in": 120, + "interval": 2 +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/E2ETest.cs b/Vonage.Test/NumberVerification/Authenticate/E2ETest.cs new file mode 100644 index 00000000..fcffb2d0 --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/E2ETest.cs @@ -0,0 +1,41 @@ +using System.Net; +using System.Threading.Tasks; +using Vonage.NumberVerification.Authenticate; +using Vonage.Test.Common.Extensions; +using WireMock.ResponseBuilders; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "E2E")] +public class E2ETest : SimSwap.E2EBase +{ + public E2ETest() : base(typeof(E2ETest).Namespace) + { + } + + [Fact] + public async Task Authenticate() + { + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/oauth2/auth") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .WithBody( + "login_hint=tel:%2B447700900000&scope=openid+dpv%3AFraudPreventionAndDetection%23check-sim-swap") + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAuthorize)))); + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/oauth2/token") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .WithBody("auth_req_id=123456789&grant_type=urn:openid:params:grant-type:ciba") + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAccessToken)))); + await this.Helper.VonageClient.NumberVerificationClient + .AuthenticateAsync(AuthenticateRequest.Parse("447700900000", + "dpv:FraudPreventionAndDetection#check-sim-swap")) + .Should() + .BeSuccessAsync(new AuthenticateResponse("ABCDEFG")); + } +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/GetTokenRequestTest.cs b/Vonage.Test/NumberVerification/Authenticate/GetTokenRequestTest.cs new file mode 100644 index 00000000..6c79c7c3 --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/GetTokenRequestTest.cs @@ -0,0 +1,13 @@ +using FluentAssertions; +using Vonage.NumberVerification.Authenticate; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "Request")] +public class GetTokenRequestTest +{ + [Fact] + public void GetEndpointPath_ShouldReturnApiEndpoint() => + new GetTokenRequest("123456").GetEndpointPath().Should().Be("oauth2/token"); +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/Authenticate/SerializationTest.cs b/Vonage.Test/NumberVerification/Authenticate/SerializationTest.cs new file mode 100644 index 00000000..de31237b --- /dev/null +++ b/Vonage.Test/NumberVerification/Authenticate/SerializationTest.cs @@ -0,0 +1,31 @@ +using Vonage.NumberVerification.Authenticate; +using Vonage.Serialization; +using Vonage.Test.Common; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.NumberVerification.Authenticate; + +[Trait("Category", "Serialization")] +public class SerializationTest +{ + private readonly SerializationTestHelper helper = new SerializationTestHelper( + typeof(SerializationTest).Namespace, + JsonSerializerBuilder.BuildWithSnakeCase()); + + [Fact] + public void ShouldDeserializeAuthorize() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedAuthorizeResponse()); + + [Fact] + public void ShouldDeserializeAccessToken() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedTokenResponse()); + + internal static AuthorizeResponse GetExpectedAuthorizeResponse() => new AuthorizeResponse("123456789", 120, 2); + + internal static GetTokenResponse GetExpectedTokenResponse() => new GetTokenResponse("ABCDEFG", "Bearer", 3600); +} \ No newline at end of file diff --git a/Vonage.Test/NumberVerification/E2EBase.cs b/Vonage.Test/NumberVerification/E2EBase.cs new file mode 100644 index 00000000..ab1581b8 --- /dev/null +++ b/Vonage.Test/NumberVerification/E2EBase.cs @@ -0,0 +1,16 @@ +using Vonage.Serialization; +using Vonage.Test.Common; +using Vonage.Test.TestHelpers; + +namespace Vonage.Test.NumberVerification; + +public class E2EBase +{ + internal readonly TestingContext Helper; + internal readonly SerializationTestHelper Serialization; + + protected E2EBase(string serializationNamespace) : this() => this.Serialization = + new SerializationTestHelper(serializationNamespace, JsonSerializerBuilder.BuildWithSnakeCase()); + + protected E2EBase() => this.Helper = TestingContext.WithBearerCredentials("Url.Api.EMEA"); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs index ea17f347..258d1bce 100644 --- a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs +++ b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequestTest.cs @@ -10,7 +10,7 @@ namespace Vonage.Test.SimSwap.Authenticate; public class AuthenticateRequestTest { private const string ValidScope = "scope=test"; - + [Theory] [InlineData("")] [InlineData(" ")] @@ -18,7 +18,7 @@ public class AuthenticateRequestTest public void Parse_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value) => AuthenticateRequest.Parse(value, ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace.")); - + [Theory] [InlineData("")] [InlineData(" ")] @@ -26,22 +26,22 @@ public void Parse_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value 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() => AuthenticateRequest.Parse("1234567abc123", ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number can only contain digits.")); - + [Fact] - public void Parse_ShouldReturnFailure_GivenNumberLengthIsHigherThan7() => + public void Parse_ShouldReturnFailure_GivenNumberLengthIsLowerThan7() => AuthenticateRequest.Parse("123456", ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be lower than 7.")); - + [Fact] - public void Parse_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() => + public void Parse_ShouldReturnFailure_GivenNumberLengthIsHigherThan15() => AuthenticateRequest.Parse("1234567890123456", ValidScope).Should() .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be higher than 15.")); - + [Theory] [InlineData("1234567", "1234567")] [InlineData("123456789012345", "123456789012345")] @@ -53,14 +53,14 @@ public void Parse_ShouldSetNumber(string value, string expected) => .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() { diff --git a/Vonage.Test/Vonage.Test.csproj b/Vonage.Test/Vonage.Test.csproj index 3918ae76..0d2b3faf 100644 --- a/Vonage.Test/Vonage.Test.csproj +++ b/Vonage.Test/Vonage.Test.csproj @@ -1212,6 +1212,12 @@ PreserveNewest + + Always + + + Always + diff --git a/Vonage/NumberVerification/Authenticate/AuthenticateRequest.cs b/Vonage/NumberVerification/Authenticate/AuthenticateRequest.cs new file mode 100644 index 00000000..2ef762f9 --- /dev/null +++ b/Vonage/NumberVerification/Authenticate/AuthenticateRequest.cs @@ -0,0 +1,41 @@ +using Vonage.Common; +using Vonage.Common.Monads; +using Vonage.Common.Validation; + +namespace Vonage.NumberVerification.Authenticate; + +/// +/// Represents a request to authenticate towards NumberVerification API. +/// +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, string tokenScope) => + PhoneNumber.Parse(number).Map(phoneNumber => new AuthenticateRequest + { + 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, this.Scope); +} \ No newline at end of file diff --git a/Vonage/NumberVerification/Authenticate/AuthenticateResponse.cs b/Vonage/NumberVerification/Authenticate/AuthenticateResponse.cs new file mode 100644 index 00000000..af43e840 --- /dev/null +++ b/Vonage/NumberVerification/Authenticate/AuthenticateResponse.cs @@ -0,0 +1,16 @@ +using System.Net.Http.Headers; + +namespace Vonage.NumberVerification.Authenticate; + +/// +/// Represents an authentication response. +/// +/// The access token. +public record AuthenticateResponse(string AccessToken) +{ + /// + /// + /// + public AuthenticationHeaderValue BuildAuthenticationHeader() => + new AuthenticationHeaderValue("Bearer", this.AccessToken); +} \ No newline at end of file diff --git a/Vonage/NumberVerification/Authenticate/AuthorizeRequest.cs b/Vonage/NumberVerification/Authenticate/AuthorizeRequest.cs new file mode 100644 index 00000000..6ad7eb46 --- /dev/null +++ b/Vonage/NumberVerification/Authenticate/AuthorizeRequest.cs @@ -0,0 +1,31 @@ +using System.Net; +using System.Net.Http; +using System.Text; +using Vonage.Common; +using Vonage.Common.Client; + +namespace Vonage.NumberVerification.Authenticate; + +internal record AuthorizeRequest(PhoneNumber Number, string Scope) : IVonageRequest +{ + public string GetEndpointPath() => "oauth2/auth"; + + public HttpRequestMessage BuildRequestMessage() => + VonageRequestBuilder + .Initialize(HttpMethod.Post, this.GetEndpointPath()) + .WithContent(this.GetRequestContent()) + .Build(); + + private string GetUrlEncoded() + { + var builder = new StringBuilder(); + builder.Append("login_hint=tel:"); + builder.Append(WebUtility.UrlEncode(this.Number.NumberWithInternationalIndicator)); + builder.Append("&scope=openid"); + builder.Append(WebUtility.UrlEncode($" {this.Scope}")); + return builder.ToString(); + } + + private StringContent GetRequestContent() => + new StringContent(this.GetUrlEncoded(), Encoding.UTF8, "application/x-www-form-urlencoded"); +} \ No newline at end of file diff --git a/Vonage/NumberVerification/Authenticate/AuthorizeResponse.cs b/Vonage/NumberVerification/Authenticate/AuthorizeResponse.cs new file mode 100644 index 00000000..69acbb33 --- /dev/null +++ b/Vonage/NumberVerification/Authenticate/AuthorizeResponse.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Vonage.NumberVerification.Authenticate; + +internal record AuthorizeResponse( + [property: JsonPropertyName("auth_req_id")] + string RequestId, + [property: JsonPropertyName("expires_in")] + int ExpiresIn, + [property: JsonPropertyName("interval")] + int Interval) +{ + internal GetTokenRequest BuildGetTokenRequest() => new GetTokenRequest(this.RequestId); +} \ No newline at end of file diff --git a/Vonage/NumberVerification/Authenticate/GetTokenRequest.cs b/Vonage/NumberVerification/Authenticate/GetTokenRequest.cs new file mode 100644 index 00000000..7a51b14b --- /dev/null +++ b/Vonage/NumberVerification/Authenticate/GetTokenRequest.cs @@ -0,0 +1,29 @@ +using System.Net; +using System.Net.Http; +using System.Text; +using Vonage.Common.Client; + +namespace Vonage.NumberVerification.Authenticate; + +internal record GetTokenRequest(string RequestId) : IVonageRequest +{ + public HttpRequestMessage BuildRequestMessage() => + VonageRequestBuilder + .Initialize(HttpMethod.Post, this.GetEndpointPath()) + .WithContent(this.GetRequestContent()) + .Build(); + + public string GetEndpointPath() => "oauth2/token"; + + private string GetUrlEncoded() + { + var builder = new StringBuilder(); + builder.Append("auth_req_id="); + builder.Append(WebUtility.UrlEncode(this.RequestId)); + builder.Append("&grant_type=urn:openid:params:grant-type:ciba"); + return builder.ToString(); + } + + private StringContent GetRequestContent() => + new StringContent(this.GetUrlEncoded(), Encoding.UTF8, "application/x-www-form-urlencoded"); +} \ No newline at end of file diff --git a/Vonage/NumberVerification/Authenticate/GetTokenResponse.cs b/Vonage/NumberVerification/Authenticate/GetTokenResponse.cs new file mode 100644 index 00000000..1fdccbd6 --- /dev/null +++ b/Vonage/NumberVerification/Authenticate/GetTokenResponse.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace Vonage.NumberVerification.Authenticate; + +internal record GetTokenResponse( + [property: JsonPropertyName("access_token")] + string AccessToken, + [property: JsonPropertyName("token_type")] + string TokenType, + [property: JsonPropertyName("expires_in")] + int ExpiresIn); \ No newline at end of file diff --git a/Vonage/NumberVerification/INumberVerificationClient.cs b/Vonage/NumberVerification/INumberVerificationClient.cs new file mode 100644 index 00000000..9c40a495 --- /dev/null +++ b/Vonage/NumberVerification/INumberVerificationClient.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Vonage.Common.Monads; +using Vonage.NumberVerification.Authenticate; + +namespace Vonage.NumberVerification; + +/// +/// Exposes NumberVerification features. +/// +public interface INumberVerificationClient +{ + /// + /// Authenticates towards NumberVerification API to retrieve an authentication token. + /// + /// The request. + /// Success or Failure. + Task> AuthenticateAsync(Result request); +} \ No newline at end of file diff --git a/Vonage/NumberVerification/NumberVerificationClient.cs b/Vonage/NumberVerification/NumberVerificationClient.cs new file mode 100644 index 00000000..de06d79f --- /dev/null +++ b/Vonage/NumberVerification/NumberVerificationClient.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Vonage.Common.Client; +using Vonage.Common.Monads; +using Vonage.NumberVerification.Authenticate; +using Vonage.Serialization; + +namespace Vonage.NumberVerification; + +internal class NumberVerificationClient : INumberVerificationClient +{ + private readonly VonageHttpClient vonageClient; + + internal NumberVerificationClient(VonageHttpClientConfiguration configuration) => + this.vonageClient = new VonageHttpClient(configuration, JsonSerializerBuilder.BuildWithSnakeCase()); + + /// + public Task> AuthenticateAsync(Result request) => + request.Map(BuildAuthorizeRequest) + .BindAsync(this.SendAuthorizeRequest) + .Map(BuildGetTokenRequest) + .BindAsync(this.SendGetTokenRequest) + .Map(BuildAuthenticateResponse); + + private static AuthenticateResponse BuildAuthenticateResponse(GetTokenResponse response) => + new AuthenticateResponse(response.AccessToken); + + private Task> SendGetTokenRequest(GetTokenRequest request) => + this.vonageClient.SendWithResponseAsync(request); + + private Task> SendAuthorizeRequest(AuthorizeRequest request) => + this.vonageClient.SendWithResponseAsync(request); + + private static GetTokenRequest BuildGetTokenRequest(AuthorizeResponse request) => request.BuildGetTokenRequest(); + + private static AuthorizeRequest BuildAuthorizeRequest(AuthenticateRequest request) => + request.BuildAuthorizeRequest(); +} \ No newline at end of file diff --git a/Vonage/Vonage.csproj b/Vonage/Vonage.csproj index 158022de..64db36c6 100644 --- a/Vonage/Vonage.csproj +++ b/Vonage/Vonage.csproj @@ -80,6 +80,9 @@ + + + diff --git a/Vonage/VonageClient.cs b/Vonage/VonageClient.cs index 3dff9093..833ac2aa 100644 --- a/Vonage/VonageClient.cs +++ b/Vonage/VonageClient.cs @@ -14,6 +14,7 @@ using Vonage.NumberInsights; using Vonage.NumberInsightV2; using Vonage.Numbers; +using Vonage.NumberVerification; using Vonage.Pricing; using Vonage.ProactiveConnect; using Vonage.Redaction; @@ -37,37 +38,37 @@ public class VonageClient private readonly Maybe configuration = Maybe.None; private readonly ITimeProvider timeProvider = new TimeProvider(); private Credentials credentials; - + /// /// Constructor for VonageClient. /// /// Credentials to be used for further HTTP calls. public VonageClient(Credentials credentials) => this.Credentials = credentials; - + internal VonageClient(Credentials credentials, Configuration configuration, ITimeProvider timeProvider) { this.timeProvider = timeProvider; this.configuration = configuration; this.Credentials = credentials; } - + internal VonageClient(Configuration configuration) { this.configuration = this.GetConfiguration(); this.Credentials = configuration.BuildCredentials(); } - + public IAccountClient AccountClient { get; private set; } - + public IApplicationClient ApplicationClient { get; private set; } - + /// /// Exposes Conversations features. /// public IConversationsClient ConversationsClient { get; private set; } - + public IConversionClient ConversionClient { get; private set; } - + /// /// Gets or sets credentials for this client. /// @@ -82,67 +83,69 @@ public Credentials Credentials this.PropagateCredentials(); } } - + /// /// Exposes Meetings features. /// public IMeetingsClient MeetingsClient { get; private set; } - + public IMessagesClient MessagesClient { get; private set; } - + public INumberInsightClient NumberInsightClient { get; private set; } - + /// /// Exposes Number Insight V2 features. /// public INumberInsightV2Client NumberInsightV2Client { get; private set; } - + public INumbersClient NumbersClient { get; private set; } - + public IPricingClient PricingClient { get; private set; } - + /// /// Exposes ProactiveConnect features. /// public IProactiveConnectClient ProactiveConnectClient { get; private set; } - + public IRedactClient RedactClient { get; private set; } - + public IShortCodesClient ShortCodesClient { get; private set; } - + public ISmsClient SmsClient { get; private set; } - + /// /// Exposes SubAccounts features. /// public ISubAccountsClient SubAccountsClient { get; private set; } - + /// /// Exposes User management features. /// public IUsersClient UsersClient { get; private set; } - + public IVerifyClient VerifyClient { get; private set; } - + /// /// Exposes VerifyV2 features. /// public IVerifyV2Client VerifyV2Client { get; private set; } - + /// /// Exposes Video features. /// public IVideoClient VideoClient { get; private set; } - + public IVoiceClient VoiceClient { get; private set; } - + public ISimSwapClient SimSwapClient { get; private set; } - + + public INumberVerificationClient NumberVerificationClient { get; private set; } + private VonageHttpClientConfiguration BuildConfiguration(HttpClient client) => new(client, this.Credentials.GetAuthenticationHeader(), this.Credentials.GetUserAgent()); - + private Configuration GetConfiguration() => this.configuration.IfNone(Configuration.Instance); - + private void PropagateCredentials() { var currentConfiguration = this.GetConfiguration(); @@ -171,6 +174,7 @@ private void PropagateCredentials() this.MeetingsClient = new MeetingsClient(euConfiguration, new FileSystem()); this.ProactiveConnectClient = new ProactiveConnectClient(euConfiguration); this.SimSwapClient = new SimSwapClient(euConfiguration); + this.NumberVerificationClient = new NumberVerificationClient(euConfiguration); this.VideoClient = new VideoClient(videoConfiguration); } } \ No newline at end of file