using Api.Services.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Models.Settings; using Swashbuckle.AspNetCore.Annotations; using Models.Requests; using Shared.Models.Responses; 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(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [SwaggerResponse(StatusCodes.Status400BadRequest, "Captcha verification failed or token missing", typeof(ErrorResponse))] public async Task Verify([FromBody] CaptchaVerifyRequest req, CancellationToken ct) { if (req is null || string.IsNullOrWhiteSpace(req.Token)) { return BadRequest(new ErrorResponse { Error = "Missing token", Code = "captcha_token_missing" }); } 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 ErrorResponse { Error = "Captcha verification failed.", Code = "captcha_verification_failed", Score = verdict.Score }); } return Ok(verdict); } } }