Skip to content

Commit 99ad0ab

Browse files
Ticket #889 : Add CAPTCHA
1 parent fa7a124 commit 99ad0ab

57 files changed

Lines changed: 526 additions & 27 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@using FormBuilder.Components.Drag
2+
3+
<DraggableFormElement IsEditModeEnabled=@IsEditModeEnabled ParentContext="@ParentContext" Context="@Context" TDialog="ReCaptchaDialog" TRecord="ReCaptchaRecord" Record="@Value">
4+
<Children>
5+
@if(IsEditModeEnabled)
6+
{
7+
<span>Configure the RECAPTCHA</span>
8+
}
9+
else
10+
{
11+
<div id="@UniqueId"></div>
12+
}
13+
</Children>
14+
</DraggableFormElement>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using FormBuilder.Components.Drag;
2+
using Microsoft.AspNetCore.Components;
3+
using Microsoft.JSInterop;
4+
using System.ComponentModel;
5+
6+
namespace FormBuilder.Components.FormElements.ReCaptcha
7+
{
8+
public partial class ReCaptchaComponent : IGenericFormElement<ReCaptchaRecord>
9+
{
10+
[Parameter]
11+
public ReCaptchaRecord Value
12+
{
13+
get; set;
14+
}
15+
16+
[Parameter]
17+
public ParentEltContext ParentContext
18+
{
19+
get; set;
20+
}
21+
22+
[Parameter]
23+
public WorkflowContext Context
24+
{
25+
get; set;
26+
}
27+
28+
[Parameter]
29+
public bool IsEditModeEnabled
30+
{
31+
get; set;
32+
}
33+
34+
[Inject]
35+
private IJSRuntime jSRuntime
36+
{
37+
get; set;
38+
}
39+
40+
private string UniqueId = Guid.NewGuid().ToString();
41+
42+
protected override async Task OnAfterRenderAsync(bool firstRender)
43+
{
44+
if(firstRender)
45+
{
46+
if (!IsEditModeEnabled && !string.IsNullOrWhiteSpace(Value.SiteKey))
47+
{
48+
await jSRuntime.InvokeAsync<object>("reCaptchaV2.init");
49+
await jSRuntime.InvokeAsync<int>("reCaptchaV2.render", DotNetObjectReference.Create(this), UniqueId, Value.SiteKey);
50+
}
51+
}
52+
}
53+
54+
55+
[JSInvokable, EditorBrowsable(EditorBrowsableState.Never)]
56+
public void CallbackOnSuccess(string response)
57+
{
58+
Value.Value = response;
59+
StateHasChanged();
60+
}
61+
62+
[JSInvokable, EditorBrowsable(EditorBrowsableState.Never)]
63+
public void CallbackOnExpired()
64+
{
65+
66+
}
67+
}
68+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace FormBuilder.Components.FormElements.ReCaptcha
2+
{
3+
public class ReCaptchaDefinition : GenericFormElementDefinition<ReCaptchaRecord>
4+
{
5+
public override Type UiElt => typeof(ReCaptchaComponent);
6+
7+
8+
public static string TYPE = "ReCaptcha";
9+
10+
public override string Type => TYPE;
11+
12+
public override string Icon => "frame_person";
13+
14+
public override ElementDefinitionCategories Category => ElementDefinitionCategories.ELEMENT;
15+
16+
protected override void ProtectedInit(ReCaptchaRecord record, WorkflowContext context, List<IFormElementDefinition> definitions)
17+
{
18+
}
19+
}
20+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@using System.Text.Json
2+
@using FormBuilder.Components.Shared
3+
@inject DialogService dialogService
4+
5+
<RadzenStack>
6+
<RadzenFormField Text="Site key">
7+
<RadzenTextBox @bind-Value="@Record.SiteKey"></RadzenTextBox>
8+
</RadzenFormField>
9+
<RadzenButton Text="Save" Click="@HandleSave"></RadzenButton>
10+
</RadzenStack>
11+
12+
@code {
13+
public int selectedIndex { get; set; } = 0;
14+
ReCaptchaRecord CopyRecord { get; set; } = new ReCaptchaRecord();
15+
[Parameter] public ReCaptchaRecord Record { get; set; }
16+
[Parameter] public WorkflowContext WorkflowContext { get; set; }
17+
18+
protected override void OnParametersSet()
19+
{
20+
base.OnParametersSet();
21+
if (Record != null)
22+
{
23+
CopyRecord = JsonSerializer.Deserialize<ReCaptchaRecord>(JsonSerializer.Serialize(Record));
24+
}
25+
}
26+
27+
private void HandleSave()
28+
{
29+
dialogService.Close();
30+
}
31+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using FormBuilder.Models;
2+
using System.Text.Json.Nodes;
3+
4+
namespace FormBuilder.Components.FormElements.ReCaptcha
5+
{
6+
public class ReCaptchaRecord : IFormElementRecord
7+
{
8+
public string Id
9+
{
10+
get; set;
11+
}
12+
13+
public string CorrelationId
14+
{
15+
get; set;
16+
}
17+
18+
public string Type => ReCaptchaDefinition.TYPE;
19+
20+
public List<LabelTranslation> Labels
21+
{
22+
get; set;
23+
}
24+
25+
public Dictionary<string, object> HtmlAttributes
26+
{
27+
get; set;
28+
}
29+
30+
public string CssStyle
31+
{
32+
get; set;
33+
}
34+
35+
public string SiteKey
36+
{
37+
get; set;
38+
}
39+
40+
public string Value
41+
{
42+
get; set;
43+
}
44+
45+
public void Apply(JsonNode node)
46+
{
47+
}
48+
49+
public void ExtractJson(JsonObject json)
50+
{
51+
json.Add("CaptchaValue", Value);
52+
json.Add("CaptchaType", "V2ReCaptcha");
53+
}
54+
55+
public IFormElementRecord GetChild(string id)
56+
{
57+
return null;
58+
}
59+
60+
public IFormElementRecord GetChildByCorrelationId(string correlationId)
61+
{
62+
return null;
63+
}
64+
}
65+
}

formbuilder/FormBuilder/Models/IFormElementRecord.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using FormBuilder.Components.FormElements.ListData;
99
using FormBuilder.Components.FormElements.Paragraph;
1010
using FormBuilder.Components.FormElements.Password;
11+
using FormBuilder.Components.FormElements.ReCaptcha;
1112
using FormBuilder.Components.FormElements.Row;
1213
using FormBuilder.Components.FormElements.StackLayout;
1314
using FormBuilder.Components.FormElements.Title;
@@ -29,6 +30,7 @@ namespace FormBuilder.Models;
2930
[JsonDerivedType(typeof(ImageRecord), typeDiscriminator: "Image")]
3031
[JsonDerivedType(typeof(BackButtonRecord), typeDiscriminator: "BackButton")]
3132
[JsonDerivedType(typeof(RowLayoutRecord), typeDiscriminator: "RowLayout")]
33+
[JsonDerivedType(typeof(ReCaptchaRecord), typeDiscriminator: "ReCaptchaRecord")]
3234
public interface IFormElementRecord
3335
{
3436
string Id { get; set; }

formbuilder/FormBuilder/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using FormBuilder.Components.FormElements.ListData;
1313
using FormBuilder.Components.FormElements.Paragraph;
1414
using FormBuilder.Components.FormElements.Password;
15+
using FormBuilder.Components.FormElements.ReCaptcha;
1516
using FormBuilder.Components.FormElements.Row;
1617
using FormBuilder.Components.FormElements.StackLayout;
1718
using FormBuilder.Components.FormElements.Title;
@@ -65,6 +66,7 @@ public static void AddFormBuilderUi(this IServiceCollection services, Action<For
6566
services.AddTransient<IFormElementDefinition, TitleDefinition>();
6667
services.AddTransient<IFormElementDefinition, ImageDefinition>();
6768
services.AddTransient<IFormElementDefinition, BackButtonDefinition>();
69+
services.AddTransient<IFormElementDefinition, ReCaptchaDefinition>();
6870

6971
services.AddTransient<IDateTimeHelper, DateTimeHelper>();
7072
services.AddTransient<ITranslationHelper, TranslationHelper>();

formbuilder/FormBuilder/wwwroot/lib.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ FormBuilder.refreshCss = function (id, cssContent) {
4747
return;
4848
}
4949

50-
console.log('update');
5150
styleElement.removeAttribute('href');
5251
styleElement.id = id;
5352
styleElement.innerHTML = cssContent;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
var reCaptchaV2 = reCaptchaV2 || {};
3+
reCaptchaV2.scriptLoaded = null;
4+
5+
reCaptchaV2.waitScriptLoaded = function (resolve) {
6+
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.render !== 'undefined') {
7+
resolve();
8+
} else {
9+
setTimeout(() => reCaptchaV2.waitScriptLoaded(resolve), 100);
10+
}
11+
};
12+
13+
reCaptchaV2.init = function () {
14+
const scripts = Array.from(document.getElementsByTagName('script'));
15+
if (!scripts.some(s => (s.src || '').startsWith('https://www.google.com/recaptcha/api.js'))) {
16+
const script = document.createElement('script');
17+
script.src = 'https://www.google.com/recaptcha/api.js?render=explicit';
18+
script.async = true;
19+
script.defer = true;
20+
document.head.appendChild(script);
21+
}
22+
23+
if (reCaptchaV2.scriptLoaded === null) {
24+
reCaptchaV2.scriptLoaded = new Promise(reCaptchaV2.waitScriptLoaded);
25+
}
26+
return reCaptchaV2.scriptLoaded;
27+
};
28+
29+
reCaptchaV2.render = function (dotNetObj, selector, siteKey) {
30+
return new Promise((resolve, reject) => {
31+
reCaptchaV2.init().then(() => {
32+
try {
33+
const widgetId = grecaptcha.render(selector, {
34+
'sitekey': siteKey,
35+
'callback': (response) => {
36+
dotNetObj.invokeMethodAsync('CallbackOnSuccess', response);
37+
},
38+
'expired-callback': () => {
39+
dotNetObj.invokeMethodAsync('CallbackOnExpired');
40+
},
41+
'error-callback': () => {
42+
dotNetObj.invokeMethodAsync('CallbackOnError');
43+
}
44+
});
45+
resolve(widgetId);
46+
} catch (error) {
47+
reject(error);
48+
}
49+
}).catch(reject);
50+
});
51+
};
52+
53+
// Fonction utilitaire pour réinitialiser un widget
54+
reCaptchaV2.reset = function (widgetId) {
55+
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.reset !== 'undefined') {
56+
grecaptcha.reset(widgetId);
57+
}
58+
};
59+
60+
// Fonction utilitaire pour obtenir la réponse
61+
reCaptchaV2.getResponse = function (widgetId) {
62+
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.getResponse !== 'undefined') {
63+
return grecaptcha.getResponse(widgetId);
64+
}
65+
return null;
66+
};

src/IdServer/SimpleIdServer.IdServer.Email/StandardEmailAuthForms.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public static class StandardEmailAuthForms
1818
.ConfigureAuthentication(emailAuthForm)
1919
.AddErrorMessage(AuthFormErrorMessages.MaximumNumberActiveSessions, Global.MaximumNumberActiveSessions)
2020
.AddErrorMessage(AuthFormErrorMessages.MissingLogin, Global.MissingLogin)
21+
.AddErrorMessage(AuthFormErrorMessages.InvalidCaptcha, Global.CaptchaIsNotValid)
2122
.AddErrorMessage(AuthFormErrorMessages.MissingReturnUrl, Global.MissingReturnUrl)
2223
.AddErrorMessage(AuthFormErrorMessages.UserDoesntExist, Global.UserDoesntExist)
2324
.AddErrorMessage(AuthFormErrorMessages.InvalidCredential, Global.InvalidCredential)

0 commit comments

Comments
 (0)