Merge pull request #120 from Moonlight-Panel/AddReCaptchaToRegister
Added recaptcha. Added recaptcha to register page
This commit is contained in:
18
Moonlight/App/Helpers/MustBeTrueAttribute.cs
Normal file
18
Moonlight/App/Helpers/MustBeTrueAttribute.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Logging.Net;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class MustBeTrueAttribute : ValidationAttribute
|
||||||
|
{
|
||||||
|
public override bool IsValid(object value)
|
||||||
|
{
|
||||||
|
if (value is bool boolValue)
|
||||||
|
{
|
||||||
|
return boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,31 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
namespace Moonlight.App.Models.Forms;
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
public class UserRegisterModel
|
public class UserRegisterModel
|
||||||
{
|
{
|
||||||
[Required, EmailAddress]
|
[Required(ErrorMessage = "You need to specify a email address")]
|
||||||
|
[EmailAddress(ErrorMessage = "You need to specify a valid email address")]
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|
||||||
[Required, MinLength(3)]
|
[Required(ErrorMessage = "You need to specify a first name")]
|
||||||
|
[MinLength(3, ErrorMessage = "You need to specify a valid first name")]
|
||||||
|
[MaxLength(30, ErrorMessage = "You need to specify a valid first name")]
|
||||||
public string FirstName { get; set; }
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
[Required, MinLength(3)]
|
|
||||||
|
[Required(ErrorMessage = "You need to specify a last name")]
|
||||||
|
[MinLength(3, ErrorMessage = "You need to specify a valid last name")]
|
||||||
|
[MaxLength(30, ErrorMessage = "You need to specify a valid last name")]
|
||||||
public string LastName { get; set; }
|
public string LastName { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required(ErrorMessage = "You need to specify a password")]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required(ErrorMessage = "You need to specify a password")]
|
||||||
public string ConfirmPassword { get; set; }
|
public string ConfirmPassword { get; set; }
|
||||||
|
|
||||||
|
[MustBeTrue(ErrorMessage = "Please solve the captcha")]
|
||||||
|
public bool Captcha { get; set; }
|
||||||
}
|
}
|
||||||
91
Moonlight/App/Services/Interop/ReCaptchaService.cs
Normal file
91
Moonlight/App/Services/Interop/ReCaptchaService.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Interop;
|
||||||
|
|
||||||
|
public class ReCaptchaService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime JsRuntime;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
|
|
||||||
|
private readonly string SiteKey;
|
||||||
|
private readonly string SecretKey;
|
||||||
|
private readonly bool Enable = false;
|
||||||
|
|
||||||
|
public Func<string, Task>? OnResponse { get; set; }
|
||||||
|
public Func<Task>? OnValidResponse { get; set; }
|
||||||
|
|
||||||
|
public ReCaptchaService(
|
||||||
|
IJSRuntime jsRuntime,
|
||||||
|
ConfigService configService)
|
||||||
|
{
|
||||||
|
JsRuntime = jsRuntime;
|
||||||
|
ConfigService = configService;
|
||||||
|
|
||||||
|
var recaptchaConfig = ConfigService
|
||||||
|
.GetSection("Moonlight")
|
||||||
|
.GetSection("Security")
|
||||||
|
.GetSection("ReCaptcha");
|
||||||
|
|
||||||
|
Enable = recaptchaConfig.GetValue<bool>("Enable");
|
||||||
|
|
||||||
|
if (Enable)
|
||||||
|
{
|
||||||
|
SiteKey = recaptchaConfig.GetValue<string>("SiteKey");
|
||||||
|
SecretKey = recaptchaConfig.GetValue<string>("SecretKey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> IsEnabled()
|
||||||
|
{
|
||||||
|
return Task.FromResult(Enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Create(string elementId)
|
||||||
|
{
|
||||||
|
var page = DotNetObjectReference.Create(this);
|
||||||
|
var res = await JsRuntime.InvokeAsync<object>("moonlight.recaptcha.render", elementId, SiteKey, page);
|
||||||
|
|
||||||
|
return res.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable, EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public async void CallbackOnSuccess(string res)
|
||||||
|
{
|
||||||
|
if(OnResponse != null)
|
||||||
|
await OnResponse.Invoke(res);
|
||||||
|
|
||||||
|
var b = await Validate(res);
|
||||||
|
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
if(OnValidResponse != null)
|
||||||
|
await OnValidResponse.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable, EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public void CallbackOnExpired()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> Validate(string res)
|
||||||
|
{
|
||||||
|
var url = "https://www.google.com/recaptcha/api/siteverify";
|
||||||
|
|
||||||
|
var client = new RestClient();
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
request.AddParameter("secret", SecretKey);
|
||||||
|
request.AddParameter("response", res);
|
||||||
|
|
||||||
|
var resp = await client.PostAsync(request);
|
||||||
|
|
||||||
|
var data = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(resp.Content))
|
||||||
|
).Build();
|
||||||
|
return data.GetValue<bool>("success");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,6 +117,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<FabricService>();
|
builder.Services.AddScoped<FabricService>();
|
||||||
builder.Services.AddSingleton<BucketService>();
|
builder.Services.AddSingleton<BucketService>();
|
||||||
builder.Services.AddScoped<RatingService>();
|
builder.Services.AddScoped<RatingService>();
|
||||||
|
builder.Services.AddScoped<ReCaptchaService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||||
|
|||||||
@@ -60,29 +60,34 @@
|
|||||||
|
|
||||||
<SmartForm Model="UserRegisterModel" OnValidSubmit="CreateUser">
|
<SmartForm Model="UserRegisterModel" OnValidSubmit="CreateUser">
|
||||||
<div class="fv-row mb-4 fv-plugins-icon-container">
|
<div class="fv-row mb-4 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="UserRegisterModel.Email" placeholder="@(SmartTranslateService.Translate("Email"))" name="email" autocomplete="off" class="form-control bg-transparent" />
|
<InputText @bind-Value="UserRegisterModel.Email" placeholder="@(SmartTranslateService.Translate("Email"))" name="email" autocomplete="off" class="form-control bg-transparent"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="UserRegisterModel.FirstName" type="text" placeholder="@(SmartTranslateService.Translate("Firstname"))" name="text" class="form-control bg-transparent" />
|
<InputText @bind-Value="UserRegisterModel.FirstName" type="text" placeholder="@(SmartTranslateService.Translate("Firstname"))" name="text" class="form-control bg-transparent"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="UserRegisterModel.LastName" type="text" placeholder="@(SmartTranslateService.Translate("Lastname"))" name="text"class="form-control bg-transparent" />
|
<InputText @bind-Value="UserRegisterModel.LastName" type="text" placeholder="@(SmartTranslateService.Translate("Lastname"))" name="text"class="form-control bg-transparent"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="UserRegisterModel.Password" type="password" placeholder="@(SmartTranslateService.Translate("Password"))" name="password" autocomplete="off" class="form-control bg-transparent" />
|
<InputText @bind-Value="UserRegisterModel.Password" type="password" placeholder="@(SmartTranslateService.Translate("Password"))" name="password" autocomplete="off" class="form-control bg-transparent"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="UserRegisterModel.ConfirmPassword" type="password" placeholder="@(SmartTranslateService.Translate("Repeat password"))" name="password" autocomplete="off" class="form-control bg-transparent" />
|
<InputText @bind-Value="UserRegisterModel.ConfirmPassword" type="password" placeholder="@(SmartTranslateService.Translate("Repeat password"))" name="password" autocomplete="off" class="form-control bg-transparent"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3 mb-3">
|
||||||
|
<SmartReCaptcha @bind-Value="UserRegisterModel.Captcha">
|
||||||
|
</SmartReCaptcha>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-grid mb-6">
|
<div class="d-grid mb-6">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<TL>Sign-up</TL>
|
<TL>Sign-up</TL>
|
||||||
|
|||||||
49
Moonlight/Shared/Components/Forms/SmartReCaptcha.razor
Normal file
49
Moonlight/Shared/Components/Forms/SmartReCaptcha.razor
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Logging.Net
|
||||||
|
|
||||||
|
@inject ReCaptchaService ReCaptchaService
|
||||||
|
|
||||||
|
@inherits InputBase<bool>
|
||||||
|
|
||||||
|
<div class="d-flex flex-center" id="@UniqueId"></div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private string UniqueId = Guid.NewGuid().ToString();
|
||||||
|
private string Id;
|
||||||
|
|
||||||
|
private async Task OnValid()
|
||||||
|
{
|
||||||
|
CurrentValue = true;
|
||||||
|
await ValueChanged.InvokeAsync(CurrentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
ReCaptchaService.OnValidResponse += OnValid;
|
||||||
|
|
||||||
|
if(await ReCaptchaService.IsEnabled())
|
||||||
|
Id = await ReCaptchaService.Create(UniqueId);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await OnValid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TryParseValueFromString(string? value, out bool result, out string? validationErrorMessage)
|
||||||
|
{
|
||||||
|
if (bool.TryParse(value, out result))
|
||||||
|
{
|
||||||
|
validationErrorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
validationErrorMessage = "Invalid value.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user