using Api.Services.Contracts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Models.Settings;
using Swashbuckle.AspNetCore.Annotations;
using Models.Requests;
using Common.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.
///
/// 200 OK with the configured public site key as a plain string.
[HttpGet]
[SwaggerOperation(Summary = "Get captcha public key", Description = "Returns the public reCAPTCHA site key required by the frontend to render the challenge widget.")]
[SwaggerResponse(StatusCodes.Status200OK, "Public site key returned")]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult GetSiteKey()
{
return Ok(_captchaSettings.PublicKey);
}
///
/// Verifies a reCAPTCHA token submitted by the client and returns the full verification verdict.
///
/// The verification request containing the token and optional expected action name.
/// Cancellation token.
///
/// 200 OK with the full captcha verdict when verification passes;
/// 400 Bad Request with an if the token is missing or verification fails.
///
[HttpPost("verify")]
[SwaggerOperation(Summary = "Verify captcha token", Description = "Verifies a reCAPTCHA token and returns the provider verdict including the score.")]
[SwaggerResponse(StatusCodes.Status200OK, "Token verified successfully")]
[SwaggerResponse(StatusCodes.Status400BadRequest, "Token missing or verification failed", typeof(ErrorResponse))]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
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);
}
}
}