Skip to content

Commit d5fef5d

Browse files
committed
Adding a reCAPTCHA V3 tag. This can be used to automatically bind the challenge to a button.
1 parent 277b5bc commit d5fef5d

File tree

4 files changed

+505
-1
lines changed

4 files changed

+505
-1
lines changed

src/ReCaptcha/Localization/Resources.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ReCaptcha/Localization/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@
117117
<resheader name="writer">
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120+
<data name="ActionPropertyNullErrorMessage" xml:space="preserve">
121+
<value>The action is a madatory property, but was not set. </value>
122+
</data>
120123
<data name="CallbackPropertyNullErrorMessage" xml:space="preserve">
121124
<value>A callback function name must be specified. Invisible reCAPTCHA does not work without it.</value>
122125
</data>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System;
2+
using System.Text.Encodings.Web;
3+
using Griesoft.AspNetCore.ReCaptcha.Configuration;
4+
using Griesoft.AspNetCore.ReCaptcha.Extensions;
5+
using Griesoft.AspNetCore.ReCaptcha.Localization;
6+
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
7+
using Microsoft.AspNetCore.Razor.TagHelpers;
8+
using Microsoft.Extensions.Options;
9+
10+
namespace Griesoft.AspNetCore.ReCaptcha.TagHelpers
11+
{
12+
/// <summary>
13+
/// A reCAPTCHA V3 tag helper, which can be used to automatically bind the challenge to a button.
14+
/// </summary>
15+
/// <remarks>
16+
/// The <see cref="FormId"/> is required. With the exception that you set a <see cref="Callback"/> instead.
17+
/// When setting both the value set to <c>Callback</c> always wins and the <c>FormId</c> value is basically irrelevant.
18+
///
19+
/// For easiest use of this tag helper set only the <c>FormId</c>. This will add a default callback function to the body. That function does
20+
/// submit the form after a successful reCAPTCHA challenge.
21+
///
22+
/// If the tag is not inside the form that is going to be submitted, you should use a custom callback function. The default callback function
23+
/// does not add the reCAPTCHA token to the form, which will result in response verification failure.
24+
/// </remarks>
25+
/// <example>
26+
/// The simplest use of the tag would be:
27+
/// <code>
28+
/// <recaptchav3 formid="myForm" action="submit">Submit</recaptchav3>
29+
/// </code>
30+
///
31+
/// Which will translate into the following HTML:
32+
/// <code>
33+
/// <button class="g-recaptcha" data-sitekey="your_site_key" data-callback='submitmyForm' data-action='submit'>Submit</button>
34+
/// </code>
35+
/// </example>
36+
[HtmlTargetElement(Attributes = "callback,action")]
37+
[HtmlTargetElement(Attributes = "formid,action")]
38+
public class RecaptchaV3TagHelper : TagHelper
39+
{
40+
private readonly ITagHelperComponentManager _tagHelperComponentManager;
41+
private readonly RecaptchaSettings _settings;
42+
43+
/// <summary>
44+
///
45+
/// </summary>
46+
/// <param name="settings"></param>
47+
/// <param name="tagHelperComponentManager"></param>
48+
/// <exception cref="ArgumentNullException"></exception>
49+
public RecaptchaV3TagHelper(IOptionsMonitor<RecaptchaSettings> settings, ITagHelperComponentManager tagHelperComponentManager)
50+
{
51+
_ = settings ?? throw new ArgumentNullException(nameof(settings));
52+
_ = tagHelperComponentManager ?? throw new ArgumentNullException(nameof(tagHelperComponentManager));
53+
54+
_settings = settings.CurrentValue;
55+
_tagHelperComponentManager = tagHelperComponentManager;
56+
}
57+
58+
/// <summary>
59+
/// The id of the form that will be submitted after a successful reCAPTCHA challenge.
60+
/// </summary>
61+
/// <remarks>This does only apply when not specifying a <see cref="Callback"/>.</remarks>
62+
public string? FormId { get; set; }
63+
64+
/// <summary>
65+
/// Set the name of your callback function, which is called when the reCAPTCHA challenge was successful.
66+
/// A "g-recaptcha-response" token is added to your callback function parameters for server-side verification.
67+
/// </summary>
68+
public string Callback { get; set; } = string.Empty;
69+
70+
/// <summary>
71+
/// The name of the action that was triggered.
72+
/// </summary>
73+
/// <remarks>You should verify that the server-side verification response returns the same action.</remarks>
74+
public string Action { get; set; } = string.Empty;
75+
76+
/// <inheritdoc />
77+
/// <exception cref="ArgumentNullException"></exception>
78+
/// <exception cref="NullReferenceException">Thrown when both <see cref="Callback"/> and <see cref="FormId"/> or <see cref="Action"/> are/is null or empty.</exception>
79+
public override void Process(TagHelperContext context, TagHelperOutput output)
80+
{
81+
_ = output ?? throw new ArgumentNullException(nameof(output));
82+
83+
if (string.IsNullOrEmpty(Callback) && string.IsNullOrEmpty(FormId))
84+
{
85+
throw new NullReferenceException(Resources.CallbackPropertyNullErrorMessage);
86+
}
87+
88+
if (string.IsNullOrEmpty(Action))
89+
{
90+
throw new NullReferenceException(Resources.ActionPropertyNullErrorMessage);
91+
}
92+
93+
if (string.IsNullOrEmpty(Callback))
94+
{
95+
Callback = $"submit{FormId}";
96+
_tagHelperComponentManager.Components.Add(new CallbackScriptTagHelperComponent(FormId!));
97+
}
98+
99+
output.TagMode = TagMode.StartTagAndEndTag;
100+
101+
output.TagName = "button";
102+
output.AddClass("g-recaptcha", HtmlEncoder.Default);
103+
104+
output.Attributes.SetAttribute("data-sitekey", _settings.SiteKey);
105+
output.Attributes.SetAttribute("data-callback", Callback);
106+
output.Attributes.SetAttribute("data-action", Action);
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)