diff --git a/samples/KITT.Web.ReCaptcha.Samples.Http/Program.cs b/samples/KITT.Web.ReCaptcha.Samples.Http/Program.cs index e210a1d..52467f6 100644 --- a/samples/KITT.Web.ReCaptcha.Samples.Http/Program.cs +++ b/samples/KITT.Web.ReCaptcha.Samples.Http/Program.cs @@ -11,7 +11,7 @@ builder.Services.AddCors( options => options.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin())); -builder.Services.AddReCaptchaV2(options => +builder.Services.AddReCaptchaV2HttpClient(options => { options.SecretKey = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"; // this is the v2 test secret }); diff --git a/samples/v2/KITT.Web.ReCaptcha.Sample.v2.BlazorServer/Program.cs b/samples/v2/KITT.Web.ReCaptcha.Sample.v2.BlazorServer/Program.cs index 5d8896f..af340c0 100644 --- a/samples/v2/KITT.Web.ReCaptcha.Sample.v2.BlazorServer/Program.cs +++ b/samples/v2/KITT.Web.ReCaptcha.Sample.v2.BlazorServer/Program.cs @@ -6,7 +6,7 @@ builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddReCaptchaV2(options => +builder.Services.AddReCaptchaV2HttpClient(options => { options.SecretKey = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"; }); diff --git a/samples/v2/KITT.Web.ReCaptcha.Sample.v2.Wasm.Api/Program.cs b/samples/v2/KITT.Web.ReCaptcha.Sample.v2.Wasm.Api/Program.cs index 699dc33..80b6017 100644 --- a/samples/v2/KITT.Web.ReCaptcha.Sample.v2.Wasm.Api/Program.cs +++ b/samples/v2/KITT.Web.ReCaptcha.Sample.v2.Wasm.Api/Program.cs @@ -5,7 +5,7 @@ .ConfigureFunctionsWorkerDefaults() .ConfigureServices((ctx, services) => { - services.AddReCaptchaV2(options => options.SecretKey = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"); + services.AddReCaptchaV2HttpClient(options => options.SecretKey = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"); }) .Build(); diff --git a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/KITT.Web.ReCaptcha.Sample.v3.BlazorServer.csproj b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/KITT.Web.ReCaptcha.Sample.v3.BlazorServer.csproj index 0dd2276..7f4c9b6 100644 --- a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/KITT.Web.ReCaptcha.Sample.v3.BlazorServer.csproj +++ b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/KITT.Web.ReCaptcha.Sample.v3.BlazorServer.csproj @@ -8,6 +8,7 @@ + diff --git a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Pages/Index.razor b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Pages/Index.razor index e095d69..73a8d33 100644 --- a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Pages/Index.razor +++ b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Pages/Index.razor @@ -2,7 +2,8 @@ @using System.ComponentModel.DataAnnotations @using KITT.Web.ReCaptcha.Blazor.v3 -@inject ReCaptchaService ReCaptcha +@inject Blazor.v3.ReCaptchaService ReCaptchaClient +@inject Http.v3.ReCaptchaService ReCaptchaHttp KITT ReCaptcha v3 - Blazor Server sample @@ -45,11 +46,24 @@ { try { - var reCaptchaClientResponse = await ReCaptcha.VerifyAsync(action: "submit"); + var reCaptchaClientResponse = await ReCaptchaClient.VerifyAsync(action: "submit"); if (reCaptchaClientResponse.Succeeded) { - message = "reCaptcha validated on client successfully!"; - isSuccessMessage = true; + var serverSideResponse = await ReCaptchaHttp.VerifyAsync( + reCaptchaClientResponse.Response, + action: "submit"); + + if (serverSideResponse.Success) + { + message = "reCaptcha validated successfully!"; + isSuccessMessage = true; + } + else + { + message = string.Join(",", serverSideResponse.ErrorCodes); + isSuccessMessage = false; + } + } else { diff --git a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Program.cs b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Program.cs index 547d6c5..3c1c56c 100644 --- a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Program.cs +++ b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/Program.cs @@ -1,4 +1,5 @@ using KITT.Web.ReCaptcha.Blazor.v3; +using KITT.Web.ReCaptcha.Http.v3; var builder = WebApplication.CreateBuilder(args); @@ -6,7 +7,9 @@ builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddReCaptchaV3(options => options.SiteKey = builder.Configuration["ReCaptcha:SiteKey"]!); +builder.Services + .AddReCaptchaV3(options => options.SiteKey = builder.Configuration["ReCaptcha:SiteKey"]!) + .AddReCaptchaV3HttpClient(options => options.SecretKey = builder.Configuration["ReCaptcha:SecretKey"]!); var app = builder.Build(); diff --git a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/appsettings.Development.json b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/appsettings.Development.json index 770d3e9..1c6a417 100644 --- a/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/appsettings.Development.json +++ b/samples/v3/KITT.Web.ReCaptcha.Sample.v3.BlazorServer/appsettings.Development.json @@ -5,5 +5,9 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ReCaptcha": { + "SiteKey": "6Le_GAgpAAAAACUDETr8C09Fb-rr1ZU0eP9_5kX_", + "SecretKey": "6Le_GAgpAAAAADWRoVeXqSg7BZL_cBWwPpt_sBdp" } } diff --git a/src/KITT.Web.ReCaptcha.Http/KITT.Web.ReCaptcha.Http.csproj b/src/KITT.Web.ReCaptcha.Http/KITT.Web.ReCaptcha.Http.csproj index bd13c20..95e2c59 100644 --- a/src/KITT.Web.ReCaptcha.Http/KITT.Web.ReCaptcha.Http.csproj +++ b/src/KITT.Web.ReCaptcha.Http/KITT.Web.ReCaptcha.Http.csproj @@ -6,7 +6,7 @@ enable True KITT.Web.ReCaptcha.Http - 0.1.0 + 0.2.0 Alberto Mori Contains the service which calls Google reCaptcha verification endpoint for server-side validation https://github.com/albx/KITT.Web.ReCaptcha diff --git a/src/KITT.Web.ReCaptcha.Http/README.md b/src/KITT.Web.ReCaptcha.Http/README.md index 709c4cc..0c5116d 100644 --- a/src/KITT.Web.ReCaptcha.Http/README.md +++ b/src/KITT.Web.ReCaptcha.Http/README.md @@ -13,14 +13,15 @@ It can be installed using the ```dotnet add package``` command or the NuGet wiza dotnet add package KITT.Web.ReCaptcha.Http ``` -## Usage +## reCaptcha v2 +### Usage The project gives you an HttpClient service which expose a *VerifyAsync* method to verify the reCaptcha response send by the user from the client. -Add the namespace ```KITT.Web.ReCaptcha.Http.v2``` to your ```Program.cs``` and use the *AddReCaptchaV2* extension method to your ```IServiceCollection``` instance: +Add the namespace ```KITT.Web.ReCaptcha.Http.v2``` to your ```Program.cs``` and use the *AddReCaptchaV2HttpClient* extension method to your ```IServiceCollection``` instance: ``` -builder.Services.AddReCaptchaV2(options => +builder.Services.AddReCaptchaV2HttpClient(options => { options.SecretKey = ""; }); @@ -42,7 +43,7 @@ app.MapPost("/send", async (ReCaptchaService reCaptchaService, [FromBody] SendRe }); ``` -## Methods +### Methods The ```VerifyAsync``` method has the following input parameters: @@ -59,4 +60,57 @@ The method returns an instance of the ```ReCaptchaResponse``` class, which have |**Success**|*bool*: whether the verification ended successfully| |**ChallengeTimestamp**|*DateTime*: the timestamp of the challenge load| |**Hostname**|*string*: the hostname of the site where the reCAPTCHA was solved| -|**ErrorCodes**|*IEnumerable<string>*: the optional list of error codes (see [Google's official documentation](https://developers.google.com/recaptcha/docs/verify#error_code_reference))| \ No newline at end of file +|**ErrorCodes**|*IEnumerable<string>*: the optional list of error codes (see [Google's official documentation](https://developers.google.com/recaptcha/docs/verify#error_code_reference))| + +## reCaptcha v3 +### Usage + +The project gives you an HttpClient service which expose a *VerifyAsync* method to verify the reCaptcha response send by the user from the client. + +Add the namespace ```KITT.Web.ReCaptcha.Http.v3``` to your ```Program.cs``` and use the *AddReCaptchaV3HttpClient* extension method to your ```IServiceCollection``` instance: + +``` +builder.Services.AddReCaptchaV3HttpClient(options => +{ + options.SecretKey = ""; +}); +``` + +Then you can inject the ```ReCaptchaService``` class whenever you need and call the ```VerifyAsync``` like this: + +``` +app.MapPost("/send", async (ReCaptchaService reCaptchaService, [FromBody] SendRequest request) => +{ + // Here you call the reCaptcha server-side validation + var captchaResponse = await reCaptchaService.VerifyAsync(request.CaptchaResponse, request.Action); + if (!captchaResponse.Success) + { + return Results.BadRequest(captchaResponse.ErrorCodes); + } + + return Results.Ok(); +}); +``` + +### Methods + +The ```VerifyAsync``` method has the following input parameters: + +|Property|Description| +|---|---| +|**response** (Required)|*string*: The user response token provided by the reCAPTCHA client-side integration on your site.| +|**action** (Required)|*string*: The action value used to configure the reCaptcha| +|**remoteIp** (Optional)|*string*: The user's IP address. (Default: *null*)| +|**cancellationToken** (Optional)|*CancellationToken*: a cancellation token instance (Default: *CancellationToken.None*)| + +The method returns an instance of the ```ReCaptchaResponse``` class, which have the following properties: + +|Property|Description| +|---|---| +|**Success**|*bool*: whether the verification ended successfully| +|**Score**|*double*: the score for the request (from 0.0 to 1.0)| +|**Action**|*string*: the action name for this request| +|**ChallengeTimestamp**|*DateTime*: the timestamp of the challenge load| +|**Hostname**|*string*: the hostname of the site where the reCAPTCHA was solved| +|**ErrorCodes**|*IEnumerable<string>*: the optional list of error codes (see [Google's official documentation](https://developers.google.com/recaptcha/docs/verify#error_code_reference))| + diff --git a/src/KITT.Web.ReCaptcha.Http/v2/ServiceCollectionExtensions.cs b/src/KITT.Web.ReCaptcha.Http/v2/ServiceCollectionExtensions.cs index 010719d..79edc01 100644 --- a/src/KITT.Web.ReCaptcha.Http/v2/ServiceCollectionExtensions.cs +++ b/src/KITT.Web.ReCaptcha.Http/v2/ServiceCollectionExtensions.cs @@ -14,7 +14,7 @@ public static class ServiceCollectionExtensions /// The instance /// The action used to configure the options /// The instance for method chaining - public static IServiceCollection AddReCaptchaV2( + public static IServiceCollection AddReCaptchaV2HttpClient( this IServiceCollection services, Action configureOptions) { diff --git a/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaResponse.cs b/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaResponse.cs index aa1a3b0..c490c5b 100644 --- a/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaResponse.cs +++ b/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaResponse.cs @@ -2,6 +2,9 @@ namespace KITT.Web.ReCaptcha.Http.v3; +/// +/// Defines the response of the call to the reCaptcha verification endpoint +/// public record ReCaptchaResponse { /// @@ -10,9 +13,15 @@ public record ReCaptchaResponse [JsonPropertyName("success")] public bool Success { get; init; } + /// + /// Gets the score for the request + /// [JsonPropertyName("score")] public double Score { get; init; } + /// + /// Gets the action name for the request + /// [JsonPropertyName("action")] public string Action { get; init; } = string.Empty; diff --git a/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaService.cs b/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaService.cs index ce40503..9b126c3 100644 --- a/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaService.cs +++ b/src/KITT.Web.ReCaptcha.Http/v3/ReCaptchaService.cs @@ -4,20 +4,39 @@ namespace KITT.Web.ReCaptcha.Http.v3; +/// +/// This service verifies the captcha response from the client calling the Google API +/// public class ReCaptchaService { private readonly HttpClient _httpClient; private readonly ReCaptchaConfiguration _configuration; + /// + /// Constructs the service instance + /// + /// The instance configured to call the Google API + /// The instance which contains the server side secret key + /// Thrown when or instance is null public ReCaptchaService(HttpClient httpClient, IOptions reCaptchaConfigurationOptions) { - _httpClient = httpClient; + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _configuration = reCaptchaConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(reCaptchaConfigurationOptions)); ThrowIfConfigurationIsNotValid(_configuration); } + /// + /// Verifies the response of the reCaptcha client side integration + /// + /// (Required) The user response token provided by the reCAPTCHA client-side integration on your site. + /// (Required) The action value used to configure the reCaptcha + /// (Optional) The user's IP address. + /// A instance + /// The received from the call to the Google verification endpoint + /// Thrown when response is null or white-space + /// Thrown when the action returned from the server call does not match with the specified value from the client public async Task VerifyAsync(string response, string action, string? remoteIp = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(response)) diff --git a/src/KITT.Web.ReCaptcha.Http/v3/ServiceCollectionExtensions.cs b/src/KITT.Web.ReCaptcha.Http/v3/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..a239743 --- /dev/null +++ b/src/KITT.Web.ReCaptcha.Http/v3/ServiceCollectionExtensions.cs @@ -0,0 +1,30 @@ +using KITT.Web.ReCaptcha.Http.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace KITT.Web.ReCaptcha.Http.v3; + +/// +/// Defines the extensions methods to register in the IoC container +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds service and configures all the options needed + /// + /// The instance + /// The action used to configure the options + /// The instance for method chaining + public static IServiceCollection AddReCaptchaV3HttpClient( + this IServiceCollection services, + Action configureOptions) + { + services.Configure(configureOptions); + + services.AddHttpClient(); + + services.AddHttpClient( + client => client.BaseAddress = new Uri("https://www.google.com/recaptcha/")); + + return services; + } +} diff --git a/tests/KITT.Web.ReCaptcha.Http.Test/v3/ReCaptchaServiceTest.cs b/tests/KITT.Web.ReCaptcha.Http.Test/v3/ReCaptchaServiceTest.cs index c7771db..0b156aa 100644 --- a/tests/KITT.Web.ReCaptcha.Http.Test/v3/ReCaptchaServiceTest.cs +++ b/tests/KITT.Web.ReCaptcha.Http.Test/v3/ReCaptchaServiceTest.cs @@ -8,7 +8,7 @@ namespace KITT.Web.ReCaptcha.Http.Test.v3; public class ReCaptchaServiceTest { - private static readonly Uri _googleRecaptchaBaseUri = new Uri("https://www.google.com/recaptcha/"); + private static readonly Uri _googleRecaptchaBaseUri = new("https://www.google.com/recaptcha/"); #region Ctor tests [Theory] @@ -17,7 +17,7 @@ public class ReCaptchaServiceTest [InlineData(" ")] public void Ctor_Should_Throw_Argument_Exception_If_Secret_Key_Is_Missing(string secretKey) { - using HttpClient httpClient = new HttpClient(); + using HttpClient httpClient = new(); IOptions reCaptchaConfigurationOptions = Options.Create(new ReCaptchaConfiguration { SecretKey = secretKey }); var ex = Assert.Throws(() => new ReCaptchaService(httpClient, reCaptchaConfigurationOptions));