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>();
|
||||||
|
|||||||
@@ -57,39 +57,44 @@
|
|||||||
<TL>Or with email</TL>
|
<TL>Or with email</TL>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</SmartForm>
|
</SmartForm>
|
||||||
|
|
||||||
<div class="text-gray-500 text-center fw-semibold fs-6">
|
<div class="text-gray-500 text-center fw-semibold fs-6">
|
||||||
<TL>Already registered?</TL>
|
<TL>Already registered?</TL>
|
||||||
|
|
||||||
@@ -106,13 +111,13 @@
|
|||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private UserRegisterModel UserRegisterModel = new();
|
private UserRegisterModel UserRegisterModel = new();
|
||||||
|
|
||||||
private async Task DoGoogle()
|
private async Task DoGoogle()
|
||||||
{
|
{
|
||||||
var url = await GoogleOAuth2Service.GetUrl();
|
var url = await GoogleOAuth2Service.GetUrl();
|
||||||
NavigationManager.NavigateTo(url, true);
|
NavigationManager.NavigateTo(url, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoDiscord()
|
private async Task DoDiscord()
|
||||||
{
|
{
|
||||||
var url = await DiscordOAuth2Service.GetUrl();
|
var url = await DiscordOAuth2Service.GetUrl();
|
||||||
@@ -126,7 +131,7 @@
|
|||||||
await AlertService.Error(SmartTranslateService.Translate("Passwords need to match"));
|
await AlertService.Error(SmartTranslateService.Translate("Passwords need to match"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await UserService.Register(UserRegisterModel.Email, UserRegisterModel.Password, UserRegisterModel.FirstName, UserRegisterModel.LastName);
|
var token = await UserService.Register(UserRegisterModel.Email, UserRegisterModel.Password, UserRegisterModel.FirstName, UserRegisterModel.LastName);
|
||||||
await CookieService.SetValue("token", token, 10);
|
await CookieService.SetValue("token", token, 10);
|
||||||
|
|
||||||
@@ -135,4 +140,4 @@
|
|||||||
else
|
else
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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