Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/captcha #46

Merged
merged 3 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
480 changes: 10 additions & 470 deletions .editorconfig

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions src/Argon.Api/Features/Captcha/CaptchaFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Argon.Api.Features.Captcha;

using Microsoft.Extensions.Configuration;

public static class CaptchaFeature
{
public static IServiceCollection AddCaptchaFeature(this WebApplicationBuilder builder)
{
var cfg = builder.Configuration.GetSection("Captcha");
builder.Services.Configure<CaptchaOptions>(cfg);
var kind = cfg.GetValue<CaptchaKind>("Kind");

return kind switch
{
CaptchaKind.NO_CAPTCHA => builder.Services.AddTransient<ICaptchaFeature, NullCaptcha>(),
CaptchaKind.CLOUDFLARE => builder.Services.AddTransient<ICaptchaFeature, CloudflareCaptcha>(),
CaptchaKind.YANDEX => builder.Services.AddTransient<ICaptchaFeature, YandexCaptcha>(),
_ => throw new ArgumentOutOfRangeException()
};
}
}
8 changes: 8 additions & 0 deletions src/Argon.Api/Features/Captcha/CaptchaKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Argon.Api.Features.Captcha;

public enum CaptchaKind
{
NO_CAPTCHA,
CLOUDFLARE,
YANDEX
}
9 changes: 9 additions & 0 deletions src/Argon.Api/Features/Captcha/CaptchaOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Argon.Api.Features.Captcha;

public class CaptchaOptions
{
public string SiteKey { get; set; }
public string SiteSecret { get; set; }
public string ChallengeEndpoint { get; set; }
public CaptchaKind Kind { get; set; }
}
41 changes: 41 additions & 0 deletions src/Argon.Api/Features/Captcha/CloudflareCaptcha.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Argon.Api.Features.Captcha;

using Extensions;
using Flurl.Http;
using Microsoft.Extensions.Options;

public class CloudflareCaptcha(HttpContext httpContext, ILogger<ICaptchaFeature> logger, IOptions<CaptchaOptions> options) : ICaptchaFeature
{
public async ValueTask<bool> ValidateAsync(string token)
{
if (string.IsNullOrEmpty(token))
return false;
var config = options.Value;
var remoteIp = httpContext.GetIpAddress();
try
{
var response = await config.ChallengeEndpoint
.PostMultipartAsync(content => content
.AddString("secret", config.SiteSecret)
.AddString("response", token)
.AddString("remoteip", remoteIp))
.ReceiveJson<CloudflareTurnstileResponse>();
logger.LogInformation("Success validate captcha token {Challenge_ts} {Hostname}", response.Challenge_ts, response.Hostname);
return response?.Success ?? false;
}
catch (Exception ex)
{
logger.LogCritical(ex, "failed validate captcha token");
return false;
}
}

public class CloudflareTurnstileResponse
{
public bool Success { get; set; }
public string Challenge_ts { get; set; }
public string Hostname { get; set; }
public string[] ErrorCodes { get; set; }
}

}
6 changes: 6 additions & 0 deletions src/Argon.Api/Features/Captcha/ICaptchaFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Argon.Api.Features.Captcha;

public interface ICaptchaFeature
{
ValueTask<bool> ValidateAsync(string token);
}
6 changes: 6 additions & 0 deletions src/Argon.Api/Features/Captcha/NullCaptcha.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Argon.Api.Features.Captcha;

public class NullCaptcha : ICaptchaFeature
{
public ValueTask<bool> ValidateAsync(string token) => new(true);
}
6 changes: 6 additions & 0 deletions src/Argon.Api/Features/Captcha/YandexCaptcha.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Argon.Api.Features.Captcha;

public class YandexCaptcha : ICaptchaFeature
{
public ValueTask<bool> ValidateAsync(string token) => throw new NotImplementedException();
}
2 changes: 2 additions & 0 deletions src/Argon.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Argon.Api.Entities;
using Argon.Api.Extensions;
using Argon.Api.Features;
using Argon.Api.Features.Captcha;
using Argon.Api.Features.EmailForms;
using Argon.Api.Features.Env;
using Argon.Api.Features.Jwt;
Expand Down Expand Up @@ -43,6 +44,7 @@
builder.AddOrleans();
builder.AddEMailForms();
builder.AddKubeResources();
builder.AddCaptchaFeature();
builder.Services.AddDataProtection();
builder.Services.AddAutoMapper(typeof(User).Assembly); // TODO
var app = builder.Build();
Expand Down
6 changes: 6 additions & 0 deletions src/Argon.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,11 @@
"Orleans": {
"ClusterId": "argonapi",
"ServiceId": "argonapi"
},
"Captcha": {
"SiteKey": "",
"SiteSecret": "",
"ChallengeEndpoint": "",
"Kind": "NO_CAPTCHA"
}
}