cambios de denuncias
This commit is contained in:
@@ -1,54 +1,64 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using System.Text.RegularExpressions;
|
||||
using GestionaDenunciasAN.Components;
|
||||
using GestionaDenunciasAN.Configuration;
|
||||
using GestionaDenunciasAN.Models;
|
||||
using GestionaDenunciasAN.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
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));
|
||||
|
||||
// Configurar servicios
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.Services.AddHttpClient("DefaultClient", client =>
|
||||
{
|
||||
client.BaseAddress = new Uri(Utilidades.urlSwagger());
|
||||
});
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.LoginPath = "/home";
|
||||
options.AccessDeniedPath = "/AccessDenied";
|
||||
});
|
||||
// Necesario para ver porqu<71> est<73> fallando ciertas cosas que dan el error Circuit
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.Name = "denuncias.auth";
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
options.LoginPath = "/";
|
||||
options.LogoutPath = "/api/auth/logout";
|
||||
options.ExpireTimeSpan = TimeSpan.FromHours(8);
|
||||
options.SlidingExpiration = false;
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddDataProtection();
|
||||
builder.Services.AddServerSideBlazor().AddCircuitOptions(option => { option.DetailedErrors = true; });
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
builder.Services.AddSession(options =>
|
||||
{
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.IsEssential = true;
|
||||
});
|
||||
|
||||
builder.Services.AddHttpClient("CertClient").ConfigurePrimaryHttpMessageHandler(() =>
|
||||
{
|
||||
return new HttpClientHandler
|
||||
{
|
||||
ClientCertificateOptions = ClientCertificateOption.Manual // Forzar la selecci<63>n del certificado
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
builder.Services.AddBlazorBootstrap();
|
||||
builder.Services.AddAntiforgery();
|
||||
builder.Services.AddSingleton<UserState>();
|
||||
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.AddHttpClient<IGestionaService, GestionaService>((sp, client) =>
|
||||
{
|
||||
var opts = sp.GetRequiredService<IOptions<GestionaOptions>>().Value;
|
||||
client.BaseAddress = new Uri(opts.ApiBase);
|
||||
client.DefaultRequestHeaders.Add("X-Gestiona-Access-Token", opts.AccessToken);
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -58,48 +68,189 @@ if (!app.Environment.IsDevelopment())
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseSession();
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var userState = context.RequestServices.GetService<UserState>();
|
||||
var path = context.Request.Path;
|
||||
context.Response.Headers.XFrameOptions = "DENY";
|
||||
context.Response.Headers.XContentTypeOptions = "nosniff";
|
||||
context.Response.Headers["Referrer-Policy"] = "no-referrer";
|
||||
context.Response.Headers.ContentSecurityPolicy =
|
||||
"default-src 'self'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data:; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net";
|
||||
await next();
|
||||
});
|
||||
|
||||
// Permitir solicitudes internas y recursos necesarios
|
||||
if (path == "/" ||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var path = context.Request.Path;
|
||||
var isPublic =
|
||||
path == "/" ||
|
||||
path.StartsWithSegments("/api") ||
|
||||
path.StartsWithSegments("/_blazor") ||
|
||||
path.StartsWithSegments("/_framework") ||
|
||||
path.StartsWithSegments("/bootstrap") ||
|
||||
path.StartsWithSegments("/Content") ||
|
||||
path.StartsWithSegments("/Scripts") ||
|
||||
path.StartsWithSegments("/css") ||
|
||||
path.StartsWithSegments("/js") ||
|
||||
path.StartsWithSegments("/favicon.ico") ||
|
||||
path.StartsWithSegments("/_framework"))
|
||||
path.StartsWithSegments("/favicon") ||
|
||||
Path.HasExtension(path.Value);
|
||||
|
||||
if (context.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
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 cookieBelongsToCurrentStartup =
|
||||
!string.IsNullOrWhiteSpace(cookieStartupStamp) &&
|
||||
string.Equals(cookieStartupStamp, appSessionLifetime.StartupStamp, StringComparison.Ordinal);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if (!hasStoredCredentials || !cookieBelongsToCurrentStartup)
|
||||
{
|
||||
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
if (!isPublic)
|
||||
{
|
||||
var returnUrl = $"{context.Request.Path}{context.Request.QueryString}";
|
||||
context.Response.Redirect($"/?returnUrl={Uri.EscapeDataString(returnUrl)}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPublic || context.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Redirigir al home si no hay token y la ruta no es p<>blica
|
||||
if (userState?.Token == null)
|
||||
var loginReturnUrl = $"{context.Request.Path}{context.Request.QueryString}";
|
||||
context.Response.Redirect($"/?returnUrl={Uri.EscapeDataString(loginReturnUrl)}");
|
||||
});
|
||||
app.UseAntiforgery();
|
||||
|
||||
var api = app.MapGroup("/api");
|
||||
|
||||
api.MapPost("/auth/login", async (
|
||||
LoginRequest request,
|
||||
HttpContext httpContext,
|
||||
GlobalLeaksClient globalLeaksClient,
|
||||
GlobalLeaksSessionStore sessionStore,
|
||||
LoginRateLimiter rateLimiter,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
var appSessionLifetime = httpContext.RequestServices.GetRequiredService<AppSessionLifetime>();
|
||||
if (!rateLimiter.AllowAttempt(ip))
|
||||
{
|
||||
Console.WriteLine($"Redirigiendo al home desde: {path}");
|
||||
context.Response.Redirect("/");
|
||||
return;
|
||||
return Results.Json(
|
||||
new ApiError("Demasiados intentos. Espera un minuto."),
|
||||
statusCode: StatusCodes.Status429TooManyRequests);
|
||||
}
|
||||
|
||||
// Continuar con la solicitud
|
||||
await next();
|
||||
});
|
||||
if (string.IsNullOrWhiteSpace(request.Username) ||
|
||||
string.IsNullOrWhiteSpace(request.Password) ||
|
||||
string.IsNullOrWhiteSpace(request.Authcode))
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError("Debes indicar usuario, contraseña y código 2FA."),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(request.Authcode.Trim(), @"^\d{6}$"))
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError("El código 2FA debe tener exactamente 6 dígitos."),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
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,
|
||||
cancellationToken);
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, session.Username),
|
||||
new("app_startup_stamp", appSessionLifetime.StartupStamp),
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(session.Role))
|
||||
{
|
||||
claims.Add(new Claim("gl_role", session.Role));
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var authProperties = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = false,
|
||||
AllowRefresh = true,
|
||||
};
|
||||
|
||||
await httpContext.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
principal,
|
||||
authProperties);
|
||||
|
||||
return Results.Ok(new LoginResponse(session.Username));
|
||||
}
|
||||
catch (GlobalLeaksValidationException ex)
|
||||
{
|
||||
return Results.Json(new ApiError(ex.Message), statusCode: ex.StatusCode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError("No se ha podido conectar con GlobalLeaks."),
|
||||
statusCode: StatusCodes.Status502BadGateway);
|
||||
}
|
||||
}).DisableAntiforgery();
|
||||
|
||||
api.MapPost("/auth/logout", async (
|
||||
HttpContext httpContext,
|
||||
GlobalLeaksSessionStore sessionStore,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var username = httpContext.User.Identity?.Name;
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
await sessionStore.DeleteAsync(username, cancellationToken);
|
||||
}
|
||||
|
||||
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Results.Ok(new { ok = true });
|
||||
}).DisableAntiforgery();
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
Reference in New Issue
Block a user