denuncias
This commit is contained in:
@@ -19,7 +19,11 @@ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo("es-ES");
|
||||
builder.Services.Configure<ApiDenunciasOptions>(builder.Configuration.GetSection(ApiDenunciasOptions.SectionName));
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
.AddInteractiveServerComponents(options =>
|
||||
{
|
||||
options.DetailedErrors = true;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(180);
|
||||
});
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
|
||||
builder.Services
|
||||
@@ -37,12 +41,17 @@ builder.Services
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddDataProtection();
|
||||
builder.Services.AddServerSideBlazor().AddCircuitOptions(option => { option.DetailedErrors = true; });
|
||||
builder.Services.AddServerSideBlazor().AddCircuitOptions(option =>
|
||||
{
|
||||
option.DetailedErrors = true;
|
||||
option.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(180);
|
||||
});
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddAntiforgery();
|
||||
builder.Services.AddScoped<UserState>();
|
||||
builder.Services.AddSingleton<AppSessionLifetime>();
|
||||
builder.Services.AddSingleton<LoginRateLimiter>();
|
||||
builder.Services.AddScoped<UiBusyService>();
|
||||
builder.Services.AddScoped<ApiDenunciasClient>();
|
||||
builder.Services.AddScoped<IDenunciaStore, ApiDenunciaStore>();
|
||||
builder.Services.AddScoped<IInboxTrackingService, ApiInboxTrackingService>();
|
||||
@@ -140,11 +149,60 @@ app.UseAntiforgery();
|
||||
|
||||
var api = app.MapGroup("/api");
|
||||
|
||||
api.MapPost("/auth/prepare", async (
|
||||
ApiLoginPrepareRequest request,
|
||||
ApiDenunciasClient apiClient,
|
||||
IOptions<ApiDenunciasOptions> apiOptions,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) ||
|
||||
string.IsNullOrWhiteSpace(request.Password))
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError("Debes indicar usuario y contrasena."),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var loginTimeoutSeconds = Math.Clamp(apiOptions.Value.LoginTimeoutSeconds, 15, 300);
|
||||
using var loginTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
loginTimeout.CancelAfter(TimeSpan.FromSeconds(loginTimeoutSeconds));
|
||||
|
||||
var prepared = await apiClient.PrepareLoginAsync(
|
||||
request with { Username = request.Username.Trim() },
|
||||
loginTimeout.Token);
|
||||
|
||||
return Results.Ok(prepared);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return Results.Json(new ApiError(ex.Message), statusCode: StatusCodes.Status401Unauthorized);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.Json(new ApiError(ex.Message), statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError($"La API de denuncias no ha respondido en {apiOptions.Value.LoginTimeoutSeconds} segundos ({apiOptions.Value.BaseUrl})."),
|
||||
statusCode: StatusCodes.Status504GatewayTimeout);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError($"No se ha podido conectar con la API de denuncias ({apiOptions.Value.BaseUrl}). Detalle: {ex.Message}"),
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
}).DisableAntiforgery();
|
||||
|
||||
api.MapPost("/auth/login", async (
|
||||
LoginRequest request,
|
||||
HttpContext httpContext,
|
||||
ApiDenunciasClient apiClient,
|
||||
LoginRateLimiter rateLimiter,
|
||||
IOptions<ApiDenunciasOptions> apiOptions,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
@@ -174,13 +232,17 @@ api.MapPost("/auth/login", async (
|
||||
|
||||
try
|
||||
{
|
||||
var loginTimeoutSeconds = Math.Clamp(apiOptions.Value.LoginTimeoutSeconds, 15, 300);
|
||||
using var loginTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
loginTimeout.CancelAfter(TimeSpan.FromSeconds(loginTimeoutSeconds));
|
||||
|
||||
var login = await apiClient.LoginAsync(
|
||||
request with
|
||||
{
|
||||
Username = request.Username.Trim(),
|
||||
Authcode = request.Authcode.Trim()
|
||||
},
|
||||
cancellationToken);
|
||||
loginTimeout.Token);
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
@@ -220,6 +282,104 @@ api.MapPost("/auth/login", async (
|
||||
new ApiError(ex.Message),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError($"La API de denuncias no ha respondido en {apiOptions.Value.LoginTimeoutSeconds} segundos ({apiOptions.Value.BaseUrl}). Comprueba los logs de ApiDenuncias: probablemente esta esperando a GlobalLeaks o a una dependencia externa."),
|
||||
statusCode: StatusCodes.Status504GatewayTimeout);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError($"No se ha podido conectar con la API de denuncias ({apiOptions.Value.BaseUrl}). Detalle: {ex.Message}"),
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
}).DisableAntiforgery();
|
||||
|
||||
api.MapPost("/auth/complete", async (
|
||||
ApiLoginCompleteRequest request,
|
||||
HttpContext httpContext,
|
||||
ApiDenunciasClient apiClient,
|
||||
IOptions<ApiDenunciasOptions> apiOptions,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var appSessionLifetime = httpContext.RequestServices.GetRequiredService<AppSessionLifetime>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.PendingLoginId) ||
|
||||
string.IsNullOrWhiteSpace(request.Authcode))
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError("Debes indicar el codigo 2FA."),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(request.Authcode.Trim(), @"^\d{6}$"))
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError("El codigo 2FA debe tener exactamente 6 digitos."),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var loginTimeoutSeconds = Math.Clamp(apiOptions.Value.LoginTimeoutSeconds, 15, 300);
|
||||
using var loginTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
loginTimeout.CancelAfter(TimeSpan.FromSeconds(loginTimeoutSeconds));
|
||||
|
||||
var login = await apiClient.CompleteLoginAsync(
|
||||
request with { Authcode = request.Authcode.Trim() },
|
||||
loginTimeout.Token);
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
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(login.Role))
|
||||
{
|
||||
claims.Add(new Claim("gl_role", login.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(login.Username));
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return Results.Json(new ApiError(ex.Message), statusCode: StatusCodes.Status401Unauthorized);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError(ex.Message),
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError($"La API de denuncias no ha respondido en {apiOptions.Value.LoginTimeoutSeconds} segundos ({apiOptions.Value.BaseUrl})."),
|
||||
statusCode: StatusCodes.Status504GatewayTimeout);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return Results.Json(
|
||||
new ApiError($"No se ha podido conectar con la API de denuncias ({apiOptions.Value.BaseUrl}). Detalle: {ex.Message}"),
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
}).DisableAntiforgery();
|
||||
|
||||
api.MapPost("/auth/logout", async (
|
||||
|
||||
Reference in New Issue
Block a user