cambios en apidenuncias

This commit is contained in:
2026-04-30 08:55:00 +02:00
parent 3b77fc827d
commit b22f02807d
295 changed files with 26359 additions and 3777 deletions

View File

@@ -15,9 +15,7 @@ var builder = WebApplication.CreateBuilder(args);
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.GetCultureInfo("es-ES");
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo("es-ES");
builder.Services.Configure<GestionaOptions>(builder.Configuration.GetSection("Gestiona"));
builder.Services.Configure<GlobalLeaksOptions>(builder.Configuration.GetSection(GlobalLeaksOptions.SectionName));
builder.Services.Configure<ComplaintStorageOptions>(builder.Configuration.GetSection(ComplaintStorageOptions.SectionName));
builder.Services.Configure<ApiDenunciasOptions>(builder.Configuration.GetSection(ApiDenunciasOptions.SectionName));
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
@@ -40,23 +38,18 @@ builder.Services.AddAuthorization();
builder.Services.AddDataProtection();
builder.Services.AddServerSideBlazor().AddCircuitOptions(option => { option.DetailedErrors = true; });
builder.Services.AddHttpContextAccessor();
builder.Services.AddBlazorBootstrap();
builder.Services.AddAntiforgery();
builder.Services.AddScoped<UserState>();
builder.Services.AddSingleton<AppSessionLifetime>();
builder.Services.AddSingleton<LoginRateLimiter>();
builder.Services.AddSingleton<GlobalLeaksSessionStore>();
builder.Services.AddScoped<GlobalLeaksClient>();
builder.Services.AddScoped<IDenunciaStore, MySqlDenunciaStore>();
builder.Services.AddScoped<IInboxTrackingService, InboxTrackingService>();
builder.Services.AddScoped<DenunciaInboxService>();
builder.Services.AddScoped<GestionaDocumentWorkflowService>();
builder.Services.AddScoped<ApiDenunciasClient>();
builder.Services.AddScoped<IDenunciaStore, ApiDenunciaStore>();
builder.Services.AddScoped<IInboxTrackingService, ApiInboxTrackingService>();
builder.Services.AddHttpClient<IGestionaService, GestionaService>((sp, client) =>
builder.Services.AddHttpClient(ApiDenunciasClient.HttpClientName, (sp, client) =>
{
var opts = sp.GetRequiredService<IOptions<GestionaOptions>>().Value;
client.BaseAddress = new Uri(opts.ApiBase);
client.DefaultRequestHeaders.Add("X-Gestiona-Access-Token", opts.AccessToken);
var opts = sp.GetRequiredService<IOptions<ApiDenunciasOptions>>().Value;
client.BaseAddress = new Uri(opts.BaseUrl.TrimEnd('/') + "/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});
@@ -104,22 +97,20 @@ app.Use(async (context, next) =>
var username = context.User.Identity?.Name?.Trim();
var appSessionLifetime = context.RequestServices.GetRequiredService<AppSessionLifetime>();
var cookieStartupStamp = context.User.FindFirst("app_startup_stamp")?.Value;
var hasStoredCredentials = false;
var hasApiToken = !string.IsNullOrWhiteSpace(context.User.FindFirst(ApiDenunciasClient.AccessTokenClaim)?.Value);
var tokenIsStillValid = true;
var cookieBelongsToCurrentStartup =
!string.IsNullOrWhiteSpace(cookieStartupStamp) &&
string.Equals(cookieStartupStamp, appSessionLifetime.StartupStamp, StringComparison.Ordinal);
if (!string.IsNullOrWhiteSpace(username))
var tokenExpiresAt = context.User.FindFirst(ApiDenunciasClient.TokenExpiresAtClaim)?.Value;
if (!string.IsNullOrWhiteSpace(tokenExpiresAt) &&
DateTimeOffset.TryParse(tokenExpiresAt, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var expiresAt))
{
var sessionStore = context.RequestServices.GetRequiredService<GlobalLeaksSessionStore>();
var storedSession = await sessionStore.GetAsync(username, context.RequestAborted);
hasStoredCredentials =
storedSession is not null &&
!string.IsNullOrWhiteSpace(storedSession.Username) &&
!string.IsNullOrWhiteSpace(storedSession.Password);
tokenIsStillValid = expiresAt > DateTimeOffset.UtcNow;
}
if (!hasStoredCredentials || !cookieBelongsToCurrentStartup)
if (string.IsNullOrWhiteSpace(username) || !hasApiToken || !tokenIsStillValid || !cookieBelongsToCurrentStartup)
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
@@ -148,8 +139,7 @@ var api = app.MapGroup("/api");
api.MapPost("/auth/login", async (
LoginRequest request,
HttpContext httpContext,
GlobalLeaksClient globalLeaksClient,
GlobalLeaksSessionStore sessionStore,
ApiDenunciasClient apiClient,
LoginRateLimiter rateLimiter,
CancellationToken cancellationToken) =>
{
@@ -180,34 +170,25 @@ api.MapPost("/auth/login", async (
try
{
var session = await globalLeaksClient.LoginAsync(
request.Username.Trim(),
request.Password,
request.Authcode.Trim(),
cancellationToken);
var resolvedUsername = string.IsNullOrWhiteSpace(session.Username)
? request.Username.Trim()
: session.Username.Trim();
session = new GlSession(session.Id, resolvedUsername, session.Role);
await sessionStore.SaveAsync(
session.Username,
request.Password,
session.Id,
session.Role,
var login = await apiClient.LoginAsync(
request with
{
Username = request.Username.Trim(),
Authcode = request.Authcode.Trim()
},
cancellationToken);
var claims = new List<Claim>
{
new(ClaimTypes.Name, session.Username),
new(ClaimTypes.Name, login.Username),
new("app_startup_stamp", appSessionLifetime.StartupStamp),
new(ApiDenunciasClient.AccessTokenClaim, login.AccessToken),
new(ApiDenunciasClient.TokenExpiresAtClaim, login.ExpiresAtUtc.ToString("O", CultureInfo.InvariantCulture)),
};
if (!string.IsNullOrWhiteSpace(session.Role))
if (!string.IsNullOrWhiteSpace(login.Role))
{
claims.Add(new Claim("gl_role", session.Role));
claims.Add(new Claim("gl_role", login.Role));
}
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
@@ -223,29 +204,35 @@ api.MapPost("/auth/login", async (
principal,
authProperties);
return Results.Ok(new LoginResponse(session.Username));
return Results.Ok(new LoginResponse(login.Username));
}
catch (GlobalLeaksValidationException ex)
catch (UnauthorizedAccessException ex)
{
return Results.Json(new ApiError(ex.Message), statusCode: ex.StatusCode);
return Results.Json(new ApiError(ex.Message), statusCode: StatusCodes.Status401Unauthorized);
}
catch
catch (InvalidOperationException ex)
{
return Results.Json(
new ApiError("No se ha podido conectar con GlobalLeaks."),
statusCode: StatusCodes.Status502BadGateway);
new ApiError(ex.Message),
statusCode: StatusCodes.Status400BadRequest);
}
}).DisableAntiforgery();
api.MapPost("/auth/logout", async (
HttpContext httpContext,
GlobalLeaksSessionStore sessionStore,
ApiDenunciasClient apiClient,
CancellationToken cancellationToken) =>
{
var username = httpContext.User.Identity?.Name;
if (!string.IsNullOrWhiteSpace(username))
if (httpContext.User.Identity?.IsAuthenticated == true)
{
await sessionStore.DeleteAsync(username, cancellationToken);
try
{
await apiClient.LogoutAsync(cancellationToken);
}
catch
{
// Aunque la API no responda, el usuario debe poder cerrar la sesion local.
}
}
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);