From bee37a65f32ec36bc68dcf90260992daace7e937 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Mon, 9 Mar 2026 15:05:36 +0100 Subject: [PATCH] do not use powcapserver.aspnetcore package; use api controller for captcha endpoints --- zero/ApplicationBuilderExtensions.cs | 7 +++ zero/Security/CaptchaController.cs | 84 ++++++++++++++++++++++++++++ zero/Security/CaptchaTagHelper.cs | 2 +- zero/Security/ZeroSecurityModule.cs | 70 ++++++----------------- zero/zero.csproj | 2 +- 5 files changed, 110 insertions(+), 55 deletions(-) create mode 100644 zero/Security/CaptchaController.cs diff --git a/zero/ApplicationBuilderExtensions.cs b/zero/ApplicationBuilderExtensions.cs index 0a4e5b1d..edb9ef3a 100644 --- a/zero/ApplicationBuilderExtensions.cs +++ b/zero/ApplicationBuilderExtensions.cs @@ -8,6 +8,13 @@ public static class ApplicationBuilderExtensions { app.UseMiddleware(); app.UseOutputCache(); + + if (app is WebApplication webApplication) + { + webApplication.MapRazorPages(); + webApplication.MapControllers(); + } + ZeroBuilder.Modules.Configure(app, null, app.ApplicationServices); return app; } diff --git a/zero/Security/CaptchaController.cs b/zero/Security/CaptchaController.cs new file mode 100644 index 00000000..8b96f384 --- /dev/null +++ b/zero/Security/CaptchaController.cs @@ -0,0 +1,84 @@ +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using PowCapServer.Abstractions; +using PowCapServer.Models; +using zero.Mvc; + +namespace zero.Security; + +[ApiController] +[Route("/api/captcha")] +public class CaptchaController(ICaptchaService captchaService) : ZeroController +{ + [HttpPost("challenge")] + public async Task PostChallenge() + { + ChallengeTokenInfo challengeTokenInfo = await captchaService.CreateChallengeAsync(HttpContext.RequestAborted); + return Json(challengeTokenInfo); + } + + + [HttpPost("{useCase}/challenge")] + public async Task PostChallengeWithUseCase(string useCase) + { + ChallengeTokenInfo challengeTokenInfo = await captchaService.CreateChallengeAsync(useCase, HttpContext.RequestAborted); + return Json(challengeTokenInfo); + } + + + [HttpPost("redeem")] + public async Task PostRedeem(ChallengeSolution model) + { + if (model == null) + { + return BadRequest(); + } + + RedeemChallengeResult result = await captchaService.RedeemChallengeAsync(model).ConfigureAwait(false); + return Json(result); + } + + + [HttpPost("{useCase}/redeem")] + public async Task PostRedeemWithUseCase(string useCase, ChallengeSolution model) + { + if (model == null) + { + return BadRequest(); + } + + RedeemChallengeResult result = await captchaService.RedeemChallengeAsync(useCase, model).ConfigureAwait(false); + return Json(result); + } + + + [HttpGet("cap.wasm")] + public IActionResult GetWasmFile() + { + Assembly assembly = typeof(ZeroSecurityModule).GetTypeInfo().Assembly; + Stream resource = assembly.GetManifestResourceStream("zero.Resources.cap_wasm_bg_0_0_6.wasm"); + + if (resource is null) + { + return NotFound(); + } + + return File(resource, "application/wasm"); + } + + + [HttpGet("cap.widget.js")] + public IActionResult GetWidgetJsFile() + { + Assembly assembly = typeof(ZeroSecurityModule).GetTypeInfo().Assembly; + Stream resource = assembly.GetManifestResourceStream("zero.Resources.cap_min_0_1_41.js"); + + if (resource is null) + { + return NotFound(); + } + + return File(resource, "text/javascript"); + } +} \ No newline at end of file diff --git a/zero/Security/CaptchaTagHelper.cs b/zero/Security/CaptchaTagHelper.cs index 7961c618..c99d609c 100644 --- a/zero/Security/CaptchaTagHelper.cs +++ b/zero/Security/CaptchaTagHelper.cs @@ -8,7 +8,7 @@ using zero.Security; namespace zero.TagHelpers; -[HtmlTargetElement("app-captcha", Attributes = "for,lang", TagStructure = TagStructure.NormalOrSelfClosing)] +[HtmlTargetElement("app-captcha", Attributes = "for", TagStructure = TagStructure.NormalOrSelfClosing)] public class CaptchaTagHelper(IOptionsMonitor options) : TagHelper { [HtmlAttributeNotBound] diff --git a/zero/Security/ZeroSecurityModule.cs b/zero/Security/ZeroSecurityModule.cs index 08d2e7f6..0ff9968b 100644 --- a/zero/Security/ZeroSecurityModule.cs +++ b/zero/Security/ZeroSecurityModule.cs @@ -1,11 +1,10 @@ -using System.IO; -using System.Reflection; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection.Extensions; +using PowCapServer; +using PowCapServer.Abstractions; namespace zero.Security; @@ -14,60 +13,25 @@ internal class ZeroSecurityModule : ZeroModule public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) { IConfigurationSection captchaSection = configuration.GetSection("Zero:Captcha"); - services.AddOptions().Bind(captchaSection); - services.AddPowCapServer(options => - { - CaptchaOptions captchaOptions = captchaSection.Get() ?? new CaptchaOptions(); - options.Default = captchaOptions; - }); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddTransient(); + services.Configure(opts => opts.Default = captchaSection.Get() ?? new CaptchaOptions()); + services.AddOptions().Bind(captchaSection); } public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) { - IOptionsMonitor options = serviceProvider.GetRequiredService>(); - string endpointPrefix = options.CurrentValue.Endpoint; + // if (app is WebApplication webApp) + // { + // webApp.MapGet("/teamup/sync", async (ISchedulerFactory scheduler) => + // { + // await SyncTeamUpDataJob.RunNow(scheduler); + // return "done"; + // }); + // } - app.UseRouting(); - app.MapPowCapServer(endpointPrefix); - app.UseEndpoints(endpoints => - { - endpoints.MapGet(endpointPrefix + "/cap.wasm", async context => - { - Assembly assembly = typeof(ZeroSecurityModule).GetTypeInfo().Assembly; - Stream resource = assembly.GetManifestResourceStream("zero.Resources.cap_wasm_bg_0_0_6.wasm"); - - if (resource is null) - { - context.Response.StatusCode = StatusCodes.Status404NotFound; - return; - } - - await using (resource) - { - context.Response.ContentType = "application/wasm"; - await resource.CopyToAsync(context.Response.Body, context.RequestAborted); - } - }); - - endpoints.MapGet(endpointPrefix + "/cap.widget.js", async context => - { - Assembly assembly = typeof(ZeroSecurityModule).GetTypeInfo().Assembly; - Stream resource = assembly.GetManifestResourceStream("zero.Resources.cap_min_0_1_41.js"); - - if (resource is null) - { - context.Response.StatusCode = StatusCodes.Status404NotFound; - return; - } - - await using (resource) - { - context.Response.ContentType = "text/javascript"; - await resource.CopyToAsync(context.Response.Body, context.RequestAborted); - } - }); - }); StaticCaptchaService.Configure(serviceProvider); } } \ No newline at end of file diff --git a/zero/zero.csproj b/zero/zero.csproj index 10ad53c5..44500d41 100644 --- a/zero/zero.csproj +++ b/zero/zero.csproj @@ -17,7 +17,7 @@ - +