using Api.Services.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Api.Models.Settings; using Swashbuckle.AspNetCore.Annotations; using Api.Models.Requests; namespace Api.Controllers { /// /// Endpoints that expose captcha configuration and verification. /// [ApiController] [Route("api/[controller]")] public sealed class CaptchaController : ControllerBase { private readonly CaptchaSettings _captchaSettings; private readonly ICaptchaVerifier _captcha; private readonly ILogger _log; public CaptchaController(IOptions options, ICaptchaVerifier captcha, ILogger log) { _captchaSettings = options.Value; _captcha = captcha; _log = log; } /// /// Returns the public reCAPTCHA site key used by the client to render the widget. /// [HttpGet] [SwaggerOperation(Summary = "Get captcha site key")] [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult GetSiteKey() { return Ok(_captchaSettings.PublicKey); } /// /// Verify a captcha token and return the verification verdict. /// [HttpPost("verify")] [SwaggerOperation(Summary = "Verify captcha token")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task Verify([FromBody] CaptchaVerifyRequest req, CancellationToken ct) { if (req is null || string.IsNullOrWhiteSpace(req.Token)) return BadRequest(new { error = "Missing token" }); var userIp = HttpContext.Connection.RemoteIpAddress?.ToString(); var verdict = await _captcha.VerifyAsync(req.Token, userIp, req.ExpectedAction, ct); if (!verdict.Success) { _log.LogWarning("Captcha failed. ip={Ip} score={Score} err={Err}", userIp, verdict.Score, verdict.Error); return BadRequest(new { error = "Captcha verification failed.", score = verdict.Score }); } return Ok(verdict); } } }