Files
Chatbot-ElDia/ChatbotApi/Program.cs

206 lines
7.1 KiB
C#

using ChatbotApi.Data.Models;
using DotNetEnv;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Identity;
using Microsoft.OpenApi.Models;
using Microsoft.EntityFrameworkCore; // Necesario para UseSqlServer
// Cargar variables de entorno
Env.Load();
var builder = WebApplication.CreateBuilder(args);
// [SEGURIDAD] Configuración de Kestrel para ocultar el header "Server" (Information Disclosure)
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.AddServerHeader = false;
});
// [SEGURIDAD] CORS Restrictivo
var myAllowSpecificOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: myAllowSpecificOrigins,
policy =>
{
policy.WithOrigins(
"http://192.168.5.129:8081",
"http://192.168.5.129:8082",
"http://localhost:5173",
"http://localhost:5174",
"http://localhost:5175")
.AllowAnyHeader()
.AllowCredentials() // [SEGURIDAD] Necesario para Cookies HttpOnly
.WithMethods("GET", "POST", "PUT", "DELETE", "OPTIONS");
});
});
// 1. DbContext
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppContexto>(options =>
options.UseSqlServer(connectionString));
// 2. Identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
// [SEGURIDAD] Políticas de contraseñas robustas
options.Password.RequireDigit = true;
options.Password.RequiredLength = 12; // Aumentado de 8 a 12
options.Password.RequireNonAlphanumeric = true; // Requerimos símbolos
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
// [SEGURIDAD] Bloqueo de cuenta tras intentos fallidos (Mitigación Fuerza Bruta)
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
})
.AddEntityFrameworkStores<AppContexto>()
.AddDefaultTokenProviders();
builder.Services.AddMemoryCache();
// JWT Config
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("La clave JWT no está configurada.")
)),
ClockSkew = TimeSpan.Zero // Token expira exactamente cuando dice
};
// [SEGURIDAD] Evento para permitir lectura de Token desde Cookie
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Cookies.ContainsKey("X-Access-Token"))
{
context.Token = context.Request.Cookies["X-Access-Token"];
}
return Task.CompletedTask;
}
};
});
// [SEGURIDAD] RATE LIMITING AVANZADO
builder.Services.AddRateLimiter(options =>
{
// Política General: 30 peticiones por minuto (Suficiente para uso normal del chat)
options.AddFixedWindowLimiter(policyName: "fixed", limiterOptions =>
{
limiterOptions.PermitLimit = 30;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 2;
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
// [SEGURIDAD] Política Estricta para Login: 5 intentos por minuto (Anti Fuerza Bruta)
options.AddFixedWindowLimiter(policyName: "login-limit", limiterOptions =>
{
limiterOptions.PermitLimit = 5;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 0; // No encolar, rechazar directo
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Chatbot API", Version = "v1" });
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }
},
new string[] {}
}
});
});
// [REFACTORING] Servicios de Aplicación
builder.Services.AddHttpClient(); // Cliente HTTP eficiente
builder.Services.AddScoped<ChatbotApi.Services.IChatService, ChatbotApi.Services.ChatService>();
var app = builder.Build();
// [SEGURIDAD] Headers de Seguridad HTTP
// Se recomienda usar la librería 'NetEscapades.AspNetCore.SecurityHeaders'
// Si no la tienes, instálala: dotnet add package NetEscapades.AspNetCore.SecurityHeaders
app.UseSecurityHeaders(policy =>
{
policy.AddDefaultSecurityHeaders();
policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365); // HSTS 1 año
policy.AddContentSecurityPolicy(builder =>
{
builder.AddDefaultSrc().Self();
// Permitimos scripts inline solo si es estrictamente necesario para Swagger, idealmente usar nonces
builder.AddScriptSrc().Self().UnsafeInline();
builder.AddStyleSrc().Self().UnsafeInline();
builder.AddImgSrc().Self().Data();
builder.AddConnectSrc().Self()
.From("http://localhost:5173")
.From("http://localhost:5174")
.From("http://localhost:5175"); // Permitir conexiones explícitas
builder.AddFrameAncestors().None(); // Previene Clickjacking
});
policy.RemoveServerHeader(); // Capa extra para ocultar Kestrel
});
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
// [SEGURIDAD] Forzar HTTPS en producción
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors(myAllowSpecificOrigins);
// [SEGURIDAD] Aplicar Rate Limiting
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// Mapeamos los controladores. Las políticas de Rate Limiting se aplicarán vía Atributos en cada Controller.
app.MapControllers();
app.Run();