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() // [SEGURIDAD] Solo permitimos los verbos necesarios. Bloqueamos TRACE, HEAD, etc. .WithMethods("GET", "POST", "PUT", "DELETE", "OPTIONS"); }); }); // 1. DbContext var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); // 2. Identity builder.Services.AddIdentity(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() .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] 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[] {} } }); }); 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();