diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Sphere/Auth/AuthController.cs index 26f7b2c..7323055 100644 --- a/DysonNetwork.Sphere/Auth/AuthController.cs +++ b/DysonNetwork.Sphere/Auth/AuthController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using NodaTime; using Microsoft.EntityFrameworkCore; using System.IdentityModel.Tokens.Jwt; +using System.Text.Json; namespace DysonNetwork.Sphere.Auth; @@ -14,7 +15,8 @@ public class AuthController( AppDatabase db, AccountService accounts, AuthService auth, - IHttpContextAccessor httpContext + IConfiguration configuration, + IHttpClientFactory httpClientFactory ) : ControllerBase { public class ChallengeRequest @@ -31,8 +33,8 @@ public class AuthController( var account = await accounts.LookupAccount(request.Account); if (account is null) return NotFound("Account was not found."); - var ipAddress = httpContext.HttpContext?.Connection.RemoteIpAddress?.ToString(); - var userAgent = httpContext.HttpContext?.Request.Headers.UserAgent.ToString(); + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); + var userAgent = HttpContext.Request.Headers.UserAgent.ToString(); var now = Instant.FromDateTimeUtc(DateTime.UtcNow); @@ -186,7 +188,7 @@ public class AuthController( [HttpGet("test")] public async Task Test() { - var sessionIdClaim = httpContext.HttpContext?.User.FindFirst("session_id")?.Value; + var sessionIdClaim = HttpContext.User.FindFirst("session_id")?.Value; if (!Guid.TryParse(sessionIdClaim, out var sessionId)) return Unauthorized(); @@ -195,4 +197,53 @@ public class AuthController( return Ok(session); } + + [HttpPost("captcha")] + public async Task ValidateCaptcha([FromBody] string token) + { + var provider = configuration.GetSection("Captcha")["Provider"]?.ToLower(); + var apiKey = configuration.GetSection("Captcha")["ApiKey"]; + var apiSecret = configuration.GetSection("Captcha")["ApiSecret"]; + + var client = httpClientFactory.CreateClient(); + + switch (provider) + { + case "cloudflare": + var content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + "application/x-www-form-urlencoded"); + var response = await client.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", + content); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var cfResult = JsonSerializer.Deserialize(json); + + if (cfResult?.Success == true) + return Ok(new { success = true }); + + return BadRequest(new { success = false, errors = cfResult?.ErrorCodes }); + case "google": + var secretKey = configuration.GetSection("CaptchaSettings")["GoogleRecaptchaSecretKey"]; + if (string.IsNullOrEmpty(secretKey)) + { + return StatusCode(500, "Google reCaptcha secret key is not configured."); + } + + content = new StringContent($"secret={secretKey}&response={token}", System.Text.Encoding.UTF8, + "application/x-www-form-urlencoded"); + response = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify", content); + response.EnsureSuccessStatusCode(); + + json = await response.Content.ReadAsStringAsync(); + var capResult = JsonSerializer.Deserialize(json); + + if (capResult?.Success == true) + return Ok(new { success = true }); + + return BadRequest(new { success = false, errors = capResult?.ErrorCodes }); + default: + return StatusCode(500, "The server misconfigured for the captcha."); + } + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/CheckpointModel.cs b/DysonNetwork.Sphere/Auth/CheckpointModel.cs new file mode 100644 index 0000000..bb74632 --- /dev/null +++ b/DysonNetwork.Sphere/Auth/CheckpointModel.cs @@ -0,0 +1,17 @@ +namespace DysonNetwork.Sphere.Auth; + +public class CloudflareVerificationResponse +{ + public bool Success { get; set; } + public string[]? ErrorCodes { get; set; } +} + +public class GoogleVerificationResponse +{ + public bool Success { get; set; } + public float Score { get; set; } + public string Action { get; set; } + public DateTime ChallengeTs { get; set; } + public string Hostname { get; set; } + public string[]? ErrorCodes { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/CheckpointPage.cshtml b/DysonNetwork.Sphere/Pages/CheckpointPage.cshtml new file mode 100644 index 0000000..2fcf464 --- /dev/null +++ b/DysonNetwork.Sphere/Pages/CheckpointPage.cshtml @@ -0,0 +1,149 @@ +@page "/auth/captcha" +@model DysonNetwork.Sphere.Pages.CheckpointPage + +@{ + Layout = null; + + var cfg = ViewData.Model.Configuration; + var provider = cfg.GetSection("Captcha")["Provider"]?.ToLower(); + var apiKey = cfg.GetSection("Captcha")["ApiKey"]; +} + + + + + + + Solar Network Captcha + + + @switch (provider) + { + case "recaptcha": + + break; + case "cloudflare": + + break; + } + + +
+
+

reCaptcha

+ @switch (provider) + { + case "cloudflare": +
+ break; + case "recaptcha": +
+ break; + default: +

Captcha provider not configured correctly.

+ break; + } +
+ +
+ + + \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/CheckpointPage.cshtml.cs b/DysonNetwork.Sphere/Pages/CheckpointPage.cshtml.cs new file mode 100644 index 0000000..b467290 --- /dev/null +++ b/DysonNetwork.Sphere/Pages/CheckpointPage.cshtml.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace DysonNetwork.Sphere.Pages; + +public class CheckpointPage(IConfiguration configuration) : PageModel +{ + [BindProperty] public IConfiguration Configuration { get; set; } = configuration; + + public void OnGet() + { + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 70298f0..24d5e10 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -40,7 +40,7 @@ builder.Services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); }); -builder.Services.AddHttpContextAccessor(); +builder.Services.AddRazorPages(); // Casbin permissions @@ -155,7 +155,7 @@ using (var scope = app.Services.CreateScope()) db.Database.Migrate(); } -if (app.Environment.IsDevelopment()) app.MapOpenApi(); +app.MapOpenApi(); app.UseSwagger(); app.UseSwaggerUI(); @@ -179,6 +179,8 @@ app.UseAuthorization(); app.UseMiddleware(); app.MapControllers(); +app.MapStaticAssets(); +app.MapRazorPages(); var tusDiskStore = new tusdotnet.Stores.TusDiskStore( builder.Configuration.GetSection("Tus").GetValue("StorePath")! diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 3c78ba5..b4eaf04 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -44,5 +44,10 @@ "EnableSsl": true } ] + }, + "Captcha": { + "Provider": "recaptcha", + "ApiKey": "6LfIzSArAAAAAN413MtycDcPlKa636knBSAhbzj-", + "ApiSecret": "" } }