Changes
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
using Api.Services.Contracts.Models;
|
||||
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
|
||||
{
|
||||
@@ -29,7 +29,7 @@ namespace Api.Controllers
|
||||
/// Returns the public reCAPTCHA site key used by the client to render the widget.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[SwaggerOperation(Summary = "Get reCAPTCHA site key")]
|
||||
[SwaggerOperation(Summary = "Get captcha site key")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public IActionResult GetSiteKey()
|
||||
{
|
||||
@@ -43,7 +43,7 @@ namespace Api.Controllers
|
||||
[SwaggerOperation(Summary = "Verify captcha token")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> Verify([FromBody] VerifyRequest req, CancellationToken ct)
|
||||
public async Task<IActionResult> Verify([FromBody] CaptchaVerifyRequest req, CancellationToken ct)
|
||||
{
|
||||
if (req is null || string.IsNullOrWhiteSpace(req.Token)) return BadRequest(new { error = "Missing token" });
|
||||
|
||||
@@ -57,10 +57,5 @@ namespace Api.Controllers
|
||||
|
||||
return Ok(verdict);
|
||||
}
|
||||
|
||||
public sealed class VerifyRequest
|
||||
{
|
||||
public string? Token { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,26 +19,16 @@ namespace Api.Controllers
|
||||
[EnableCors("FrontendOnly")]
|
||||
public sealed class ContactController : ControllerBase
|
||||
{
|
||||
private readonly CaptchaSettings _captchaSettings;
|
||||
private readonly ICaptchaVerifier _captcha;
|
||||
private readonly IEmailSender _email;
|
||||
private readonly ILogger<ContactController> _log;
|
||||
|
||||
public ContactController(IOptions<CaptchaSettings> options, ICaptchaVerifier captcha, IEmailSender email, ILogger<ContactController> log)
|
||||
public ContactController(ICaptchaVerifier captcha, IEmailSender email, ILogger<ContactController> log)
|
||||
{
|
||||
_captchaSettings = options.Value;
|
||||
_captcha = captcha;
|
||||
_email = email;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the public reCAPTCHA site key used by the client to render
|
||||
/// the reCAPTCHA widget and obtain client-side tokens.
|
||||
/// </summary>
|
||||
/// <returns>200 OK with the public site key as a string.</returns>
|
||||
// ReCaptcha endpoints have been extracted to CaptchaController
|
||||
|
||||
/// <summary>
|
||||
/// Validates the provided reCAPTCHA token and sends a contact message
|
||||
/// via the configured email sender.
|
||||
@@ -107,7 +97,5 @@ namespace Api.Controllers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Captcha verification helper was moved to CaptchaController; ContactController calls _captcha.VerifyAsync directly.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Api.Clients.Api.Contracts;
|
||||
using Api.Models.Requests;
|
||||
using Api.Services.Contracts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
@@ -16,23 +17,25 @@ public sealed class CvMatcherController : ControllerBase
|
||||
{
|
||||
private readonly ICvMatcherApi _cvApi;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ICaptchaVerifier _captcha;
|
||||
private readonly ILogger<CvMatcherController> _logger;
|
||||
|
||||
public CvMatcherController(
|
||||
ICvMatcherApi cvApi,
|
||||
IConfiguration configuration,
|
||||
ICaptchaVerifier captcha,
|
||||
ILogger<CvMatcherController> logger)
|
||||
{
|
||||
_cvApi = cvApi;
|
||||
_configuration = configuration;
|
||||
_captcha = captcha;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upload a CV PDF to the cv-matcher-api.
|
||||
/// </summary>
|
||||
/// <param name="cv">The uploaded CV PDF file.</param>
|
||||
/// <param name="gdprConsent">Whether the user consented to GDPR processing.</param>
|
||||
/// <param name="request">The uploaded CV request.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
[HttpPost("upload")]
|
||||
[Consumes("multipart/form-data")]
|
||||
@@ -61,6 +64,14 @@ public sealed class CvMatcherController : ControllerBase
|
||||
_logger.LogInformation("Proxying CV upload to cv-matcher-api. FileName={FileName}, Size={SizeBytes}, GdprConsent={GdprConsent}",
|
||||
cv.FileName, cv.Length, gdprConsent);
|
||||
|
||||
var userIp = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
var verdict = await _captcha.VerifyAsync(request.CaptchaToken ?? string.Empty, userIp, ct);
|
||||
if (!verdict.Success)
|
||||
{
|
||||
_logger.LogWarning("Captcha verification failed for CV upload. IP={IP}", userIp);
|
||||
return BadRequest(new { error = "Captcha verification failed." });
|
||||
}
|
||||
|
||||
var stream = cv.OpenReadStream();
|
||||
var part = new Refit.StreamPart(stream, cv.FileName, "application/pdf");
|
||||
using var response = await _cvApi.Upload(part, gdprConsent);
|
||||
@@ -100,6 +111,14 @@ public sealed class CvMatcherController : ControllerBase
|
||||
!string.IsNullOrWhiteSpace(request.JobUrl),
|
||||
!string.IsNullOrWhiteSpace(request.JobDescription));
|
||||
|
||||
var userIp = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
var verdict = await _captcha.VerifyAsync(request.CaptchaToken ?? string.Empty, userIp, ct);
|
||||
if (!verdict.Success)
|
||||
{
|
||||
_logger.LogWarning("Captcha verification failed for job match. IP={IP}", userIp);
|
||||
return BadRequest(new { error = "Captcha verification failed." });
|
||||
}
|
||||
|
||||
using var response = await _cvApi.MatchJob(request);
|
||||
return await ProxyResponseAsync(response, ct);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user