quick fix

This commit is contained in:
Sebastian 2026-02-19 22:19:49 +01:00
parent 9d9c9b3f38
commit a0d98b4d77
8 changed files with 72 additions and 64 deletions

7
.gitignore vendored
View file

@ -6,9 +6,10 @@
# dotenv files # dotenv files
.env .env
*www.api.vbytes.se.key *.key
*www.api.vbytes.se.csr *.csr
*www.api.vbytes.se.crt *.crt
*.pfx
# User-specific files # User-specific files
*.rsuser *.rsuser

View file

@ -2,12 +2,12 @@ using Microsoft.AspNetCore.Mvc;
using Registration.API.Services; using Registration.API.Services;
using Registration.Domain.Models; using Registration.Domain.Models;
namespace Registration.API.Controllers namespace Registration.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ParticipantController(IVbytesParticipantRelayService relayService) : ControllerBase
{ {
[Route("api/[controller]")]
[ApiController]
public class ParticipantController(IVbytesParticipantRelayService relayService) : ControllerBase
{
private readonly IVbytesParticipantRelayService _relayService = relayService; private readonly IVbytesParticipantRelayService _relayService = relayService;
[HttpPost("register")] [HttpPost("register")]
@ -15,12 +15,11 @@ namespace Registration.API.Controllers
{ {
var result = await _relayService.RegisterParticipantAsync(participant, cancellationToken); var result = await _relayService.RegisterParticipantAsync(participant, cancellationToken);
if (result.Success) if (result.StatusCode == 200 && !result.Message.Contains("401"))
{ {
return Ok(); return Ok();
} }
return StatusCode(result.StatusCode, new { message = result.Message }); return Unauthorized();
}
} }
} }

View file

@ -12,16 +12,42 @@ builder.Services.AddScoped<IMemberRepository, MemberRepository>();
var relayOptions = builder.Configuration.GetSection("VbytesRelay").Get<VbytesRelayOptions>() var relayOptions = builder.Configuration.GetSection("VbytesRelay").Get<VbytesRelayOptions>()
?? throw new InvalidOperationException("VbytesRelay configuration section is missing."); ?? throw new InvalidOperationException("VbytesRelay configuration section is missing.");
builder.Services.Configure<VbytesRelayOptions>(builder.Configuration.GetSection("VbytesRelay"));
var certificate = X509CertificateLoader.LoadPkcs12FromFile( var certificate = X509CertificateLoader.LoadPkcs12FromFile(
relayOptions.ClientCertificatePfxPath, password: null); relayOptions.ClientCertificatePfxPath, password: null);
builder.Services.AddHttpClient(VbytesParticipantRelayService.HttpClientName, client => builder.Services.AddSingleton(sp =>
{ {
client.BaseAddress = new Uri(relayOptions.BaseUrl); var handler = new HttpClientHandler();
client.Timeout = TimeSpan.FromSeconds(30); handler.ClientCertificates.Add(certificate);
return handler;
});
var authApiOptions = builder.Configuration.GetSection("AuthApi").Get<AuthApiOptions>()
?? throw new InvalidOperationException("AuthApi configuration section is missing.");
builder.Services.Configure<VbytesRelayOptions>(builder.Configuration.GetSection("VbytesRelay"));
builder.Services.AddHttpClient<IVbytesParticipantRelayService, VbytesParticipantRelayService>(o =>
{
o.BaseAddress = new Uri(relayOptions.BaseUrl) ??
throw new InvalidOperationException("BaseUrl is missing in VbytesRelay configuration.");
o.Timeout = TimeSpan.FromSeconds(30);
})
.ConfigurePrimaryHttpMessageHandler(sp => sp.GetRequiredService<HttpClientHandler>());
builder.Services.AddHttpClient<IVbytesVolunteerRelayService, VbytesVolunteerRelayService>(o =>
{
o.BaseAddress = new Uri(relayOptions.BaseUrl);
o.Timeout = TimeSpan.FromSeconds(30);
})
.ConfigurePrimaryHttpMessageHandler(sp => sp.GetRequiredService<HttpClientHandler>());
builder.Services.Configure<AuthApiOptions>(builder.Configuration.GetSection("AuthApi"));
builder.Services.AddHttpClient<IAuthService, AuthService>(o =>
{
o.BaseAddress = new Uri(authApiOptions.BaseUrl);
o.Timeout = TimeSpan.FromSeconds(10);
}) })
.ConfigurePrimaryHttpMessageHandler(() => .ConfigurePrimaryHttpMessageHandler(() =>
{ {
@ -30,20 +56,6 @@ builder.Services.AddHttpClient(VbytesParticipantRelayService.HttpClientName, cli
return handler; return handler;
}); });
builder.Services.AddScoped<IVbytesParticipantRelayService, VbytesParticipantRelayService>();
builder.Services.AddScoped<IVbytesVolunteerRelayService, VbytesVolunteerRelayService>();
var authApiOptions = builder.Configuration.GetSection("AuthApi").Get<AuthApiOptions>()
?? throw new InvalidOperationException("AuthApi configuration section is missing.");
builder.Services.Configure<AuthApiOptions>(builder.Configuration.GetSection("AuthApi"));
builder.Services.AddHttpClient(AuthService.HttpClientName, client =>
{
client.BaseAddress = new Uri(authApiOptions.BaseUrl);
client.Timeout = TimeSpan.FromSeconds(10);
});
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddControllers(); builder.Services.AddControllers();
var app = builder.Build(); var app = builder.Build();

View file

@ -3,20 +3,18 @@ using Registration.API.Configuration;
namespace Registration.API.Services; namespace Registration.API.Services;
public class AuthService(IHttpClientFactory httpClientFactory, IOptions<AuthApiOptions> options) : IAuthService public class AuthService(HttpClient httpClient, IOptions<AuthApiOptions> options) : IAuthService
{ {
public const string HttpClientName = "AuthApi"; public const string HttpClientName = "AuthApi";
private readonly HttpClient _httpClient = httpClient;
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
private readonly AuthApiOptions _options = options.Value; private readonly AuthApiOptions _options = options.Value;
public async Task<bool> IsMemberAsync(string ssn, CancellationToken cancellationToken = default) public async Task<bool> IsMemberAsync(string ssn, CancellationToken cancellationToken = default)
{ {
var client = _httpClientFactory.CreateClient(HttpClientName); var response = await _httpClient.PostAsJsonAsync(
var response = await client.PostAsJsonAsync(
_options.ValidatePath, _options.ValidatePath,
new { ssn }, new { ssn },
cancellationToken); cancellationToken);
return response.IsSuccessStatusCode; return (int)response.StatusCode == StatusCodes.Status200OK;
} }
} }

View file

@ -6,10 +6,10 @@ using Registration.Domain.Models;
namespace Registration.API.Services; namespace Registration.API.Services;
public class VbytesParticipantRelayService( public class VbytesParticipantRelayService(
IHttpClientFactory httpClientFactory, HttpClient httpClient,
IOptions<VbytesRelayOptions> options, IOptions<VbytesRelayOptions> options,
ILogger<VbytesParticipantRelayService> logger) ILogger<VbytesParticipantRelayService> logger)
: VbytesRelayServiceBase(httpClientFactory, options), IVbytesParticipantRelayService : VbytesRelayServiceBase(httpClient, options), IVbytesParticipantRelayService
{ {
public const string HttpClientName = "VbytesRelay"; public const string HttpClientName = "VbytesRelay";
@ -21,6 +21,7 @@ public class VbytesParticipantRelayService(
if (configError is not null) if (configError is not null)
{ {
_logger.LogWarning("Relay configuration invalid: {Message}", configError.Message); _logger.LogWarning("Relay configuration invalid: {Message}", configError.Message);
return configError; return configError;
} }
@ -31,16 +32,19 @@ public class VbytesParticipantRelayService(
{ {
var result = await SendAsync(Options.ParticipantRegisterPath, payload, cancellationToken); var result = await SendAsync(Options.ParticipantRegisterPath, payload, cancellationToken);
_logger.LogInformation("Relay response: {StatusCode} - {Message}", result.StatusCode, result.Message); _logger.LogInformation("Relay response: {StatusCode} - {Message}", result.StatusCode, result.Message);
return result; return result;
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
_logger.LogWarning("Relay timed out calling {Path}", Options.ParticipantRegisterPath); _logger.LogWarning("Relay timed out calling {Path}", Options.ParticipantRegisterPath);
return new VbytesRelayResult(false, StatusCodes.Status504GatewayTimeout, "Upstream timeout."); return new VbytesRelayResult(false, StatusCodes.Status504GatewayTimeout, "Upstream timeout.");
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "VBytes participant relay exception."); _logger.LogError(ex, "VBytes participant relay exception.");
return new VbytesRelayResult(false, StatusCodes.Status502BadGateway, "Upstream relay failed."); return new VbytesRelayResult(false, StatusCodes.Status502BadGateway, "Upstream relay failed.");
} }
} }

View file

@ -1,20 +1,18 @@
using System.Net.Http.Json; using System.Text.Json;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Registration.API.Configuration; using Registration.API.Configuration;
namespace Registration.API.Services; namespace Registration.API.Services;
public abstract class VbytesRelayServiceBase( public abstract class VbytesRelayServiceBase(
IHttpClientFactory httpClientFactory, HttpClient httpClient,
IOptions<VbytesRelayOptions> options) IOptions<VbytesRelayOptions> options)
{ {
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; private readonly HttpClient _httpClient = httpClient;
protected readonly VbytesRelayOptions Options = options.Value; protected readonly VbytesRelayOptions Options = options.Value;
protected async Task<VbytesRelayResult> SendAsync(string path, object payload, CancellationToken cancellationToken) protected async Task<VbytesRelayResult> SendAsync(string path, object payload, CancellationToken cancellationToken)
{ {
var client = _httpClientFactory.CreateClient(VbytesParticipantRelayService.HttpClientName);
using var request = new HttpRequestMessage(HttpMethod.Post, BuildPath(path)) using var request = new HttpRequestMessage(HttpMethod.Post, BuildPath(path))
{ {
Content = JsonContent.Create(payload) Content = JsonContent.Create(payload)
@ -22,13 +20,13 @@ public abstract class VbytesRelayServiceBase(
request.Headers.TryAddWithoutValidation(Options.ApiKeyHeaderName, Options.ApiKey); request.Headers.TryAddWithoutValidation(Options.ApiKeyHeaderName, Options.ApiKey);
using var response = await client.SendAsync(request, cancellationToken); using var response = await _httpClient.SendAsync(request, cancellationToken);
var body = await response.Content.ReadAsStringAsync(cancellationToken); var body = await response.Content.ReadAsStringAsync(cancellationToken);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
Console.WriteLine($"[DEBUG] Relay success body: {body}"); Console.WriteLine($"[DEBUG] Relay success body: {body}");
return new VbytesRelayResult(true, StatusCodes.Status200OK, "Request relayed successfully."); return new VbytesRelayResult(true, StatusCodes.Status200OK, body);
} }
var statusCode = (int)response.StatusCode; var statusCode = (int)response.StatusCode;

View file

@ -6,10 +6,10 @@ using Registration.Domain.Models;
namespace Registration.API.Services; namespace Registration.API.Services;
public class VbytesVolunteerRelayService( public class VbytesVolunteerRelayService(
IHttpClientFactory httpClientFactory, HttpClient httpClient,
IOptions<VbytesRelayOptions> options, IOptions<VbytesRelayOptions> options,
ILogger<VbytesVolunteerRelayService> logger) ILogger<VbytesVolunteerRelayService> logger)
: VbytesRelayServiceBase(httpClientFactory, options), IVbytesVolunteerRelayService : VbytesRelayServiceBase(httpClient, options), IVbytesVolunteerRelayService
{ {
private readonly ILogger<VbytesVolunteerRelayService> _logger = logger; private readonly ILogger<VbytesVolunteerRelayService> _logger = logger;

View file

@ -46,7 +46,6 @@ export default function RegisterPage() {
setMessage({ type: "info", text: "Processing your registration..." }); setMessage({ type: "info", text: "Processing your registration..." });
try { try {
// 1. Check if the SSN has already been registered for the LAN
// Returns Ok (200) if NOT registered, Conflict (409) if registered // Returns Ok (200) if NOT registered, Conflict (409) if registered
const checkRes = await fetch(`/api/Registration/registered/${formData.ssn}`); const checkRes = await fetch(`/api/Registration/registered/${formData.ssn}`);
@ -56,17 +55,10 @@ export default function RegisterPage() {
return; return;
} }
// 2. Check membership status via GET Registration register
// Returns 200 (Ok) if member, 404 (NotFound) if not // Returns 200 (Ok) if member, 404 (NotFound) if not
const validateRes = await fetch(`/api/Registration/register/${formData.ssn}`); const validateRes = await fetch(`/api/Registration/register/${formData.ssn}`);
const isMember = validateRes.ok; const isMember = validateRes.ok;
// 3. Register the SSN in the membership system via POST
await fetch(`/api/Registration/register/${formData.ssn}`, {
method: "POST",
});
// 4. Register the participant for the event
const { ssn, ...participantData } = formData; const { ssn, ...participantData } = formData;
const finalPayload = { const finalPayload = {
...participantData, ...participantData,
@ -83,6 +75,10 @@ export default function RegisterPage() {
if (response.ok) { if (response.ok) {
setMessage({ type: "success", text: "Registration complete! You are now registered for the LAN." }); setMessage({ type: "success", text: "Registration complete! You are now registered for the LAN." });
await fetch(`/api/Registration/register/${formData.ssn}`, {
method: "POST",
});
} else { } else {
const errorData = await response.json(); const errorData = await response.json();
setMessage({ type: "error", text: errorData.message || "Event registration failed." }); setMessage({ type: "error", text: errorData.message || "Event registration failed." });